Hi Hugi, Sharing your chart with my team and clients to prove I am not making up those performance gains :)
> Already got a few list e-mails from you queued up that I want to reply to, > just haven't had the time. Looking forward! > * BTW, not having to upgrade the model version manually is a lovely usability > enhancement for testing like this. I had hoped it will be a drop-in replacement. But then we still needed to patch both Agrest and Bootique to try out the snapshot. At that rate of change, hard to keep things aligned. Andrus > On Oct 15, 2026, at 6:51 AM, Hugi Thordarson <[email protected]> wrote: > > Hi Andrus, > > I'm like a child at christmas with the Cayenne updates these days. Was > planning on waiting for M4 before upgrading, but lost willpower and moved to > -SNAPSHOT last night. Quick migration as usual (2 utility libs, 1 large-ish > app). > > I don't often share ObjectContexts between threads and I've done very little > caching / performance tuning in the app I migrated so it's performance is > more DB dependent than Cayenne dependent (few huge queries rather than many > small queries), but I benchmarked the app before/after and lo and behold; The > page I measured — the heaviest page in the app — still got a free 11% > performance improvement! Seemingly mostly due to the optimized data > structures/fetching and a somewhat enormous reduction in memory use :). > > Asked Claude to do some benchmarking, comparing M3 and -SNAPSHOT for our core > logic. The report is here, clearly shows the huge concurrency/memory wins: > https://www.whoacommunity.com/cayenne-load.html . (also shows I really need > to start looking into caching in this app) > > Since everything works great I've deployed -SNAPSHOT to production for > testing (relatively high-traffic and lots of data, my own project so no one > dies in case of errors). > > Really looking forward to having the time to sit down and try everything > else, lots of lovely things there! Already got a few list e-mails from you > queued up that I want to reply to, just haven't had the time. *sigh* I'm not > a great correspondent these days. Need more hours in the day. > > In short, yet another awesome upgrade, so once again, thanks! > > ==== > > Notes from last night's upgrade process if anyone is thinking about trying: > > * 2min : Organized imports, done (CAY-2826) > * 5min : Remove serialization from superclass templates and regenerate > (CAY-3020) > * 10min : Accommodate renamed AST* classes and a few minor API changes (like > the change to Entity.resolvePathComponents) (CAY-3039) > * 10min : Fix a couple of NullPointerExceptions, due to null values appearing > with the changed join behaviour from CAY-3032. Worth it though, good change. > * 10min : CAY-3010, Turns out a couple of timestamp columns in my postgres DB > were defined as timestamptz (timezoned) which the new logic understandably > didn't want to map to a LocalDateTime. In my case the fix was easy, the > columns weren't supposed to be timezoned (since I live in a developer's > paradise of eternal UTC+00:00) so just changed the type and everything worked > fine. > * BTW, not having to upgrade the model version manually is a lovely usability > enhancement for testing like this. > > ==== > > Cheers, > - hugi > > > >> On 14 Oct 2026, at 19:46, Andrus Adamchik <[email protected]> wrote: >> >> The first wave of changes was for concurrent access to a single context >> only, the second - for any context (concurrent or not). Concurrency >> improvements made shared contexts 90-95% as fast as non-shared ones. Other >> changes made every context ~2x as fast either by making processing more >> efficient or allocating less memory or, often, both. The big ones are: >> >> 1. Bespoke array-based DataRows (fast value reads, no HashMap allocation) >> 2. Bespoke snapshot cache (much faster, smaller, cheaper to manage LRU) >> >> Also, I just finished this round (for now; there's always more stuff we can >> improve, like entity-specific generated DataRows with precompiled object >> accessors, etc.). Now in my webapps, Cayenne portion of the request fell >> below that of JDBC, for the first time ever. >> >> Andrus >> >>> On Oct 14, 2026, at 3:05 PM, John Huss <[email protected]> wrote: >>> >>> Are those optimizations for all ObjectContexts, not just the app-scoped >>> single context used by many threads that you mentioned? >>> >>> On Mon, Oct 12, 2026 at 7:15 PM Andrus Adamchik <[email protected]> wrote: >>> >>>> And there are more select optimizations. All the CPU time savings below >>>> are "the Cayenne part of a select query" : >>>> >>>> * https://issues.apache.org/jira/browse/CAY-3049 Faster, lower-overhead >>>> LRU snapshot cache. Saves 40-60% CPU time on plain selects, 20-45% with >>>> prefetches. >>>> * https://issues.apache.org/jira/browse/CAY-3050 [STILL IN PROGRESS, but >>>> almost ready] Faster. Saves 30-40% CPU time on plain selects, 20-40% with >>>> prefetches. 25-30% less memory allocation. >>>> * https://issues.apache.org/jira/browse/CAY-3051 somewhat lower memory >>>> allocation overhead >>>> >>>> The numbers almost look too good to be true :) But good data structures go >>>> a long way. I will be checking this in a real application soon, and would >>>> love an independent confirmation once CAY-3050 makes it in. >>>> >>>> Andrus >>>> >>>>> On Oct 11, 2026, at 6:34 PM, Andrus Adamchik <[email protected]> >>>> wrote: >>>>> >>>>> Hi, >>>>> >>>>> Just pushed a couple of big improvements to DataContext read >>>> concurrency. Related jiras: CAY-3044, CAY-3045, CAY-3046, CAY-3047, >>>> CAY-3048. >>>>> >>>>> Consider a single DataContext used by many threads in parallel for >>>> selecting objects. I have a number of very active read-only apps, and an >>>> app-scoped DataContext has always been the default pattern, especially >>>> valuable with local cache. It works great until concurrency increases >>>> causing every single select to queue up on ObjectStore waiting to load its >>>> rows into the context. >>>>> >>>>> So there was a de-facto hard cap on context throughput. Previously, I >>>> solved this via context pooling. This is ugly and luckily is no longer >>>> needed in this release. In my benchmarks, throughput improved 2x. This >>>> number will vary depending on the connection pool, CPU, DB speed of course. >>>> But lock contention is gone and the new usage advice is: >>>>> >>>>> 1. If you DON'T use local cache, then a new DataContext per thread (= >>>> per request) is the most performant option. >>>>> 2. If you DO use local cache (or want to keep such option open for the >>>> future; or don't want to deal with per-request scope), use a shared >>>> DataContext and don't worry about locking. This option is slightly (~5%) >>>> slower than a per-thread context, but this is negligible. >>>>> >>>>> Before the release, I would appreciate if someone could try it out and >>>> report any issues. The changes touch on the object / snapshot consistency >>>> model. So if you see anything weird, like older versions of objects >>>> appearing after newer ones, please report. (To be clear, I don't expect >>>> this to happen :)) >>>>> >>>>> >>>>> --- >>>>> >>>>> Finally, some high-level technical details. You can stop reading here :) >>>>> >>>>> * Instead of broad per-select lock to load objects, the locking is done >>>> per object, using private "striped" locks. I was surprised using >>>> "synchronized" in a loop (on different monitors) is not slow. But >>>> apparently it is fast with modern Java. >>>>> * ObjectContext / ObjectStore object lookups are not synchronized anymore >>>>> * Eventual consistency of object states is ensured by per-object locks >>>> and incrementing snapshot versions. We had snap versions before, but now >>>> they are faster, smarter and actually help. >>>>> * Merge strategy fix to avoid excessive to-one faulting post-query. I >>>> was occasionally confused seeing objects fetched one-by-one right after >>>> they were fetched by another query prefetch. Thought this was due to weak >>>> references. Nope, just faulty merge logic + concurrency. >>>>> * Per-statement snapshot versions (instead of per-object) >>>>> * Safer fault checking to avoid a specific race condition causing >>>> ClassCastExceptions under load >>>>> >>>>> >>>>> Andrus >>>> >>>> >> >
