Repository navigation
Store sealed segment metadata in a mapped sidecar - #19998
Conversation
xiangfu0
left a comment
There was a problem hiding this comment.
Review verdict: no blocking issues — looks good to merge. (Posting as a COMMENT review rather than an explicit approval because this session's token is the PR author, and GitHub does not allow self-approval.)
Really nice piece of work — I traced the format versioning, the mmap lifecycle, and the backward-compat behavior across broker/server/controller, and it holds together well. A few highlights from the review:
- Format versioning is sound. The sidecar is a purely local, non-shipped derived cache: magic +
VERSION+ length + source fingerprints + payload CRC32C are all checked, and any mismatch (including an old/newer version) rebuilds from the canonicalmetadata.propertiesrather than mis-parsing. BumpingVERSIONrebuilds rather than migrates, and the tests cover the v2→rebuild case. - FieldSpec JSON round-trip is correct, including the
TimeFieldSpecdiscriminator omission (top-level and nested underComplexFieldSpec), andComplexFieldSpecis correctly excluded from interning. - mmap lifecycle is safe. The metadata mapping is released exactly once on every unload path and is protected by the same segment reference-count invariant that already guards the column-index mmaps —
destroy()only runs at refcount 0, so no query can read a freed mapping. The 3-attemptopen()loop has no buffer leak. - Untrusted-parse path is well-bounded — no OOB reads, negative array sizes, or infinite loops; float/double min-max round-trip through raw bits is exact (NaN/-0.0 preserved).
I only found a handful of minor, non-blocking items (inline). None of them block merge. Approving — thanks for the thorough tests and the clear header documentation.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #19998 +/- ##
============================================
+ Coverage 70.14% 70.21% +0.06%
Complexity 1522 1522
============================================
Files 3560 3564 +4
Lines 236798 237875 +1077
Branches 38054 38247 +193
============================================
+ Hits 166098 167013 +915
- Misses 57996 58078 +82
- Partials 12704 12784 +80
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:
|
xiangfu0
left a comment
There was a problem hiding this comment.
Verdict: LGTM / would approve (posting as a comment because GitHub blocks the authenticated account from formally approving its own PR in this environment).
Reviewed the updated head (eae234d). I focused on the areas most at risk: the new binary sidecar format, backward/forward compatibility of the SPI changes, mixed-version behavior, the mmap lifecycle, and the lock-free caches.
The design holds up well:
- Format: header (64B) and record (180B) offset layouts are self-consistent between
build()andvalidate()/open(), with no field overlaps. Fixed-width stat reads (int/float bit tricks, min/max tags) decode correctly, andvalidate()runs before any view is published so the rawreadString/copyTopaths are always bounds-guarded. The payload CRC plus the exact structural arithmetic (table + count*RECORD_SIZE == blobStart) make a header bit-flip effectively non-exploitable. - Compatibility: the sidecar is a per-server local derived cache;
metadata.propertiesstays canonical, and a wrong/newerVERSIONor stale fingerprint triggers a local rebuild rather than a migration. So mixed-version servers can't corrupt each other. The newColumnMetadatamethods aredefault-implemented, so plugin impls stay source/binary compatible. - mmap lifecycle: unmap happens in
SegmentDirectory.close()→SegmentMetadataImpl.close(), reached only viadestroy()after the segment's reference count hits 0. Queries pin the segment through theSegmentDataManagerfor their whole lifetime, so pruner/execution reads of aMappedColumnMetadataview can't overlap the unmap. No use-after-unmap in the serving path. - Concurrency: the
AtomicReferenceArrayCAS memoization and thevolatilelock-free caches inMappedColumnMetadataare correct (idempotent recompute, safe publication).
No correctness, compatibility, or lifecycle defects found. I left a few minor hardening/perf nits inline — none blocking. Approving.
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed the mapped sidecar end to end (encode/decode fidelity, mmap lifecycle, concurrency, perf, back-compat). The design is solid and the round-trip test coverage is strong. One thing I'd like to resolve before merge is the always-on load behavior (inline), plus a couple of minor notes. Nice work on keeping metadata.properties canonical and making the sidecar a rebuildable cache.
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed the current HEAD focusing on the mapped-sidecar format, concurrency/lifecycle, and null/numeric correctness. Really nice work — the format is versioned and self-describing, stale/corrupt sidecars rebuild from canonical metadata.properties, and the min/max round-trip preserves exact numeric fidelity (incl. NaN/-0.0). Lifecycle looks safe: load is single-threaded and the segment is published through the ConcurrentHashMap handoff, and the mapped buffer is unmapped on the same refcount-gated destroy() path that already protects the column data buffers.
One thing worth addressing before merge (MAJOR): the sidecar is written into the live serving segment directory, and the peer-download / segment-upload paths in TablesResource re-tar that directory recursively with no filename filter (createCompressedTarFile at ~L622 and ~L1112, via TarCompressionUtils.addFileToCompressedTar). So segment_metadata.bin gets baked into the deep-store copy and shipped on every peer download. It's not a correctness bug — CRC comes from creation.meta and the receiver re-validates/rebuilds the sidecar against its own metadata.properties, so it self-heals — but it violates the "purely local cache" invariant the class javadoc states and pollutes deep store / wastes transfer bandwidth. Suggest excluding MappedSegmentMetadataStore.FILE_NAME at those two tar sites (and ideally in CrcUtils.collectFiles alongside creation.meta/*.crc for future-proofing). A couple of smaller notes inline. Did not approve since there's a MAJOR item; CI status couldn't be confirmed from here.
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed the current head (643f9fb), focusing on what changed since the earlier reviewed commits (the new createSegmentTransferTarFile exclusion and the segment.metadata.mmap.enabled instance opt-out), plus a fresh correctness/lifecycle/compat pass over the mapped sidecar core.
The design holds up well. The sidecar validation is solid: CRC32C is checked on the heap copy before any offset is trusted, every blob offset/length is bounds-checked with long arithmetic, and matchesMapped re-verifies the mapping byte-for-byte against the validated bytes so those bounds transfer to the mmap. The mmap lifecycle is correct too — it rides the same reference-count-gated teardown as the existing index buffers, close() is idempotent, and every failure path (validation, empty segment, disabled opt-out) falls back to eager metadata. The transfer-tar exclusion correctly keeps the sidecar local while always shipping metadata.properties, so mixed-version peers rebuild fine.
No CRITICAL or MAJOR issues — only the minor notes inline, none blocking. (Posting as a comment rather than an approval since GitHub won't let me approve a PR under this account; from a review standpoint this looks good to merge.)
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed at 6b6ff51. Thorough, well-engineered change — the mmap sidecar lifecycle, backward compatibility, and failure handling are all handled carefully and backed by strong tests. Assessment: approve (posting as a comment since the review tooling is authenticated as the PR author and GitHub blocks self-approval).
What I checked and found sound:
- Lifecycle / mmap cleanup: the metadata mapping follows the same reference-counted destroy model as the existing index buffers.
SegmentLocalFSDirectory.close()closes the mapping in afinallyafter readers retire, andImmutableSegmentImpl.destroy()→_segmentDirectory.close()reaches it.EmptyIndexSegment.destroy()closes the metadata on the no-directory path too. - Compatibility:
metadata.propertiesstays canonical; the sidecar is a disposable, fingerprinted, versioned local cache. Stale / corrupt / wrong-version files rebuild rather than decode as unknown bytes, and every failure path falls back to eager metadata so the segment stays available. - Distributed state: peer-download and deep-store-upload archives exclude the root sidecar and its in-progress temp file (scoped to the segment root, so a similarly-named nested index file is untouched), keeping the cache server-local.
- Concurrency: the TOCTOU window between validate and
mapFileis re-checked against the exact mapped bytes and source fingerprints before publishing; the lazy per-column caches use correct double-checked locking; the load-time overlays are mutated only before the segment is published.
Test coverage is excellent: eager-vs-mapped round-trip equivalence (including toJson, custom map, stats, OPEN_STRUCT, legacy TimeFieldSpec), corruption/version rebuild, source-change invalidation, zero-sized index placeholder handling, mmap buffer-count leak assertions, and the transfer filter.
One forward-looking note left inline on the live-mapping delegation. No blocking issues.
xiangfu0
left a comment
There was a problem hiding this comment.
Automated review — intended decision: APPROVE (submitted as a comment because the only available credentials belong to the PR author, and GitHub blocks self-approval).
Reviewed at HEAD 1d05946a9a584018311fd92fc0433fba2c327e3d across compatibility, concurrency/lifecycle, correctness, and resource handling.
Decision: Approve. The mixed-version and sidecar-isolation design is sound and I found no CRITICAL or MAJOR defects. segment_metadata.bin is a disposable local cache: it is excluded from both live-directory tar paths in TablesResource (peer download + deep-store upload), so it never reaches deep store or another node, and any older/other reader rebuilds it from the canonical metadata.properties (or ignores the unknown file). The versioned header + CRC/fingerprint validation correctly treats a stale/old-version/corrupt sidecar as a cache miss and rebuilds it, and segment.metadata.mmap.enabled cleanly reverts to eager metadata.
On lifecycle: the mapped buffer is closed on every teardown path (ImmutableSegmentImpl.destroy → SegmentLocalFSDirectory.close → SegmentMetadataImpl.close, the empty-segment-with/without-directory paths, and the open/constructor failure paths) with no double-close or leak. Reads of mapped metadata on the query and REST paths are protected by the existing SegmentDataManager reference count, so close() (which unmaps the buffer) only runs after all readers release the segment. The lazy double-checked fields in MappedColumnMetadata are correctly published via volatile, and the multi-loader race in open() is handled (full-byte re-compare + fingerprint recheck before publishing, buffer closed on every non-success path).
A few MINOR / defensive notes are left as inline comments; none block merge.
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed the delta since the last reviewed SHA (d79866b..66339cd). The lazy per-column caching, the mapped-metadata fallback/opt-out, the OPEN_STRUCT logical-column handling in getColumnNames(), and the tar sidecar exclusion all look sound and are well covered. I built and ran the focused suites under JDK 25: TarCompressionUtilsTest, SegmentMetadataImplTest, ImmutableSegmentImplTest, IndexLoadingConfigTest, LoaderTest, MultipleTreesBuilderCloseTest, and SegmentCompressionStatsReaderTest all pass. One blocking item: TablesResourceTest.testDownloadSegments fails at HEAD. A few minor notes inline too.
xiangfu0
left a comment
There was a problem hiding this comment.
LGTM — approving in spirit (GitHub blocks approving one's own PR, so posting as a comment). This is a carefully built and thoroughly tested change: the header/CRC fingerprinting, the TOCTOU re-validation after mapFile, treating a version mismatch as a cache miss rather than decoding an unknown layout, and the resource lifecycle (fallback to eager on any failure, close-after-readers-retire) all look solid. The mapped overlays (_virtualColumnMetadata/_removedColumns) are mutated only during load before publication, so the plain HashMap/HashSet are fine. Only minor/nit notes inline — nothing that should hold up the merge.
Heads up: CI was still in progress at the reviewed SHA (linter green; unit/integration/compat suites running) — worth a final glance before merge.
xiangfu0
left a comment
There was a problem hiding this comment.
Independent re-review at head fed91a9, focused on backward compatibility, the memory-mapped read/write paths, mmap resource lifecycle, and concurrency.
Verdict: no CRITICAL or MAJOR findings — this looks ready to merge. (Posting as a comment rather than a formal approval because this account authored the PR and GitHub won't let an author approve their own PR.)
What I verified:
- Backward / mixed-version compatibility — The sidecar is a disposable local cache: it is excluded from peer-download and deep-store-upload tars (root-level
segment_metadata.binplus the.segment_metadata.*.tmptemp files), and a receiving server rebuilds it from the canonicalmetadata.properties. A version mismatch or fingerprint mismatch is treated as a cache miss and rebuilt, never decoded as an unknown layout. So there is no wire/on-disk format compatibility risk across mixed broker/server/controller versions. - Correctness of the mapped read/write paths — Traced header/record/blob offset arithmetic.
validate()(on a heapbyte[]) establishestable == HEADER_SIZE,count >= 0, andtable + count*RECORD_SIZE == blobStart <= lengthwith long arithmetic, so every record/field read stays in-bounds;validateBlobforces each blob field to be exactly(-1,0)or a valid in-bounds blob, which matchesblob()'s runtime contract, so no OOB reads, negative allocations, or integer overflow are reachable from a validating file.value()'s DECIMAL/STRING/BYTES paths can't NPE becausevalidateValueBlobguarantees a present blob for those tags. - mmap lifecycle —
open()'skeepBufferflag closes the mapping on every non-success exit (including the retrycontinue), so no buffer leaks; the post-mapmatchesMapped()+ dual fingerprint recheck closes the validate→mapFile TOCTOU. Teardown is gated by the segment manager's reference count:destroy()→SegmentDirectory.close()→SegmentMetadataImpl.close()unmaps only after readers retire. Empty-segment and fallback paths close their metadata too. - Concurrency — The lazy caches in
MappedColumnMetadata(_bounds,_indexes, and the_partitionFunction/_partitionsvalue+*Loadedflag pairs) use correct double-checked locking over volatile fields. The per-ordinalMappedColumnMetadatacaching inSegmentMetadataImpluses anAtomicReferenceArraywith CAS. The mutable overlays (_virtualColumnMetadata,_removedColumns) are confined to single-threaded load before publication. - Polymorphism safety — Moving
isMaterializedChild()/getParentColumn()/sparse accessors onto theColumnMetadatainterface means every serving-path caller works against the interface; the only remainingColumnMetadataImplcast (SegmentPreProcessor) runs on eager metadata beforeloadMappedMetadata()swaps in the mapped view.
Ran locally on fed91a9 with JDK 25: SegmentMetadataImplTest (9), IndexLoadingConfigTest (7), ImmutableSegmentImplTest (7), EmptyIndexSegmentTest (4), TarCompressionUtilsTest (11), SegmentCompressionStatsReaderTest (7) — all pass.
One residual, informational only (already discussed on the value() thread): a hand-crafted sidecar with a recomputed CRC could embed a non-numeric DECIMAL (or unknown compression) blob that passes validate() and throws lazily at read time. Out of the stated threat model (local, integrity-checked, rebuilt cache), so not worth changing.
xiangfu0
left a comment
There was a problem hiding this comment.
Independent re-review at head fed91a9, focused on backward compatibility, the memory-mapped read/write paths, mmap resource lifecycle, and concurrency.
Verdict: no CRITICAL or MAJOR findings — this looks ready to merge. (Posting as a comment rather than a formal approval because this account authored the PR and GitHub won't let an author approve their own PR.)
What I verified:
- Backward / mixed-version compatibility — The sidecar is a disposable local cache: it is excluded from peer-download and deep-store-upload tars (root-level
segment_metadata.binplus the.segment_metadata.*.tmptemp files), and a receiving server rebuilds it from the canonicalmetadata.properties. A version mismatch or fingerprint mismatch is treated as a cache miss and rebuilt, never decoded as an unknown layout. So there is no wire/on-disk format compatibility risk across mixed broker/server/controller versions. - Correctness of the mapped read/write paths — Traced header/record/blob offset arithmetic.
validate()(on a heapbyte[]) establishestable == HEADER_SIZE,count >= 0, andtable + count*RECORD_SIZE == blobStart <= lengthwith long arithmetic, so every record/field read stays in-bounds;validateBlobforces each blob field to be exactly(-1,0)or a valid in-bounds blob, which matchesblob()'s runtime contract, so no OOB reads, negative allocations, or integer overflow are reachable from a validating file.value()'s DECIMAL/STRING/BYTES paths can't NPE becausevalidateValueBlobguarantees a present blob for those tags. - mmap lifecycle —
open()'skeepBufferflag closes the mapping on every non-success exit (including the retrycontinue), so no buffer leaks; the post-mapmatchesMapped()+ dual fingerprint recheck closes the validate→mapFile TOCTOU. Teardown is gated by the segment manager's reference count:destroy()→SegmentDirectory.close()→SegmentMetadataImpl.close()unmaps only after readers retire. Empty-segment and fallback paths close their metadata too. - Concurrency — The lazy caches in
MappedColumnMetadata(_bounds,_indexes, and the_partitionFunction/_partitionsvalue+*Loadedflag pairs) use correct double-checked locking over volatile fields. The per-ordinalMappedColumnMetadatacaching inSegmentMetadataImpluses anAtomicReferenceArraywith CAS. The mutable overlays (_virtualColumnMetadata,_removedColumns) are confined to single-threaded load before publication. - Polymorphism safety — Moving
isMaterializedChild()/getParentColumn()/sparse accessors onto theColumnMetadatainterface means every serving-path caller works against the interface; the only remainingColumnMetadataImplcast (SegmentPreProcessor) runs on eager metadata beforeloadMappedMetadata()swaps in the mapped view.
Ran locally on fed91a9 with JDK 25: SegmentMetadataImplTest (9), IndexLoadingConfigTest (7), ImmutableSegmentImplTest (7), EmptyIndexSegmentTest (4), TarCompressionUtilsTest (11), SegmentCompressionStatsReaderTest (7) — all pass.
One residual, informational only (already discussed on the value() thread): a hand-crafted sidecar with a recomputed CRC could embed a non-numeric DECIMAL (or unknown compression) blob that passes validate() and throws lazily at read time. Out of the stated threat model (local, integrity-checked, rebuilt cache), so not worth changing.
xiangfu0
left a comment
There was a problem hiding this comment.
Took a fresh pass over the current head, focusing on the on-disk format, mixed-version read/write, mmap lifecycle, and the upsert/dedup concurrency. The compat story looks solid: the v4 version bump plus rebuild-on-mismatch means old sidecars are never misread, the sidecar is a disposable local cache excluded from peer-download and deep-store transfers, and the mapping is unmapped through the segment read-lock on destroy so readers can't touch freed buffers. Nice test coverage on the round-trip, checksum-valid-but-unparseable, legacy TIME specs, and v3->v4 rebuild paths. A couple of non-blocking notes inline; nothing I'd call a blocker. (Note: I'm posting as the PR author's token, so I can't formally approve my own PR here.)
xiangfu0
left a comment
There was a problem hiding this comment.
Reviewed the current HEAD fresh, focusing on on-disk format/serialization safety, mixed-version/compat, concurrency, and the use-after-close lifecycle of the mmap'd metadata. (Posting as a comment review since GitHub won't let the token approve its own PR — but this is an approve-equivalent: no blocking findings.)
The design holds up well. The sidecar is a disposable, server-local cache (correctly excluded from the peer-download/deep-store tar in createSegmentTransferTarFile, and a version or fingerprint mismatch just triggers a local rebuild rather than a cross-version decode), so there's no wire/format compat exposure. On the format itself, every blob reference reachable at serve time is bounds-validated before any mmap copyTo/getInt runs, the full payload is covered by a CRC32C (with the CRC field zeroed), and matchesMapped() + the re-checked source fingerprints close the TOCTOU window between validate and mapFile. The new ColumnMetadata default methods and @Nullable getColumnMetadataFor keep existing implementers binary-compatible. I ran SegmentMetadataImplTest, ImmutableSegmentImplTest, LoaderTest, BasePartitionUpsertMetadataManagerTest, and ConcurrentMapPartitionDedupMetadataManagerWithTTLTest on JDK 25 — all green.
No blocking issues. Two small non-blocking notes inline. Nice work, and thanks for the thorough PR writeup and benchmark context.
xiangfu0
left a comment
There was a problem hiding this comment.
Fresh pass at 6004060, focused on eager-vs-mapped fidelity and on what the sidecar's cache key covers. One correctness issue: BIG_DECIMAL default null values change through the FieldSpec JSON round trip (reproduced with jshell). Plus three smaller points, all inline.
xiangfu0
left a comment
There was a problem hiding this comment.
Summary
Reviewed the current head (221d8cb) of "Store sealed segment metadata in a mapped sidecar" — the full diff across pinot-segment-spi, pinot-segment-local, pinot-common, and pinot-server (37 files, +2331/-117).
Overall this is a careful, well-tested change and I didn't find any correctness or compatibility blockers. A few things I specifically checked and liked:
- Mixed-version compatibility: the sidecar is a purely local, versioned artifact.
metadata.propertiesstays canonical, old versions are rebuilt rather than migrated (VERSIONbump invalidates), andcreateSegmentTransferTarFileexcludes the sidecar from server-to-server tarballs so a receiving node always rebuilds from canonical metadata. No sidecar ever crosses a node boundary. - Resource/mmap lifecycle: the mapped buffer is closed on
destroy()/directory close, and reads that could race destruction (the upsert TTL watermark path) are now guarded by the segment read lock;getWatermarkToPersistforces the reload path when it can't acquire.getDataCrc/getCrcused inrecordSnapshotPassread heap fields, so they stay safe after close. - Format internals: header offsets, fixed 180-byte records, blob refs, and the CRC-over-everything-but-its-own-slot checksum are all internally consistent between
build/validate/open, andopenre-verifies the mapped bytes against the heap copy to handle an atomic replace mid-open. - Concurrency:
MappedColumnMetadatalazy caches use volatile + synchronized double-checked init; the per-columnAtomicReferenceArrayCAS is correct. Overlays are load-time only. - Feature is opt-in and off by default (
segment.metadata.mmap.enabled, instance-level), and the tests cover round-trip fidelity, corrupt/stale rebuild, index-service version changes, decimal precision, and buffer-leak counts.
Only one minor documentation nit (inline). From my side this is good to merge once that's addressed; flagging as a comment rather than an approval because I'm the PR author and GitHub won't let me self-approve.
Finding counts: CRITICAL 0, MAJOR 0, MINOR 1 (doc nit).
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Re-reviewed ce7d6c4, including all six files changed since fcab777 and their serving/reload callers. One new P2 correctness issue remains in the detached metadata fix; details are inline.
Verified fixes: TIMESTAMP defaults preserve epoch values, including nested fields and timezone/DST coverage; column names are cached rather than rebuilt per partial upsert; the protected upsert Set field descriptor is restored; index-size caches use packed primitive arrays; the sidecar writer reuses parsed index entries; and the redundant boolean constructors are removed.
Remaining performance evidence is still incomplete: concurrent short-query behavior under the per-segment scalar-read monitor, complete cold/reused serving startup, and retained heap after warming column-name/bounds/index-size caches. These are unmeasured risks, not demonstrated latency regressions. The PR now documents them and keeps serving opt-in.
The broader SegmentMetadataImpl design concern remains: it combines eager, mapped, and consuming representations with mode-specific accessors and overlays. The check/rebuild/open flow already exists in MappedSegmentMetadataStore; isolating the backing strategy would make that flow and its ownership contracts easier to maintain. This does not make all integration changes out of scope. Version 6 is explained by invalidating development caches, including v5's timestamp encoding; it is not itself a compatibility defect.
Validation: all 13 distinct hosted checks are green for this head. This rereview inspected the changed tests and traced the reload interleaving; no repository tests/builds were run and no source changes were made.
Keep metadata.properties canonical and generate a validated local v1 binary sidecar for opt-in serving loads. Read column statistics from the mapped or direct buffer, preserve eager preprocessing and fallback, and keep the sidecar out of segment transfers. Include focused segment metadata and serving lifecycle coverage.
fa562a6 to
578a04a
Compare
xiangfu0
left a comment
There was a problem hiding this comment.
Risk: HIGH — persisted/serialized on-disk metadata format (segment_metadata.bin) plus memory-mapped buffer ownership/lifecycle and concurrency on segment metadata reads. Each is independently a high-risk characteristic, so this needs a human owner's sign-off regardless of finding severity.
Reviewed SHA: 578a04a — full merge diff vs merge-base 2877723 (39 files, +2677/−125). The branch was squashed to a single commit since my last pass (fcab777); I re-reviewed the complete current diff, not just the delta. Net-new surface since fcab777 is 7 files, all of which strengthen the design.
What changed since the last head, and it's a solid direction:
- Detached metadata snapshots (the main hardening).
HelixInstanceDataManager.getSegmentMetadata()/getAllSegmentsMetadata()now returnSegmentMetadataImpl.detachedCopy()for mapped segments — an eager, heap-backed copy built viaColumnMetadataImpl.detachedCopyOf()while the segment is still referenced, beforereleaseSegment(). The returned copy holds no reference to the mmap store, so callers that keep it past the ref-count window are safe. This closes the "caller caches metadata beyond the reference window" fragility I flagged earlier. Covered bytestDetachedMappedMetadataPreservesServingColumnsAfterClose(verifies virtual/removed columns, index sizes, ZK timestamps survive, andisMapped()==false). - API-compat fix for subclasses. The protected
_updatedSegmentsSinceLastSnapshotfield is restored to its originalSet<ImmutableSegmentImpl>type (the prior head had changed it to aConcurrentMap, which would have broken extensions). It's now akeySet(TTL_WATERMARK_NOT_SET)view over a private value-carrying map, so.add()inserts the correct marker and the watermark semantics are preserved. - Index sizes sourced from the eager
ColumnMetadataImpl(getNumIndexes/getIndexType/getIndexSize) instead of re-parsingindex_map. I verified this produces identical on-disk content: the eager path only populates index sizes for v3 + totalDocs>0, and usesEmptyColumnMetadata(0 indexes) otherwise — matching the oldreadIndexSizesspecial-case exactly. On-disk blob is still string-keyed (type id + long); the numeric id is resolved at read time, so nothing version-fragile is persisted. VERSIONreset 5→1 for the first merged format — safe because the sidecar is a disposable local cache that rebuilds on any version mismatch (feature is opt-in and unreleased).- TIMESTAMP
defaultNullValuenow encoded as text alongside numerics, preventing a precision/format drift for timestamp defaults (covered bytestMappedTimestampDefaultNullValuesPreserveInstantAcrossTimeZones).
Validation: All 14 hosted checks are green at this head — Unit Test Set 1/2, Integration Test Set 1/2, Compatibility Regression (master + release-1.5.0), MSQE compatibility, Quickstart, Linter, Binary-compat. (At my prior head these were still in progress; they have now completed successfully.)
Decision: no agent approval. I found no CRITICAL or MAJOR correctness, compatibility, or concurrency defect at this SHA, and CI is complete and green. But this is a HIGH-risk change (on-disk format + mmap lifecycle + concurrency), so per policy it requires a human maintainer's review rather than an automated approval — and the reviewing token is the PR author's account, which cannot self-approve in any case. Requesting a human owner review before merge. Minor, non-blocking note inline.
|
This closes the last order the model found, where a segment destroyed before the watermark cap read its max made the round persist no watermark. Nothing new turned up. I replayed the model's 52 known bad orders and none changed. An hour of search over 8 table shapes found nothing that breaks only with this change, and neither did a second search started next to a marked segment that gets destroyed. Each segment's comparison max is now cached before any thread can mark the segment, and The model covers one replica and one partition, and not the mapped metadata sidecar itself. |
xiangfu0
left a comment
There was a problem hiding this comment.
Focused deep-dive: design + preload behavior (follow-up to my review at this same SHA, 578a04a; this pass traced the upsert preload flow end to end against the pre-PR base).
What I verified:
- Preload invariant holds on every path.
doPreloadSegmentnow reads and caches the comparison-column max before the empty-snapshot and out-of-TTL early returns, andpreloadSegmenttracks the segment afterwards — so by the timetrackSegmentForSnapshot/markSegmentUpdatedruns, the weak cache is always populated. Preload uses the same servingIndexLoadingConfig, so preloaded segments carry mapped metadata and their maxima are cached while the mapping is open. - The watermark-capping linchpin is intact. Segments whose snapshot persist fails (lock contention, replaced mid-pass, exception) stay in the updated map and keep capping the persisted watermark via their cached maxima — readable even after the segment is destroyed and its mapping closed.
retainAllonly drops untracked segments, whose stale snapshots are gone or superseded by a tracked replacement that re-caps. The stale-snapshot-skip-on-restart protection survives the rework. - Skip-path semantics preserved: an out-of-TTL preloaded segment uses its snapshot as-is, gets tracked, caps the watermark until its next fresh snapshot round — same as base, now with the destroyed-segment read hazard removed.
- Dedup doesn't need the same treatment:
BasePartitionDedupMetadataManageronly reads segment metadata in lifecycle-ordered add/remove ops while the segment is alive; it has no background pass over tracked segments, so the upsert-only caching is the right scope. - Serving-path cost model is as designed: preprocessing still requires the eager parse, so the win is retained heap, not load time, on the serving path — consistent with the PR's stated motivation. Two minor notes inline, one of them an actionable first-boot cost saving.
No new CRITICAL/MAJOR findings. Risk classification unchanged (HIGH: persisted format + mmap lifecycle + concurrency) — human maintainer review still required; no agent approval (reviewing token is also the author account).
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Re-reviewed 578a04a. No new correctness or compatibility defect found in this iteration.
The previous detached-copy race is fixed: the implementation copies the acquired segment's in-memory fields and materializes its mapped columns without reopening the canonical directory. I checked the segment and column fields, including bounds, partitioning, OPEN_STRUCT metadata, compression/index sizes, CRCs, timestamps, and serving overlays. The regression test now moves the directory before detachment and reads the detached metadata after closing its owner.
The initial format is now version 1, with an unsupported-version rebuild test. The earlier timestamp encoding, column-name allocation, protected Set descriptor, compact index-size cache, and parser-reuse fixes remain intact. One minor exception-signature cleanup is inline.
The existing design and performance threads remain open. SegmentMetadataImpl still combines eager, mapped, and consuming representations; separating its backing strategy would simplify ownership and mode-specific accessors. Most integration changes are related to the heap/lifetime goal, so I do not classify them as unrelated scope. Concurrent short-query contention, complete cold/reused serving startup, and heap retained after warming the lazy caches remain unmeasured; the PR explicitly documents these limits and keeps serving opt-in. These are outstanding validation/design concerns, not newly demonstrated regressions.
Validation: compared all four files changed since ce7d6c4 and rechecked relevant format/serving/lifecycle paths. All 39 PR file snapshots were verified against their diff blob hashes, and all 13 distinct hosted checks are green for this head. No local builds/tests or repository source changes were made during this rereview.
xiangfu0
left a comment
There was a problem hiding this comment.
Re-review at new head 249e39d (one commit on top of my previously reviewed 578a04a: "Remove obsolete exceptions from detached metadata copy").
Delta: 2 files, +2/−9 — pure dead-code removal. detachedCopy() never throws the declared IOException/ConfigurationException (its body is field copies plus unchecked-wrapping getters), so dropping the throws clause and the now-unreachable catch in HelixInstanceDataManager is correct; javac itself forces the catch removal once the clause is gone. Only two callers exist (both in this PR), so no API impact. No behavior change.
Validation: all 13 checks are pending at this head (just pushed). The delta is signature-only on top of a head that was fully green (14/14), so no new runtime risk is introduced — but the gate still needs these runs to complete.
Risk: HIGH (unchanged — persisted on-disk format + mmap lifecycle + concurrency across the full PR). No CRITICAL/MAJOR findings at this head. Human maintainer review still required; no agent approval (reviewing token is also the author account). My two earlier non-blocking inline notes (preload-time max read visibility, double parse in build()) remain open as optional follow-ups.
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Consolidated review, updated for 249e39d.
The previously reported correctness and compatibility issues are addressed. I found no remaining confirmed correctness blocker in the reviewed implementation.
Verified fixes:
- TIMESTAMP defaults preserve their epoch values, including nested fields and DST/timezone cases.
- Column names are cached instead of rebuilding a tree for every partial upsert.
- The protected upsert field retains its original Set binary descriptor.
- Metadata-returning APIs detach their results before releasing the segment; detachment now copies the acquired state instead of reopening a directory that reload can rename or replace.
- Index-size caches use compact primitive arrays, and sidecar generation reuses parsed index entries.
- The initial format starts at version 1, and redundant boolean constructors are removed.
- The latest commit removes the obsolete checked exceptions from detachedCopy() and the unnecessary caller catch block.
Remaining concerns:
-
Load-time efficiency. Rebuilding constructs another eager metadata instance and separately reparses the properties even though the serving loader already has parsed metadata. Please reuse that input. A valid-cache open still fingerprints canonical inputs, reads and validates the complete binary into a heap array, compares the opened buffer, and fingerprints inputs again. Normally there are zero or one rebuilds, but retries permit three per open, and independent concurrent callers can duplicate generation. Consider coordinating generation for the same directory. Preserve source-change and concurrent-replacement protection when simplifying validation. These are concrete additional operations; their end-to-end cost remains unmeasured.
-
Design clarity. The check/rebuild/open lifecycle is reasonable and already centralized in MappedSegmentMetadataStore. SegmentMetadataImpl still combines eager, mapped, and consuming representations with mode-specific getters, overlays, and ownership rules. Separating mapped backing from legacy parsing would simplify maintenance. The integration changes serve the heap/lifetime goal, so I would not classify all of them as unrelated scope.
-
Performance evidence. Keep serving opt-in until representative cold/reused serving startup, concurrent short-query performance under the per-segment scalar-read monitor, and retained heap after warming column-name, bounds, and index-size caches have been measured. Existing retained-heap probes do not establish these serving costs. These remain validation gaps, not demonstrated latency regressions.
-
Minor file hygiene. The writer cleans its own temporary file in finally, but a hard crash can leave .segment_metadata.*.tmp files indefinitely, including through copy-based reloads. Please add safe abandoned-temp cleanup without deleting another active writer's file.
Validation: the substantive review covered 578a04a; I additionally verified the two-file exception-cleanup delta to 249e39d. At publication, 12 hosted checks have passed and Unit Test Set 2 is queued for the latest head. All 13 distinct checks passed on the preceding head. No local builds/tests or repository source changes were made during this review.
|
Thanks for the consolidated review. I pushed 92e17aa: sidecar generation now uses one parsed This removes one rebuild parse, but does not yet reuse the loader's existing eager object: that object has no parse-time source fingerprint, so it could be stale relative to the fingerprint taken during rebuild. Safe cleanup of orphan temp files in the live source directory still needs writer ownership or locking; deleting by name or age could remove another active writer's file. I am leaving those points, the representation discussion, and the serving-performance measurements open. Hosted CI has restarted on the new head. |
xiangfu0
left a comment
There was a problem hiding this comment.
Re-review at new head 92e17aa (one commit on top of 249e39d: "Reduce mapped metadata rebuild parsing and skip temp copies"; 4 files, +44/−7).
Delta review — both changes verified correct:
- Rebuild parse reduction:
build()now parsesmetadata.propertiesonce and threads the config into a package-private eager constructor, halving the rebuild parse cost flagged in my earlier note. I verified the eager init path is strictly read-only over the shared config, so aliasing withencodeSegmentConfiguration/encodePartitionFunctionis safe — and a single parse actually tightens correctness (one consistent view of the file for both column data and the encoded segment config, matching the fingerprint taken inopen()). - Temp-file copy exclusion: the reload directory copy now filters abandoned
.segment_metadata.*.tmpfiles (root-level only; directories and nested paths pass through, so commons-io filter semantics are safe) while deliberately keeping the validsegment_metadata.binso an unchanged segment reuses its cache after reload. This also means crash-orphaned temp files stop propagating through backup/restore cycles and get cleaned up naturally. Consistent with the tar-transfer path, which strips both (cache stays local). Covered by the newtestCopyExcludesMappedMetadataTemporaryFile.
Validation: all 13 checks pending at this head (just pushed). The delta is small and behavior-preserving on the serving path; prior head lineage was fully green, but the gate still needs these runs to complete.
Risk: HIGH (unchanged for the full PR — persisted on-disk format + mmap lifecycle + concurrency). No CRITICAL/MAJOR findings at this head. Human maintainer review still required; no agent approval (reviewing token is also the author account).
xiangfu0
left a comment
There was a problem hiding this comment.
Re-review at new head 4a3d0cb — a merge of upstream master into the branch ("Merge master to include broker test fixture fix"). For a merge commit the review question changes, so I verified accordingly:
- The merge is provably clean: the merge commit's tree is byte-identical to git's automatic merge of its two parents (
git merge-tree --write-tree 92e17aae c91d0e83=4a3d0cbb^{tree}). No manual conflict resolution, no hand edits beyond the two already-reviewed inputs. - PR content vs the new base is unchanged from what I reviewed at 92e17aa, with two benign deltas:
DimensionTableDataBytesPrimaryKeyTestandDimensionTableDataManagerTestdropped out of the diff because the PR's temp-dir hygiene fix landed upstream independently (PR head and new base are byte-identical there), and the PR diff is now 38 files. - No production-file intersection with master drift. Only two test files were touched on both sides (
TarCompressionUtilsTest,LoaderTest); the interleave is clean and the PR-side additions (testFilteredDirectory, the sidecar-aware v3 directory assertions) are intact at the merge head.
Validation: 2/13 checks green, 11 pending at this head (merge just pushed). The merge brings ~378 files of upstream change, so the full suite needs to complete on the merged tree before this head counts as validated.
Risk: HIGH (unchanged for the full PR — persisted on-disk format + mmap lifecycle + concurrency). No new findings at this head. Human maintainer review still required; no agent approval (reviewing token is also the author account).
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Re-reviewed 4a3d0cb. No new confirmed correctness or compatibility findings.
The latest changes address part of the remaining efficiency and file-hygiene concerns:
- Sidecar generation now parses metadata.properties once and passes the same PropertiesConfiguration to the eager metadata constructor and binary encoder. The constructor and column/star-tree/text metadata parsers do not mutate or retain that configuration, and source-fingerprint checks remain intact. This removes the duplicate parse inside build().
- Reload copies skip top-level .segment_metadata.*.tmp files while retaining segment_metadata.bin, including when copying from the backup directory. The new focused test covers an abandoned temporary file.
- The earlier detached-copy, TIMESTAMP, protected Set descriptor, cached column names, compact index sizes, and exception cleanup remain intact.
Two limits to those improvements: build() still constructs another eager metadata object instead of reusing the serving loader's existing metadata; and excluding temporary files from copies does not sweep existing crash leftovers from the source directory. Those leftovers remain until directory cleanup.
The existing broader concerns remain open: representative full serving startup, concurrent short-query behavior under the per-segment scalar-read monitor, and retained heap after warming caches are still unmeasured. SegmentMetadataImpl still combines eager, mapped, and consuming representations. The new package-private constructor is a narrow, useful reuse improvement, but it does not address that larger design concern. Keep serving opt-in while the documented performance validation remains outstanding.
Validation: reviewed the latest feature changes and their interaction with the merged master changes, verified all 38 PR file snapshots against their diff blob hashes, and checked current CI. All 13 hosted checks passed for this head. No local builds/tests or repository source edits were performed.
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Remaining items on 4a3d0cb:
-
Reuse the loader's already parsed metadata. The duplicate properties parse inside build() is fixed, but generation still constructs another eager SegmentMetadataImpl after the serving loader has parsed the same metadata. Reuse the post-preprocessing metadata/configuration where its identity can be verified against the canonical source, preserving the existing stale-source checks.
-
Complete serving-performance validation. Measure cold and reused-sidecar startup/readiness, peak heap/GC/RSS under realistic load concurrency, concurrent short-query behavior under the per-segment scalar-read monitor, and retained heap after warming column names, bounds, and index-size caches. Keep the feature opt-in until these results are available; current retained-heap probes do not establish these serving costs.
-
Simplify SegmentMetadataImpl's representation and ownership model. Separate mapped backing from eager/consuming metadata to reduce mode-specific accessors, nullable backing fields, and overlay/lifetime logic. The check/rebuild/open flow is already centralized; the remaining concern is the surrounding class design.
-
Safely clean abandoned sidecar temporary files. Reload copies now exclude .segment_metadata.*.tmp files, but crash leftovers remain in the source directory until directory cleanup. Add cleanup at a lifecycle point where another active writer's file cannot be deleted.
These are the remaining efficiency, validation, maintainability, and hygiene items. No confirmed correctness or compatibility blocker remains in the reviewed head.
Capture the final loader parse from canonical metadata and index map bytes, then reuse it only while source fingerprints and the index registry still match. Fall back to the existing canonical rebuild when they change. Validation: SegmentMetadataImplTest and LoaderTest#testLoad; affected-module Spotless, license, and Checkstyle checks.
xiangfu0
left a comment
There was a problem hiding this comment.
Re-review at new head 47e0240 (one commit on top of 4a3d0cb: "Reuse verified eager metadata for mapped sidecar rebuilds"; 8 files, +221/−19).
This completes the rebuild-parse optimization thread from my earlier reviews, and it takes exactly the path I had cautioned needed fingerprint coupling — and does the coupling right. I reviewed it as the highest-risk change of the recent follow-ups, since an unverified metadata reuse could silently publish a sidecar whose content doesn't match its recorded fingerprints.
Verified:
- Staleness hazard closed by construction.
captureEagerSnapshotreads the canonical bytes once, fingerprints those bytes, and parses the eager view from those same bytes — so the parsed object provably corresponds to the recorded fingerprints.build()only reuses the snapshot when its fingerprints equal the onesopen()just computed from the live files AND theIndexServiceinstance is unchanged (registry drift is also re-checked at capture time via_reusable). Anything changed on disk → fresh parse, exactly as before. - Parse parity.
fromInputStream(bytes)and the incumbentfromFile(file)configure identically (default delimiter, DefaultIOFactory, sameFileHandlerload), so a snapshot-parsed view is equivalent to a file-parsed view of the same bytes. The versioned-header variant (getSegmentMetadataFromFile) is not on either path. - Lifecycle: the snapshot is dropped on
reloadMetadata()(correct — preprocessing rewrote the file), consumed and nulled atloadMappedMetadata(), and nulled atclose(). Its metadata doubles as the directory's_segmentMetadata, so no duplicate eager object is retained. ThewithMappedMetadataEnabled()flag is set only on the final serving load after preprocessing; the needPreprocess probe uses the unflagged context; externally-created directories simply skip the optimization. - Tests are well-constructed: the stale-snapshot tests mutate content with same-length replacements so only the CRC differs — precisely exercising the fingerprint check — for both
metadata.propertiesandindex_map, plus a reload-drops-snapshot test.
Net effect: a sidecar rebuild now parses metadata.properties exactly once per segment (down from three parses two commits ago), with correctness preserved.
Validation: 2/13 checks green, 11 pending at this head. Risk: HIGH (unchanged for the full PR). No CRITICAL/MAJOR findings. Human maintainer review still required; no agent approval (reviewing token is also the author account).
Jackie-Jiang
left a comment
There was a problem hiding this comment.
Reviewed 47e0240 for format correctness, compatibility, lifecycle, performance, and maintainability. No confirmed correctness or compatibility blocker remains. The guarded eager snapshot and its cleanup look sound, and the new ColumnMetadata methods have compatible defaults. Approving with one nonblocking loader-path follow-up below.
The reported full-segment synthetic measurements improve the evidence: about 13% lower warmed retained heap, with roughly 5–7% slower cold loads and higher sampled peak heap/RSS at four load workers. Representative server startup, production query behavior, and Linux mapping headroom remain rollout gates while the feature stays opt-in. Separating eager/mapped representations and safely cleaning crash-orphaned temporary files can follow separately.
All current-head CI checks pass. This re-review was based on code inspection and the reported measurements; I did not run local tests.
Reproduce: Enable mapped segment metadata and start a server with an existing segment whose sidecar is missing or stale. The directory load parses eager metadata before mapped sidecar construction. Pass the guarded snapshot through existing-segment startup and directory-reuse reload so the sidecar can reuse that parse.
xiangfu0
left a comment
There was a problem hiding this comment.
Re-review at new head fdafe1c (one commit on top of 47e0240: "Reuse eager metadata for existing server segments"; 3 files, +58/−2).
Delta: extends the snapshot-reuse flag to BaseTableDataManager.initSegmentDirectory, covering the existing-segment startup and reload paths that previously skipped the optimization. I reviewed this against the staleness question from the last round, and the reasoning holds:
- The flag is a performance hint, not a correctness decision. Since the reuse guard lives in
build()(snapshot fingerprints +IndexServiceidentity must matchopen()'s fresh reads of the live files), no placement ofwithMappedMetadataEnabled()can cause a stale snapshot to be reused — the worst case anywhere is a wasted capture. That's what makes extending it to more call sites safe, and the loosened javadoc ("may become the mapped serving view") correctly reflects the guarded contract. - Preprocessing on these paths stays correct: when a reload/preprocess modifies
metadata.properties,reloadMetadata()drops the snapshot first thing; even a hypothetical path that modified the file without calling it would be caught by the fingerprint mismatch. - Plugin loaders are unaffected: a third-party
SegmentDirectoryLoaderthat ignores the new context flag just loses the optimization, not correctness. - The test is the right proof: it drives the real table-manager reload flow (local-copy reuse,
createBackupnever invoked), modifiesmetadata.propertiesbefore reload, asserts the loader observed the flag, and verifies the reloaded mapped view reflects the fresh content — the exact leak a stale snapshot would cause.
Validation: 2/13 checks green, 11 pending at this head (just pushed). Risk: HIGH (unchanged for the full PR — persisted on-disk format + mmap lifecycle + concurrency). No CRITICAL/MAJOR findings. Human maintainer review still required; no agent approval (reviewing token is also the author account).
…pache/pinot#19998) (#1159) ## Summary Documents the new server instance configuration `pinot.server.instance.segment.metadata.mmap.enabled` introduced in [apache/pinot#19998](apache/pinot#19998). When enabled, the server loads per-segment metadata for sealed segments from a local, file-backed `segment_metadata.bin` sidecar instead of retaining it all on heap, substantially reducing heap usage on servers hosting large numbers of sealed segments. `metadata.properties` remains canonical; the sidecar is a disposable local cache that is rebuilt if missing/stale/corrupt, with fallback to eager on-heap metadata on open failure. **Disabled by default.** ## Verification - Config key and default verified against merged code: `IndexLoadingConfig.MAPPED_SEGMENT_METADATA_ENABLED = "segment.metadata.mmap.enabled"`, read relative to `pinot.server.instance` with default `false`. - `git diff --check`: clean. - `scripts/validate-docs.py --summary-only`: PASS (no new broken anchors vs. baseline). ## Scope Single-row addition to `reference/configuration-reference/server.md`. No other changes. Upstream PR: apache/pinot#19998 Co-authored-by: intentlab-ai[bot] <intentlab-ai[bot]@users.noreply.github.com>
|
Documentation for this change has been added: pinot-contrib/pinot-docs#1159 It documents the new server instance config |
…nt; release lock (run-20261031T040114Z-873)
…(run-20261031T040114Z-873)
Motivation
Servers hosting tens of thousands of sealed segments retain per-segment schemas, column maps, and statistics on heap before queries run. In a metadata-only scale probe that repeated 16 sampled metadata/index-map pairs to model 50,000 segments, post-GC heap fell from 2,561.23 MiB with eager metadata to 126.55 MiB with mapped metadata (95.1%). Moving stable per-column statistics into a local, file-backed sidecar leaves more heap available for query execution while
metadata.propertiesremains canonical. The probe did not measure full-server or query behavior.Change
Sealed segments currently retain a
Schema, a sorted column map, and per-column metadata on heap. When enabled, the serving loader opens a versioned, read-onlysegment_metadata.binsidecar after preprocessing. Fixed-width column statistics are read from the sidecar buffer, and column metadata views are created when requested; the normal serving loader requests every physical column while building index containers.metadata.propertiesremains canonical. Missing, stale, or corrupt sidecars rebuild locally; if rebuild or sidecar open fails, serving keeps the eager metadata so the segment remains available. For first-boot generation, the final serving loader captures the canonicalmetadata.propertiesandindex_mapbytes used for eager parsing. It reuses that parsed view only while both source fingerprints and theIndexServiceidentity still match; otherwise it reparses canonical metadata. The transient snapshot is discarded after mapped open, reload, or close. Realtime metadata and callers requesting a full schema or column map retain their existing APIs.On-disk layout and serving
segment_metadata.binis a disposable local cache in initial big-endian format v1, limited to 2 GiB:metadata.propertiesand optionalindex_mapplus registered index IDs, full-file CRC32C (with its own field zeroed), segment-properties offset/length, column count, and table/blob offsets.The fixed record stride and validated absolute offsets make the file directly addressable: it has no Java object pointers to relocate or object graph to deserialize.
PinotDataBuffer.mapFileopens it read-only in mmap mode; HEAP read mode loads the sidecar into a direct buffer without an additional mapping. A sorted name array resolves a requested column to a record ordinal, and the column wrapper reads fixed-width values from the backing record. Variable values are decoded on demand. This is how the sidecar serves per-column statistics during segment use, rather than merely storing a copy that must be expanded into one heap object per column. Names, eligible shared FieldSpecs, segment-level fields, and any requested wrappers still use heap; mmap mode uses file-backed bytes that may use OS page cache/RSS; HEAP mode uses direct memory.The writer derives the sidecar from canonical metadata and replaces it via a temporary file and rename. Normal writes delete their own temporary file in a finally block, and directory copies exclude these files. An orphan from a terminated process can remain in an active segment directory; sweeping there requires writer/cleaner coordination so another active writer file is never deleted. Open validates the version, source fingerprints, CRC, bounds, column order, and typed values; after opening it verifies the buffer bytes and source fingerprints again before publishing the view. A same-version file can be opened by newer code after validation. A different version is treated as a cache miss and rebuilt from
metadata.propertiesand the optionalindex_map, not decoded as an unknown binary layout. Future format or derived-value parser changes require a version bump. Numeric FieldSpec defaults and TIMESTAMP default epochs are encoded as text to preserve their exact values. A checksum-valid sidecar with unreadable FieldSpec JSON is rebuilt once from canonical metadata. The segment directory closes the buffer after readers retire. Complex FieldSpecs stay segment-local because their child map is mutable. The validation path temporarily reads the full sidecar into a heapbyte[], so peak startup allocation is distinct from retained heap.Sidecar loading is opt-in for serving loads with an instance config; unconfigured reader and tool loads stay eager. Set
pinot.server.instance.segment.metadata.mmap.enabled=trueon a server instance to enable it. The default remains eager until representative full-server startup and Linux VMA headroom have been measured. Peer-download and deep-store-upload archives exclude the root sidecar and its in-progress temporary file, so the cache remains local to each server.Disk-size reporting includes this local sidecar for v1/v2 segments, which measure the segment root. Normal v3 segments measure their
v3/subdirectory, so they do not count the root sidecar. The effect on size-based rebalance decisions has not been separately measured.A standalone caller owns and closes its mapping:
Heap benchmark and validation
Three separate JDK 25 JVMs per mode loaded the same 128 synthetic v1 segments with 1,000 INT columns each. With
-Xms2g -Xmx2g, the probe retained all segments, performed three GCs, and measured whole-JVM used heap. The primary before/after comparison uses eager loading at the PR merge-base2877723da384and mapped loading at commit387ec7358907; mapped runs reused valid sidecars (251,241 bytes per segment). A same-commit eager control measured 35.669 MiB, within 0.005 MiB of the merge-base result. Those later fixes were not part of the older measurement; a separate current-head synthetic full-segment probe appears below.These are synthetic post-GC retained-heap results, not full-server measurements. The serving-style probe builds
ImmutableSegmentImpland column containers but opens no real index readers or queries. The current-head full-segment synthetic probe below adds index readers and in-process queries; production server readiness and representative query latency remain unmeasured. The separate metadata-only probe below reports local JVM load peaks, process RSS, and post-load mapped-file footprint.An earlier Unit attempt reported a
PinotSinkIntegrationTestmetadata 404, and the next attempt passed that class. The original cause remains unattributed; a bounded path audit found no concrete coupling to this feature.Review fixes preserve TIMESTAMP default epochs, cache column names and compact index sizes, restore the protected upsert Set field, return detached mapped metadata from the instance APIs, and reuse parsed index sizes when building the sidecar. Earlier local validation covered 57 focused tests across segment metadata, upsert, and server. The final v1 format and detached-copy changes passed all 15
SegmentMetadataImplTestcases and affected Spotless, Checkstyle, and license checks. Commit47e02405cdadds guarded reuse of the final eager parse; 18SegmentMetadataImplTestcases andLoaderTest#testLoadpassed with fresh compilation and the affected Spotless, Checkstyle, and license checks. Hosted CI is tracked on the current head in Checks; maintainer review remains pending. The prior StarTree S3 test run is older-head evidence.Production-metadata-only scale probe
Sixteen completed v3 metadata/index-map pairs from one production table were selected from a small S3-key sample and copied into separate local directories, weighted and interleaved to model 1,000, 10,000, and 50,000 segments. This is not a statistical sample of the table. The fixture contains
metadata.properties,index_map, andcreation.meta; no index data or queries were loaded. Valid sidecars were built before the timed loads. The per-segment column counts of these 16 pairs are not reported; the 1,000-column width above applies only to the synthetic v1 probe. Merge-base eager and benchmarked-commit mapped used the same fixture and JDK 25 settings (-Xms1g -Xmx8g). The 1,000-segment values are medians of three fresh JVMs per mode; 10,000 and 50,000 are one fresh JVM per mode. Benchmarked-commit eager controls at 1,000 and 10,000 were close to merge-base eager.At 50,000, sampled peak heap was 4,662.66 → 711.42 MiB, post-load macOS
vmmapphysical footprint was 5.0 GiB → 875.4 MiB, and metadata-only load time was 528.10 → 78.52 seconds. The 50,000 sidecars used 3.40 GiB on disk. The single 10,000-segment run had higher sampled peak RSS in mapped mode despite lower post-load footprint, so peak RSS needs repeated measurement. These copied samples are not 50,000 independent production segments or full-server startup/query results. The timed probe does not include serving-loader preprocessing or index initialization; sampled peaks may miss shorter spikes.Each segment in mmap mode adds roughly one metadata mapping. HEAP mode uses a direct buffer for the sidecar without an additional mapping. This macOS probe cannot establish Linux VMA headroom: before a 50,000-segment-per-server rollout, verify
/proc/sys/vm/max_map_countand/proc/<pid>/mapson the target server, including existing index/cache mappings and reload overlap.Preprocessing and serving-load performance gaps
Preprocessing uses eager
SegmentMetadataImpland updates canonicalmetadata.propertiesbefore the mapped serving open. Some index handlers callSegmentMetadataUtils.updateMetadataPropertiesfor individual columns; each call rereads and rewrites the entire properties file and eagerly reloads metadata for every column. If 100 column updates each call this helper, they can cause about 100 full-segment reloads plus the preprocessor's phase reloads. This pre-existing cost was excluded from the reported probes. Batching must preserve the forward-index metadata reload before dependent inverted, range, and FST index handlers run. The binary sidecar is opened or rebuilt only after preprocessing, rather than after each property update.On a missing or stale sidecar, generation reuses the final loader's parsed eager metadata/configuration when its captured source fingerprints and index registry still match; otherwise it reparses canonical metadata. It serializes records and blobs in temporary heap arrays, computes a CRC, writes and renames a temporary file, then rereads, validates, and opens it. A reused sidecar still fingerprints the canonical
metadata.propertiesand optionalindex_map, reads and validates the complete binary file into a temporary heap array, compares it byte for byte with the opened buffer, rechecks the source fingerprints, and parses FieldSpecs. MappedgetSchema()andgetColumnMetadataMap()also materialize compatibility objects on each call. Parallel preload workers can overlap these allocations and I/O; concurrent loaders of one segment may redundantly generate the same cache. Sidecar opening occurs after the preprocessing throttle, so that throttle does not limit this overlap.The earlier metadata-only probes did not measure binary generation time or realistic cold/reused serving startup. The current-head synthetic full-segment probe below covers these paths in-process at bounded scale. Before broad enablement, compare actual server startup and readiness time, peak heap, GC, RSS, and I/O under representative preprocessing and preload concurrency; compare production short queries hitting the same segments because scalar metadata reads synchronize on each mapped store; and confirm retained heap after compression-summary/index-size access and min/max pruning warm the lazy caches. The synthetic and metadata-only numbers do not establish production serving costs.
Current-head synthetic full-segment probe
At commit 47e0240 on Temurin JDK 25/macOS, three fresh JVMs per cell loaded 32 copies of one complete v3 segment with 1,000 INT columns and 100 rows. This fixture is TogetherAI-like in width only; it is not a production segment. The in-process loader opened real index readers. Ready measures load submission through all segment opens and one successful min/max-pruned query, not server/pod readiness. Mapped cold had no sidecars; mapped reused used sidecars from a prior run. The OS page cache was not reset. Load-phase heap was sampled every 5 ms, RSS about every 100 ms; brief spikes may be missed. Values below are medians of three independent JVMs.
The retained-heap increase follows actual
SegmentCompressionStatsReader.readcalls with both summary modes, then all column bounds and index sizes, and three explicit GCs. Mapped retained about 4 MiB less heap across 32 copies (about 13% of the eager increase). Cold sidecar creation increased median in-process readiness by about 5–7%; four-worker mapped loads had higher sampled peak heap/RSS. Reused-sidecar readiness ranges overlapped eager.One complete segment also ran filtered
COUNT, filteredSUM, and min/max-prunedCOUNTthrough in-process plans, with 100 queries per worker and three JVMs per mode. Median per-run p95 latency (microseconds) was:This tiny synthetic query mix showed no clear latency regression. The 32 load segments were identical copies; the probe does not cover actual Pinot server/pod readiness, production data/index distribution, representative queries, 50,000-segment load, Linux VMA headroom, or cold OS page cache. Those remain rollout gates; the feature stays opt-in. The harness and raw logs are archived outside the Git repository for follow-up.