(improvement) (cython only) cache deserializer instances in find_deserializer and m… (hundreds of ns improvements - 6-40x faster!) - #741
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves performance in the Cython fast-path by caching deserializer lookups and per-column deserializer arrays, avoiding repeated class-resolution work and repeated Deserializer object creation across result sets.
Changes:
- Add module-level Cython dict caches for
find_deserializer()andmake_deserializers()keyed bycqltypeobjects / tuples ofcqltypeobjects. - Update
find_deserializer()/make_deserializers()to consult and populate these caches. - Add a pytest-based benchmark module to compare cached vs uncached behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| cassandra/deserializers.pyx | Adds caching for deserializer instance resolution and for per-column deserializer arrays. |
| benchmarks/test_deserializer_cache_benchmark.py | Introduces correctness checks + pytest-benchmark-style benchmarks for the new caching behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5894e22 to
44cdd0d
Compare
9dc1ddf to
4a7b199
Compare
4a7b199 to
e5fe679
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
|
Rebased onto current VectorType subtype-dispatch correctness (the main thing I scrutinized): More importantly, the caching design here is safe by construction for when that dispatch does land:
One real gotcha I found and worked around: Testing: Rebuilt the Cython extensions locally (
All 5 existing review threads (from the Copilot review) were already resolved by the prior "Address review" commit and remain resolved. No unresolved threads to address; CI was green on the pre-rebase head and the rebase introduced no code changes to |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
cassandra/deserializers.pyx:464
make_deserializers()previously returned a fresh array per call; now it may return a shared cached array, which is a behavioral change for any external callers that (intentionally or accidentally) mutate the returned container. Relying on a docstring warning is fragile because a single mutation can corrupt the cached value for all subsequent consumers. Safer alternatives: (1) cache an immutable representation (e.g., a tuple of deserializers) and return a new array built from it each time, or (2) return an immutable type to callers and update internal callers accordingly. If keeping the shared-array behavior is required for performance, consider restricting/clearly markingmake_deserializersas internal-only and ensuring no public API contracts imply mutability.
def make_deserializers(cqltypes):
"""Create an array of Deserializers for each given cqltype in cqltypes.
The returned array may be a cached object shared across callers.
Callers must not modify the returned array."""
cdef tuple key = tuple(cqltypes)
try:
return _make_deserializers_cache[key]
|
Some logical collision with #690 - need to solve it. |
…ake_deserializers Cache find_deserializer() and make_deserializers() results in Cython cdef dict caches keyed on cqltype objects to avoid repeated class lookups and Deserializer object creation on every result set. Using cqltype objects (not id()) as cache keys holds strong references, preventing GC/id-reuse correctness issues with parameterized types.
- Add 256-entry size cap to both _deserializer_cache and _make_deserializers_cache to prevent unbounded growth from non-interned parameterized types in unprepared queries. - Add clear_deserializer_caches() public API so that runtime Des* class overrides (e.g. DesBytesType = DesBytesTypeByteArray for cqlsh) can flush stale cached instances. - Add get_deserializer_cache_sizes() diagnostic helper. - Document override/cache interaction in code comments. - Fix benchmark copyright (DataStax -> ScyllaDB), add pytest.importorskip guards for pytest-benchmark and Cython. - Add 11 unit tests for cache hit/miss, clear, eviction bounds, and size reporting. - Add clear_deserializer_caches() calls to integration test for DesBytesType override. - Add regression tests proving VectorType parameterizations that differ only by subtype (e.g. VectorType<float> vs VectorType<smallint>) get distinct, non-colliding cache entries: apply_parameters() never interns, so each is a fresh class object, and find_deserializer()/ make_deserializers() key on that object's identity rather than its name (VectorType's generated __name__ does not encode the subtype, only vector_size, so a name-based cache key would have been unsafe here).
e5fe679 to
91549a0
Compare
Cache find_deserializer() and make_deserializers() results in Cython cdef dict caches keyed on cqltype objects to avoid repeated class lookups and Deserializer object creation on every result set.
Using cqltype objects (not id()) as cache keys holds strong references, preventing GC/id-reuse correctness issues with parameterized types.
Motivation
On every result set, make_deserializers(coltypes) is called from row_parser.pyx:37, which in turn calls find_deserializer() for each column type. These functions perform class name lookups and issubclass() chains, then create fresh Deserializer objects -- all redundant work when the same column types appear repeatedly (which is always the case for prepared statements).
Benchmark results
Benchmarks compare the original code (Before) against the new cached implementation (After).
find_deserializer (single type lookup) -- 6.6x faster:
make_deserializers (5 types) -- 29x faster:
make_deserializers (10 types) -- 40x faster:
Design notes
Tests
All existing unit tests pass. New tests cover:
Pre-review checklist
./docs/source/.Fixes:annotations to PR description.