You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by "Andrus Adamchik (Jira)" <ji...@apache.org> on 2026/10/10 23:56:00 UTC
[jira] [Commented] (CAY-3046) Refreshing an object from a snapshot discards to-ones, causing fault resolution on a shared DataContext
[ https://issues.apache.org/jira/browse/CAY-3046?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18125926#comment-18125926 ]
Andrus Adamchik commented on CAY-3046:
--------------------------------------
The fix actually add a bit of time for a single query in isolation, as for each to-one it will try to analyze whether the FK has changed or not, and invalidate selectively in the former case. But I am seeing a huge overall throughput improvement (up to 2x) in situations when processing a selected data in the app code is itself somewhat sluggish (e.g., when the result is big, and there is an iterator that does serialization). And this is even without removing any synchronization locks. There are simply fewer faults firing after the fix, so there's less contention.
> Refreshing an object from a snapshot discards to-ones, causing fault resolution on a shared DataContext
> -------------------------------------------------------------------------------------------------------
>
> Key: CAY-3046
> URL: https://issues.apache.org/jira/browse/CAY-3046
> Project: Cayenne
> Issue Type: Bug
> Reporter: Andrus Adamchik
> Assignee: Andrus Adamchik
> Priority: Major
> Fix For: 5.0-M4
>
>
> DataRowUtils.refreshObjectWithSnapshot unconditionally replaces every to-one of the refreshed object with a new ToOneFault, even when the snapshot is unchanged:
> {noformat}
> public boolean visitToOne(ToOneProperty property) {
> property.invalidate(object);
> return true;
> }
> {noformat}
> ObjectResolver.objectFromDataRow calls it for every row of a query whose object is already registered. So any select that returns an already-registered object throws away its resolved to-one targets. The select that does the damage does not need any prefetches of its own.
> For readOnly entities and HOLLOW objects the same call also passes invalidateToManyRelationships=true, so prefetched to-many lists are discarded as well, and re-reading them costs a DB query. COMMITTED objects of writable entities are spared on the to-many side with the comment "they might have just been prefetched", which applies equally to to-ones.
> On a shared DataContext this also becomes a lock contention problem that may cause an avalanche of faulting. Threads A and B run the same query with the same prefetches:
> 1. A resolves its result under the ObjectStore lock, the prefetch links the to-one targets into the parent objects, A releases the lock and starts reading them.
> 2. B resolves the same rows under the lock. HierarchicalObjectResolver first resolves all root rows, which faults every to-one on every shared object, and only afterwards walks the prefetch tree and links the targets back.
> 3. A reads a to-one without a lock during that gap, sees the ToOneFault, calls resolveFault, and blocks on the ObjectStore monitor until B is done. The target is already registered, so no DB query is issued, but A pays one monitor acquisition per faulted property per row.
> If B's query does not prefetch the same path, or has no prefetches, the relationship stays a fault until some later query prefetches it again.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)