Add targetDocsPerChunk cap to VarByteChunkForwardIndexWriterV6 and wire it to table config - #18772
Open
xiangfu0 wants to merge 3 commits into
Open
Add targetDocsPerChunk cap to VarByteChunkForwardIndexWriterV6 and wire it to table config#18772xiangfu0 wants to merge 3 commits into
xiangfu0 wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #18772 +/- ##
============================================
+ Coverage 65.50% 65.51% +0.01%
Complexity 1421 1421
============================================
Files 3430 3430
Lines 218000 218042 +42
Branches 34642 34646 +4
============================================
+ Hits 142799 142854 +55
+ Misses 63641 63627 -14
- Partials 11560 11561 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an optional document-count cap for chunk flushing in VarByteChunkForwardIndexWriterV6, allowing callers to control chunk granularity by both byte budget (chunkSize) and a new targetDocsPerChunk parameter, while keeping the on-disk format/version unchanged.
Changes:
- Introduces a protected
shouldStartNewChunk(int sizeRequired)hook (and a helper for current-chunk doc count) in V4 so subclasses can add extra chunk-flush predicates without duplicatingputBytes(). - Extends V6 with a 4-arg constructor and a docs-per-chunk cap that triggers chunk flushes once the current chunk reaches the configured doc count.
- Adds a V6 unit test intended to validate docs-per-chunk behavior and round-trip correctness.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pinot-segment-local/src/main/java/org/apache/pinot/segment/local/io/writer/impl/VarByteChunkForwardIndexWriterV4.java | Extracts the “start new chunk” predicate into an overridable hook and exposes current-chunk doc count for subclasses. |
| pinot-segment-local/src/main/java/org/apache/pinot/segment/local/io/writer/impl/VarByteChunkForwardIndexWriterV6.java | Adds optional targetDocsPerChunk support via shouldStartNewChunk override and a new constructor overload. |
| pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/creator/VarByteChunkV6Test.java | Adds a test for docs-per-chunk capping and value round-tripping. |
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
7 times, most recently
from
June 30, 2026 12:10
1f3f17f to
e968dab
Compare
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
6 times, most recently
from
July 13, 2026 08:02
2bd7edb to
d57ff85
Compare
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
8 times, most recently
from
July 20, 2026 08:09
65edd08 to
0330e5c
Compare
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
3 times, most recently
from
July 23, 2026 08:57
3a9015d to
b665d4d
Compare
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
3 times, most recently
from
July 26, 2026 08:02
2549365 to
439a018
Compare
V6 currently flushes a chunk only when the next entry would overflow the chunkSize-byte buffer. This adds an optional targetDocsPerChunk parameter so a chunk can also be bounded by document count, letting callers control chunk granularity independently of the byte budget. The cap defaults to -1 (DISABLE_DOCS_PER_CHUNK), preserving the existing purely byte-driven behavior; the on-disk format and version (6) are unchanged. The buffer-overflow flush predicate is extracted into a protected shouldStartNewChunk() hook in V4 (mirroring the existing writeChunkHeader hook) so V6 adds the docs cap without duplicating putBytes(). A unit test verifies each capped chunk holds exactly targetDocsPerChunk docs and round-trips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…counts - Fail fast in the constructor when targetDocsPerChunk is neither -1 (disabled) nor positive, instead of silently treating 0/other negatives as disabled. - Strengthen the test to verify per-chunk document counts (not just the chunk count) by reading each chunk's first docId from the metadata and asserting it equals chunk * targetDocsPerChunk; use 1000 docs with a cap of 30 so the last chunk holds a remainder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rChunk The existing `targetDocsPerChunk` table-config option only derives the chunk byte budget, as `max(min(maxLength * targetDocsPerChunk, targetMaxChunkSize), 4KB)`. Because that derivation keys off the column's *longest* value, a column whose max length far exceeds its average length clamps to `targetMaxChunkSize` and stops tracking the requested doc count entirely. Add `enforceTargetDocsPerChunk` (default false) to promote `targetDocsPerChunk` into a hard per-chunk document cap, plumbed through to the V6 writer's new 4-arg constructor. A boolean over a second numeric option avoids two similarly-named settings that both mean "docs per chunk". - `ForwardIndexConfig`: new field, JSON property, getter, builder method, and a static default + setter matching the existing `targetDocsPerChunk` pattern so it can also be set cluster-wide. Rejects enforcement with a non-positive target. - `ForwardIndexUtils#getDocsPerChunkCap`: resolves to `DISABLE_DOCS_PER_CHUNK` when not enforced or when the target is non-positive. - `ForwardIndexCreatorFactory` and the three creators that instantiate V6 gain overloads; existing signatures delegate with the static default, so behavior is unchanged unless the flag is set. Note the cap is a write-time decision only: chunk size is recorded in the index file header and chunk boundaries in the per-chunk metadata, so readers derive everything from the segment. Retuning the config affects only newly built segments, and mixed-geometry segments remain readable side by side. No format or writer-version change. Also add BenchmarkVarByteV6TargetDocsPerChunk, which sweeps the cap over index size, sequential scan, 1%-selectivity ascending scan, and uniform random point lookups. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xiangfu0
force-pushed
the
varbyte-v6-target-docs-per-chunk
branch
from
July 26, 2026 22:23
439a018 to
8a94f20
Compare
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.
Summary
VarByteChunkForwardIndexWriterV6currently closes a chunk only when the next entry wouldoverflow the
chunkSize-byte buffer. This PR lets a chunk additionally be bounded by documentcount, and wires that control through to table config.
VarByteChunkForwardIndexWriterV6(file, compressionType, chunkSize, targetDocsPerChunk). The existing 3-arg constructor delegates withtargetDocsPerChunk = -1(
DISABLE_DOCS_PER_CHUNK).targetDocsPerChunk > 0, a chunk is flushed once it holds that many docs, even if the bytebuffer isn't full; otherwise behavior is unchanged.
shouldStartNewChunk(int)hookin
VarByteChunkForwardIndexWriterV4(mirroring the existingwriteChunkHeaderhook), so V6 addsthe cap without duplicating
putBytes().enforceTargetDocsPerChunk(defaultfalse) promotes the existingtargetDocsPerChunkfrom a byte-size input into a hard per-chunk document cap.Motivation
The table-config
targetDocsPerChunkdoes not actually control documents per chunk. It onlyderives the chunk byte budget:
Because that keys off the column's longest value, a column whose max length far exceeds its
average length clamps to
targetMaxChunkSizeand stops tracking the requested doc count entirely.Measured on a 1M-doc column of short log lines (avg 106 B) with 0.1% ~50 KB stack traces
(max 50094 B),
targetMaxChunkSize=1MB:targetDocsPerChunkEvery setting yields the same 9,345 docs per chunk — the default overshoots its own nominal target
by more than 9x. With
enforceTargetDocsPerChunk: trueeach setting yields exactly N.Since a point lookup decompresses a whole chunk, docs-per-chunk is the dominant term in random
access latency, so losing control of it is expensive.
Benchmark
BenchmarkVarByteV6TargetDocsPerChunk(added in this PR) sweeps the cap over index size,sequential scan, 1%-selectivity ascending scan, and uniform random point lookups. 1M docs, ZSTD,
chunkSize=1MB, JDK 25, Apple Silicon.LOW_CARD_URL (avg 73 B, 200 distinct values):
JSON_LOG (avg 163 B, structurally repetitive, unique payloads):
SKEWED_LOG (avg 106 B, max 50094 B):
Takeaways:
reduction across all three datasets.
monotonic in chunk size — JSON_LOG bottoms out at N=250–500, and HIGH_CARD_UUID at N=1000
(18.83 MB vs 20.49 MB at N=5000). So there is a real tunable sweet spot rather than
"larger is always smaller".
1.5–3x on full scan.
filtered-query shape) is flat-to-slightly-worse at every cap, since decompression already
amortizes over ~135 values per uncapped chunk. Full scan regresses 8–43% at N=250.
N≈250 looks like a reasonable starting point for point-lookup-heavy columns.
Caveats: random-access error bars are wide at large chunk sizes (JSON_LOG uncapped: 495 ± 399 µs,
Cnt=3) — direction and order of magnitude are solid, exact values approximate. All measurementsare warm-buffer; on a server with mmap'd segments and cold pages, smaller chunks should also cut
read amplification, so the lookup win is likely understated. Latency swept on ZSTD only.
Configuration
{ "fieldConfigList": [ { "name": "myColumn", "encodingType": "RAW", "indexes": { "forward": { "compressionCodec": "ZSTANDARD", "rawIndexWriterVersion": 6, "targetMaxChunkSize": "1MB", "targetDocsPerChunk": 250, "enforceTargetDocsPerChunk": true } } } ] }rawIndexWriterVersion: 6selects this writer.enforceTargetDocsPerChunkhas no effect onearlier versions, which have no cap mechanism.
enforceTargetDocsPerChunk,targetDocsPerChunkkeeps its existing byte-size-derivationmeaning exactly.
ForwardIndexConfig.setDefaultEnforceTargetDocsPerChunkallows setting it cluster-wide, matchingthe existing
targetDocsPerChunkpattern.targetDocsPerChunkis rejected at config construction.Note the chunk buffer is still allocated at
chunkSizeregardless of the cap, so a small capagainst a 1 MB
targetMaxChunkSizeleaves most of the buffer unused. LowertargetMaxChunkSizealongside the cap; nothing auto-derives it.
Constructor examples
Backward compatibility
-1at the writer,enforceTargetDocsPerChunk: falseat the config) reproduce theexact current behavior. All pre-existing constructor and factory signatures are preserved as
delegating overloads.
6) are unchanged.chunk boundaries in the per-chunk metadata, so readers derive all geometry from the segment and
never consult table config. Retuning affects only newly built segments; segments with different
geometries remain readable side by side, so the setting can be tuned over time without a reload.
Testing
EnforceTargetDocsPerChunkTestdrives a skewed column (avg ~18 B, max 50 KB) throughSingleValueVarByteRawIndexCreatorand asserts enforcement yields exactlynumDocs / targetchunks while the unenforced path yields far fewer — pinning the gap the flag closes — plus a value
round-trip on both paths.
VarByteChunkV6Test#testTargetDocsPerChunkCapsChunkasserts each capped chunk holds exactlytargetDocsPerChunkdocs and that values round-trip.ForwardIndexConfigTestcases cover the default, JSON round-trip, builder copy, and rejectionof enforcement with a non-positive target; new
ForwardIndexUtilsTestcases covergetDocsPerChunkCap.pinot-segment-local,plus 20 in
pinot-segment-spi.🤖 Generated with Claude Code