Skip to content

Cache type service readers in page readers - #955

Merged
jt2594838 merged 1 commit into
developfrom
add_type_service_cache
Sep 11, 2026
Merged

Cache type service readers in page readers#955
jt2594838 merged 1 commit into
developfrom
add_type_service_cache

Conversation

@jt2594838

@jt2594838 jt2594838 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Cache type-specific page value readers and the bound deletion predicate in PageReader and ValuePageReader.

Repeated reads reuse the type dispatch result, and each page reader binds its deletion predicate once. Service readers are initialized lazily when the corresponding API is first used; the predicate is initialized during construction.

Performance

Local JMH 1.37 measurements on Windows 11, Intel Core i9-12900, JBR/OpenJDK 21.0.8. JVM arguments: -Xms512m -Xmx512m -XX:+UseG1GC -XX:ActiveProcessorCount=2. Each scenario uses one thread and five independent JVM runs, with four 500 ms warmup iterations and six 500 ms measurement iterations per run, using -prof gc. Values below are medians of the five per-run means. Lower ns/op and B/op are better; time reduction is (baseline - cached) / baseline.

Measurement scope: these results were collected for the earlier ValuePageReader snapshot with cached valueReader and deletePredicate, before adding the batch/column-builder caches and the PageReader changes in commit 4fabada0. They characterize the nextValue optimization, not the exact final PR revision or its other APIs. The measured snapshot is identified by source SHA-256 b0694ce4ef3e75a4bf1a3a0d5ad6e9c0f7a5ed43441b35b78c85337eb4cc35b0.

scan reuses four PLAIN-encoded pages of 1,024 rows, calling nextValue in row/column order and consuming results with a JMH Blackhole. Its 4,096 calls include bitmap-null and deleted rows; buffer rewinds are included and amortized across those calls. MIXED uses INT32, INT64, DOUBLE, and BOOLEAN columns. MIXED_NULL_DELETE additionally has 25% bitmap-null rows per column and a deletion interval covering the last quarter of timestamps. This measures in-memory decoding, without file I/O or decompression.

Compared with the implementation before caching

Baseline: e00a1e43969efb9f3a9725fbd8e6d75595fd7e5d, after the type-service refactor. These variants use the same dependency classes.

Scenario Baseline ns/op Reader only ns/op Reader + predicate ns/op Time reduction vs baseline B/op: baseline → cached
INT64 4.577 5.034 4.793 -4.7% 24 → 24
INT32 6.903 8.177 6.850 +0.8% 24 → 24
DOUBLE 4.527 5.767 4.831 -6.7% 24 → 24
MIXED 8.993 8.060 6.853 +23.8% 38 → 22
MIXED_NULL_DELETE 8.811 7.311 6.089 +30.9% 24.375 → 12.375

Reader-only allocation matches the baseline in every scenario. Caching the predicate additionally reduces allocation in the mixed scenarios. The single-type measurements do not show a consistent speedup.

Compared with the version before the type-service refactor

Baseline: 9be35b9bea2c95dd6f0201aeb8f04255775f2a9f, the parent of 02b503b92416a04fcfb360f446c08ef3e0978ed2. Its complete java/common and java/tsfile modules were built and used with the same harness and JMH settings.

Scenario Pre-refactor ns/op Reader + predicate ns/op Time reduction B/op: pre-refactor → cached
INT64 5.797 4.793 +17.3% 24 → 24
INT32 8.151 6.850 +16.0% 24 → 24
DOUBLE 5.736 4.831 +15.8% 24 → 24
MIXED 6.322 6.853 -8.4% 22 → 22
MIXED_NULL_DELETE 6.433 6.089 +5.4% 12.375 → 12.375

The pre-refactor runs were collected later, rather than interleaved with the cached variants, and include changes in dependency classes. These percentages are descriptive comparisons, not an isolated causal estimate of the refactor. Run-to-run variability is substantial: MIXED ranges from 5.639–8.969 ns/op before the refactor and 6.674–7.976 ns/op with caching. The overlapping ranges do not establish a consistent 8.4% regression.

New-page first value

This separate INT64 benchmark includes buffer wrapping, decoder/reader construction, bitmap parsing, and the first nextValue call. It measures a new reader in a warmed JVM, not cold JVM startup.

Version Median ns/op Run range ns/op B/op
Pre-refactor 37.040 32.682–41.444 344
Before caching 43.743 34.128–51.905 344
Reader only 40.986 35.652–44.871 352
Reader + predicate 37.988 32.469–55.177 368

The timing ranges overlap, while allocation increases by 24 B per new reader in the measured cached variant. All four variants passed value-by-value checks for all five scenarios, two complete page replays, and the first value of a new page. These microbenchmarks do not establish end-to-end query throughput gains.

Validation

  • mvn.cmd -P with-java -pl java/tsfile -am test '-Dtest=PageReaderTest,TsFileLastReaderTest' '-Dsurefire.failIfNoSpecifiedTests=false'
  • 19 tests reported: 18 passed, 1 skipped, 0 failures, 0 errors
  • Checkstyle passed
  • Spotless passed
  • git diff --check passed


// Reuse type-specific readers and the deletion predicate across repeated page reads.
private PageDataValueReader batchDataValueReader;
private PageDataBlockValueReader blockValueReader;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache these type-specific readers for the lifetime of the page reader. The data type is immutable, so repeated page scans can reuse the dispatch result and avoid rebuilding service lambdas.

long timestamp = timeDecoder.readLong(timeBuffer);
valueReader.read(
valueDecoder, valueBuffer, recordFilter, pageData, timestamp, allSatisfy, isDeleted);
batchDataValueReader.read(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the deletion predicate bound once per reader so every decoded row uses the same instance without allocating a method reference for each page read.

private final Decoder valueDecoder;

// Reuse the type-specific reader across per-row nextValue calls.
private PageDataTsPrimitiveValueReader valueReader;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuse the primitive, batch, and column-builder readers across the value-page APIs. These readers are stateless with respect to the page and only depend on the immutable data type, so lazy initialization preserves construction behavior while removing repeated type dispatch.

TypeServices.READ_PAGE_VALUE_TO_TSPRIMITIVETYPE_SERVICE.call(
Type.fromTsDataType(dataType));
}
return valueReader.read(valueDecoder, valueBuffer, timestamp, deletePredicate);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the cached primitive reader and deletion predicate in the hot per-row path. This removes repeated type dispatch and method-reference creation while preserving decoder ordering and deletion semantics.

long timestamp = timeDecoder.readLong(timeBuffer);
PageDataReadStatus status =
valueReader.read(
blockValueReader.read(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reuse the cached TsBlock reader for repeated block page reads. Lazy initialization keeps the existing construction path and removes repeated type service lookup from the row loop.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.48649% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.94%. Comparing base (e00a1e4) to head (4fabada).
⚠️ Report is 4 commits behind head on develop.

Files with missing lines Patch % Lines
...pache/tsfile/read/reader/page/ValuePageReader.java 82.14% 5 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #955      +/-   ##
===========================================
- Coverage    64.03%   63.94%   -0.10%     
===========================================
  Files          757      758       +1     
  Lines        52993    53249     +256     
  Branches      8342     8446     +104     
===========================================
+ Hits         33936    34051     +115     
- Misses       17421    17535     +114     
- Partials      1636     1663      +27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jt2594838
jt2594838 merged commit 5e207cd into develop Sep 11, 2026
18 checks passed
@jt2594838
jt2594838 deleted the add_type_service_cache branch September 11, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants