perf: skip column decoding for cached entities and precompute constructor metadata#252
Merged
Merged
Conversation
…ctor metadata Queries that repeat the same entity across rows (joins over eager foreign keys) paid for decoding the parent's columns on every row even though the mapper's early cache lookup skips construction on a hit. The new ColumnSkipper probes the same caches before column extraction: for each entity region it decodes only the columns up to and including the primary key and, on a hit, leaves the remaining columns undecoded. The mapper then resolves the entity from the primary key alone. Column access stays in ascending order, and interner hits are pinned in a strong reference list until the next row so a garbage collection between probe and mapping cannot clear them. Skip regions are computed once per record type alongside the compiled argument plan. Constructing a record also carried per-invocation reflective overhead: getParameterTypes and getParameters clone their arrays on every call, setAccessible ran per call, and the enum step resolved its mapper per value. Constructor metadata (parameter names, non-null flags, primitive flags, the accessible constructor) is now precomputed and cached per constructor, and enum mappers are resolved once at plan compilation. The integration test counts positional column reads through a JDBC proxy and asserts the exact expected number for the pet-owner-city graph, covering nested regions, interner identity, and null foreign keys.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the remaining checklist item of #246: skip parent column decoding on cache hits. The mapper's early cache lookup already skips construction when a nested entity is cached, but every column of every row was still decoded out of the JDBC
ResultSetbefore the mapper ran, including the repeated parent columns the lookup then ignored.Column skipping
A new
ColumnSkipperprobes the same caches as the mapper's early lookup, but before column extraction. For each entity column region it decodes only the columns up to and including the primary key; on a cache hit the remaining columns are left undecoded, and the mapper resolves the entity from the primary key alone.EntityCacheatREPEATABLE_READ+ and the query-scopedWeakInternerotherwise; the top-level region only consults the entity cache, since top-level records are never interned. This mirrorsRecordStepandnewInstanceexactly.ObjectMapperexposes the skipper via a default method returning null, so only the record mapper opts in; sealed-type mappers and the JPA path are untouched.Constructor metadata
Record construction carried per-invocation reflective overhead on the row mapping hot path:
getParameterTypes()andgetParameters()clone their arrays on every call,setAccessible(true)ran per call, and the enum step resolved its mapper per value. Constructor metadata (parameter names, non-null flags, primitive flags, the accessible constructor instance) is now precomputed and cached per constructor; enum mappers are resolved once at plan compilation. Error messages and diagnostics are unchanged.Testing
ColumnSkipperIntegrationTestcounts positional column reads through a JDBC proxy and asserts the exact expected number for the pet → owner → city graph: full decode on first occurrence, key-only decode on repeats, full decode for null foreign keys, nested city regions, and interner identity across rows. Full suites green: storm-core 2264, storm-kotlin 1627, storm-java21 517.Measured effect (#214 suite, PostgreSQL 17, same session)
The collapsed error bar on joinWithMapping100 (±372 → ±69) reflects the removed allocation pressure.