Visitar URL original
Add size-based table retention by xiangfu0 · Pull Request #19770 · apache/pinot · GitHub
Skip to content

Add size-based table retention - #19770

Merged
xiangfu0 merged 13 commits into
masterfrom
xiangfu0/codex2/table-size-retention
Oct 11, 2026
Merged

xiangfu0 merged 13 commits into
masterfrom
xiangfu0/codex2/table-size-retention

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

PR flow

Add size-based retention: process table, enforce size limit, evict oldest segments, update blocked gauge.

flowchart TD
  N0["processTable #40;F2#41;"]:::stModified
  classDef stAdded fill:#dafbe1,stroke:#1a7f37,color:#1f2328,stroke-width:2px
  classDef stModified fill:#fff8c5,stroke:#9a6700,color:#1f2328,stroke-width:2px
  classDef stRemoved fill:#ffebe9,stroke:#cf222e,color:#1f2328,stroke-width:2px
  classDef stUnchanged fill:#f6f8fa,stroke:#656d76,color:#1f2328,stroke-width:1px
Loading

AI-generated · Green: added · Yellow: modified · Red: removed · Gray: existing

Partial evidence: 0 file patches omitted; 1 truncated.

Diff evidence
  • F2: pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/retention/RetentionManager.java — before · after
  • Regenerate PR flow

Tables currently retain segments only by time, so a burst can exceed a storage target while all segments remain within the TTL. Add opt-in segmentsConfig.retentionSize (for example, "100G") to periodically evict the oldest eligible segments. OFFLINE APPEND tables and completed REALTIME segments are supported; limits apply independently to each typed table and can be used alone or alongside time retention. Each typed table expires its own oldest eligible data without looking up the OFFLINE routing time boundary. REALTIME size retention may expire data newer than that boundary, independently of whether it has moved to OFFLINE. Existing hybrid time-retention boundary rules remain unchanged.

Size accounting uses the compressed segment archive size recorded in ZooKeeper metadata for active, live data, counted once regardless of replicas. Live-segment lineage filtering excludes COMPLETED sources and destinations of other entries to avoid counting replacement copies twice. The cap does not measure decompressed server disk usage, memory usage, or total physical deep-store usage. Existing storage quotas retain their upload-admission behavior. A protected size-resolution hook supports logical segment groups.

Cleanup order and eviction rule

  1. Run ordinary time retention, then segment-lineage cleanup. Read IdealState first, then read one planning lineage snapshot and reuse it for live-byte accounting and the lineage boundary. An IN_PROGRESS entry does not skip the whole table.
  2. Sum compressed archive bytes for live completed data. REALTIME includes DONE/UPLOADED segments and excludes IN_PROGRESS/COMMITTING segments from accounting and eviction. If the total is at or below retentionSize, select nothing.
  3. Protect the newest OFFLINE segment according to the ordering below, and the highest-sequence DONE LLC segment for each REALTIME partition group. REALTIME size eviction uses its own completed segments, regardless of the OFFLINE counterpart or hybrid routing time boundary.
  4. A segment's ordering timestamp is the first nonnegative value among end time, creation time, and push time, in that order. Protect segments with all three timestamps negative. Order by this timestamp ascending, then by segment name ascending. The OFFLINE newest-segment floor retains the maximum timestamp/name pair. Oldest eligible data is selected first; segment size does not determine priority.
  5. Select only the eligible old prefix before the first active segment listed in any retained lineage entry, then stop. Never skip over lineage to evict newer segments. Every name in both segmentsFrom and segmentsTo is protected, regardless of segment age, entry state (IN_PROGRESS, COMPLETED, or REVERTED), or the lineage-exclusive deletion setting, until its entry is removed. Replacement copies excluded from size accounting still define stop positions if present in IdealState, even when another protection makes them ineligible.
  6. Skip zero-byte candidates and select whole segments until projected remaining size is at or below the cap, the lineage boundary is reached, or candidates are exhausted. Before submitting the prefix, acquire the local lineage updater lock and re-read the complete lineage entry map. Any change in members, state, timestamp, or entry identity aborts the batch for retry next cycle. This conservative check also retries for timestamp-only changes or entries wholly newer than the lineage boundary, even when the selected prefix is unaffected. This size-retention guard always applies, including when lineage-exclusive deletion is disabled. An unchanged snapshot permits submission through the existing public deletion path, with its additional guard when enabled. Check the deletion response before accepting projected reductions or logging success; rejection is logged as failed eviction. Selection order determines victims and does not guarantee physical deletion order. Whole-segment eviction can leave the table below the cap.

Missing metadata for a live active segment or an unknown size for a live completed segment skips the pass. If an active lineage boundary cannot be placed because its metadata or all ordering timestamps are missing, skip over-cap eviction safely. Lineage destinations not yet in IdealState do not cause a whole-table skip. Protected live completed data still counts toward the cap, so lineage, the newest OFFLINE segment, or realtime recovery data can leave the table above its cap. This is a periodic, best-effort retention limit; physical removal follows the existing deleted-segment retention lifecycle.

Lineage barriers can keep an over-cap table at sizeRetentionBlocked=1 for hours or longer during continuous merge or rollup. With the default lineage manager, COMPLETED sources become eligible for deletion after segmentsConfig.replacedSegmentsRetentionPeriod (4h for APPEND/other ingestion types, 24h for REFRESH); their entry is removed on a subsequent cleanup pass once the sources have left IdealState, releasing the destination barrier. segmentsConfig.lineageEntryCleanupRetentionPeriod (1d by default) controls stale IN_PROGRESS cleanup, rather than adding a one-day delay to COMPLETED entries. REVERTED entries are eligible for cleanup immediately. Cleanup runs before size eviction on the retention-manager schedule (6h by default, or the configured period/cron). These windows determine cleanup eligibility and do not guarantee when the size cap will be met.

The local updater lock coordinates the final check and deletion submission with this controller’s lineage writers. The check and deletion are not a global transaction across controllers; another controller can still publish lineage after the fresh read.

Monitoring

The table-scoped sizeRetentionBlocked gauge (ControllerGauge.SIZE_RETENTION_BLOCKED) reports the latest completed configured pass: 1 when the pass cannot run safely, deletion fails, or the table remains above the cap; 0 for an empty/within-cap table or successful eviction within the cap. Publish the result exactly once when the pass finishes, including exceptions. The gauge is absent when size retention is disabled or unsupported, and is removed when this controller loses table leadership or the table is deleted.

Validation

All 78 targeted tests passed with no failures, errors, or skips across RetentionManagerTest, SegmentLineageCleanupTest, ControllerGaugeTest, ControllerZkHelixUtilsTest, RebalanceCheckerTest, TableConfigBuilderTest, and TableConfigUtilsTest. Coverage includes both source/destination sides in every lineage state, a 400-day-old completed entry, a completed destination whose source is absent or newer, old-prefix/empty-prefix cutoff, shadow copies, undated or missing boundaries, fallback/name ties, realtime recovery protections, and independent REALTIME size eviction without consulting the hybrid time boundary. Fresh validation tests reject new membership (including an earlier non-target marker), state/timestamp changes, and read failures; they verify validation and deletion hold the local updater lock, including with lineage-exclusive deletion disabled. The real-registry gauge lifecycle test covers policy removal, deletion failure followed by successful retry, reconfiguration, and leadership cleanup.

Scoped Spotless, Checkstyle, and license checks passed on pinot-common, pinot-controller, pinot-segment-local, and pinot-spi. Compilation with deprecation and warning flags produced no source warnings on added lines. Local builds used a temporary compiler classpath addition for the existing missing JetBrains annotation dependency in ZstandardDecompressor; no build dependency changes are included.

Upstream Apache Pinot #19773 provides the controller job-limit fix; the duplicate test repair has been removed from this PR after rebasing onto master.

Release note: add optional segmentsConfig.retentionSize for periodic, oldest-first retention by compressed live segment size, protecting every listed lineage segment and stopping at its boundary with OFFLINE newest-segment and realtime recovery safeguards plus a table-level blocked-status gauge. OFFLINE and REALTIME size limits expire data independently of the hybrid routing time boundary.

@xiangfu0 xiangfu0 added release-notes Referenced by PRs that need attention when compiling the next release notes feature New functionality labels Oct 6, 2026
@codecov-commenter

codecov-commenter commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.60%. Comparing base (f818bae) to head (b53e33f).
⚠️ Report is 14 commits behind head on master.

Files with missing lines Patch % Lines
...troller/helix/core/retention/RetentionManager.java 96.45% 0 Missing and 5 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19770      +/-   ##
============================================
+ Coverage     68.55%   68.60%   +0.04%     
  Complexity     1486     1486              
============================================
  Files          3525     3526       +1     
  Lines        229852   230288     +436     
  Branches      36507    36593      +86     
============================================
+ Hits         157568   157981     +413     
- Misses        59965    59969       +4     
- Partials      12319    12338      +19     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 68.60% <96.77%> (+0.04%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 68.60% <96.77%> (+0.04%) ⬆️
unittests 68.59% <96.77%> (+0.04%) ⬆️
unittests1 58.32% <57.14%> (+0.01%) ⬆️
unittests2 40.21% <96.77%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed the retention logic against the lineage/deletion paths it interacts with. The config plumbing, validation, and safety-skip behaviors (unknown sizes, missing metadata, REFRESH tables) all look solid, and the test coverage is thorough. Left a few inline comments — the main ones are about how lineage-related bytes are accounted (replaced segments and in-progress replacements can trigger eviction of live data) and about the silent-failure path when deleteSegments rejects the batch.

@xiangfu0 xiangfu0 left a comment

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.

Re-reviewed the update (2e170fc). All five earlier comments are addressed well: replaced segments are excluded from accounting, an in-progress replacement now postpones the whole pass, the deleteSegments response is checked with correct byte accounting on failure, the newest OFFLINE segment is protected, hybrid REALTIME eviction is bounded by the offline time boundary, and the SIZE_RETENTION_BLOCKED gauge covers the observability gap. I also ran RetentionManagerTest at this head (46 tests, 0 failures) and the new tests are genuinely regression-sensitive — e.g. the lineage and newest-segment tests now assert the opposite of what the v1 tests asserted. Remaining feedback inline: one real issue (gauge cardinality for unconfigured tables) and a few minors.

@xiangfu0
xiangfu0 requested review from Jackie-Jiang and yashmayya and a balanced review from Copilot October 7, 2026 05:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@xiangfu0 xiangfu0 left a comment

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.

Re-reviewed c32ac7f. All four round-2 comments are addressed cleanly:

  • The disabled branch now calls removeTableGauge, so only tables with retentionSize configured carry the sizeRetentionBlocked series, and the ControllerGauge doc says "absent when disabled".
  • The pass is refactored into enforceSizeBasedRetention with a single gauge write in a finally, so healthy passes never expose a transient blocked value, while an interrupting exception still publishes 1 (and there's now a test for exactly that path).
  • One lineage snapshot is shared across the IN_PROGRESS check, replaced-segment filtering, and candidate protection, with the IdealState read deliberately ordered before the lineage read — I verified the invariant the comment claims: a replacement writes its IN_PROGRESS entry before its destinations enter ideal state, so a lineage snapshot without the entry guarantees the earlier IS snapshot has no destinations, and double-counting is impossible. testSizeRetentionSeesLineageStartedWhileReadingIdealState pins the race window directly. The flag-off residual race is now documented in the getRetentionSize javadoc.
  • verifySizeRetentionGauge pins exactly one gauge write with the final value, and the real-registry lifecycle test covers register → blocked → recover → remove-on-disable → remove-on-leadership-loss end to end.

I ran RetentionManagerTest at this head on JDK 25: 49 tests, 0 failures, 0 errors. One take-it-or-leave-it nit inline; otherwise this looks ready to me.

@xiangfu0 xiangfu0 left a comment

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.

Re-reviewed the three new commits. The lineage-barrier redesign is a real improvement over the full-pass IN_PROGRESS skip: eviction can now make progress on the old prefix during a replacement, every lineage member (any state) is protected by position rather than by flag-dependent filtering, and the pre-delete snapshot recheck under getLineageUpdaterLock closes the exclusive-delete-off TOCTOU raised earlier, within a controller (the cross-controller limitation is honestly documented). The round-3 nit is resolved by construction — the redundant getDeleteBlockedSegments call is gone and the barrier strictly subsumes it.

Things I verified rather than assumed:

  • The snapshot-equality check is sound: LineageEntry has value-based equals/hashCode including state and timestamp, so freshly-parsed entries compare correctly (and testSizeRetentionRejectsPrefixWhenLineageStateOrTimestampChanges depends on exactly that).
  • Shadow copies are excluded from accounting but still establish the barrier, and the barrier survives the last-LLC and hybrid-boundary candidate filters (the two CannotHideLineageBarrier tests pin this).
  • The IdealState-before-lineage read order still prevents double-counting under the new model, since non-COMPLETED destinations are filtered from accounting.
  • Ran RetentionManagerTest (62 tests) and ControllerZkHelixUtilsTest (2 tests) at this head on JDK 25: 0 failures, 0 errors.

Two inline notes — one operational question on barrier pinning, one process nit on the drive-by test fix. Nothing blocking from my side.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/codex2/table-size-retention branch from 258073b to 5b357d5 Compare October 8, 2026 04:04
@xiangfu0
xiangfu0 force-pushed the xiangfu0/codex2/table-size-retention branch from 5b357d5 to a456352 Compare October 8, 2026 04:24

@xiangfu0 xiangfu0 left a comment

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.

Re-reviewed the rebase plus 0690fde. Both round-4 comments are addressed and the scope issue resolved itself in the best possible way:

  • The ControllerZkHelixUtilsTest drive-by fix was extracted into #19773 and merged separately, so this PR is back to cleanly retention-scoped files after the rebase.
  • The new getRetentionSize paragraphs document exactly what I asked for: the barrier-pinning durations (4h/24h replaced-segment windows, 1d stale-IN_PROGRESS, the 6h retention schedule as an additional release delay, and the explicit "hours or longer at sizeRetentionBlocked=1" warning for merge-heavy tables), plus an explicit statement that the pre-delete snapshot check deliberately over-rejects.

I fact-checked every claim in the new javadoc against the code rather than taking it on faith: the 4h/24h defaults and all-ingestion-type applicability match DefaultLineageManager (the old getReplacedSegmentsRetentionPeriod doc was actually wrong on both counts — claiming APPEND ignored the setting and a 1d default — so these corrections fix real errors); REVERTED entries are indeed eligible immediately (state == REVERTED || short-circuits the age check); "unset, empty, or unparseable → default" matches getRetentionMsFromConfig; the 6h DEFAULT_RETENTION_MANAGER_FREQUENCY_PERIOD is right; and even the subtle two-pass timing ("entry is removed on a subsequent cleanup pass after sources leave IdealState") is accurate, since entry removal checks membership against the segment list captured at pass start.

Re-ran RetentionManagerTest on the rebased head (new master base) on JDK 25: 62 tests, 0 failures, 0 errors.

No remaining concerns — LGTM. (As the PR author I can't formally approve, but everything raised across the five review rounds is now resolved.)

@Jackie-Jiang Jackie-Jiang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few non-blocking simplifications I’d suggest:

  • Use long instead of BigInteger. Overflow would require nearly 8 EiB of compressed data in one table. We don’t need arbitrary-precision accounting or the synthetic overflow test for this use case.
  • Simplify candidate filtering and use set membership. Filter the segment metadata directly instead of converting metadata → name list → name set → metadata. In particular, materialize the last-completed LLC names into a HashSet: the current ArrayList.removeAll(HashMap.values()) can take O(segments × partitions).
  • Consolidate duplicate tests. The disabled-policy tests overlap, and the real-registry lifecycle test already covers deletion failure followed by successful retry. Keep the stronger lifecycle coverage and move any distinct assertions there. The null deletion-response case and corresponding guard can also go; the production API always returns a response.

The timestamp fallback makes sense for OFFLINE APPEND tables without a time column, so I’d keep it. I’m also fine keeping SIZE_RETENTION_BLOCKED for visibility into policies that cannot meet their cap.

@xiangfu0

xiangfu0 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the simplification review in 89f6f0dc7d.

  • Switched retained/projected byte accounting to long and removed the synthetic overflow test.
  • Filter candidate metadata directly, with a HashSet for last-completed LLC names and direct hybrid-boundary filtering.
  • Consolidated disabled-policy and failure/retry tests into the real-registry lifecycle test. It covers both REFRESH and unset policies, registry absence, exact gauge publication and deletion counts, reconfiguration, and leadership cleanup. Removed the null-response scenario and guard; the production deletion API always returns a response.

All 79 targeted tests passed with no failures, errors, or skips, including 58 RetentionManagerTest cases. Scoped Spotless, Checkstyle, and license checks passed, with no compiler warnings on added lines. The PR description has been updated.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed 89f6f0d. The simplification is almost entirely a win, and I verified the pieces that could have changed behavior:

  • The candidateNames removal is semantically equivalent in all four filter branches — the ts≥0 pre-filter, last-LLC exclusion, hybrid boundary (endTime < 0 || >= boundary removal matches the old endTime >= 0 && < boundary retain), and newest-OFFLINE removal all select the same victims as before, and the barrier logic is untouched since it reads lineageOwnedMetadata independently.
  • Dropping the response == null guard is safe: deleteSegmentsInternal returns a non-null response on every path (the whole body is wrapped in try/catch returning success/failure), and the test helper stubs SUCCESS so no mock default leaks through.
  • The test consolidation into testSizeRetentionGaugeRegistryLifecycle preserves the failure→blocked→retry→recovered coverage against a real metrics registry, now in both REFRESH and unconfigured variants.
  • Ran RetentionManagerTest at this head on JDK 25: 58 tests, 0 failures, 0 errors.

One real regression inline: the BigInteger→long change removes the overflow protection and its test, and the failure mode isn't benign — see the comment for the one-line way to keep both the simplicity and the safety.

Reproduce with completed segments sized Long.MAX_VALUE and one byte: unchecked addition wrapped negative and published a healthy gauge. Use checked long addition to skip eviction, warn, and publish the blocked gauge for both OFFLINE and REALTIME.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed 3556208 — this resolves the round-6 overflow finding exactly as suggested: Math.addExact keeps the long simplicity while restoring the fail-safe behavior (warn + skip + sizeRetentionBlocked=1 on corrupt EB-scale sizes instead of silently reporting healthy), and the restored test now asserts the skip semantics for both OFFLINE and REALTIME. Ran RetentionManagerTest at this head on JDK 25: 59 tests, 0 failures, 0 errors.

Nothing further from me — every finding across the seven review rounds is resolved. LGTM.

Remove the checked-addition guard and synthetic overflow case. Totals beyond Long.MAX_VALUE are outside realistic table sizes.
@xiangfu0
xiangfu0 requested a review from Jackie-Jiang October 8, 2026 20:19

@xiangfu0 xiangfu0 left a comment

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.

Reviewed d521496. This cleanly reverts the Math.addExact guard and the synthetic overflow test, per the author's stated decision that totals beyond Long.MAX_VALUE are outside realistic table sizes. The resulting RetentionManager.java and RetentionManagerTest.java are byte-identical to the 89f6f0d state, which I already verified and ran green (58 tests, 0 failures on JDK 25), so there's nothing new to re-verify.

I'll register the residual trade-off once for the record and then consider it settled: the other corrupt-metadata shapes in this pass (negative size, missing metadata, unlocatable barrier) fail safe with a WARN and sizeRetentionBlocked=1, while a wraparound-scale corrupt sizeInBytes now reports the table healthy. Accepting that is a legitimate call — the input requires a segment claiming exabytes in ZK — and it's the author's to make. One small inline ask remains from round 6's fallback option; otherwise this is still LGTM.

With hybrid time retention enabled, REALTIME size eviction could skip its configured cap while OFFLINE boundary resolution was unavailable. Remove that cross-table dependency and expire each typed table oldest first. Keep lineage and recovery protections, and document intentional size expiry beyond the hybrid routing cutoff.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed 7ee85f7. This reverses the hybrid time-boundary guard in favor of the "document it" option from the round-1 comment, and I think it's the right call on reflection: the guard made a hybrid REALTIME table's size cap silently unenforceable whenever the OFFLINE boundary couldn't resolve (broker RPC down, schema/time-column issues), coupling the REALTIME table's disk-safety mechanism to cross-table health. An operator who sets a size cap is stating a resource bound; not enforcing it because an unrelated lookup failed was the worse failure mode, and the gauge sat at 1 with no way to make progress.

Verified on this head:

  • The removal is complete and clean — getHybridTimeBoundaryMs remains in use by hybrid time retention only, no stale imports in main or test code (checkstyle ran clean this time, not skipped).
  • The rewritten tests assert the decoupling properly: getOfflineTableConfig and getTimeBoundaryInfo are verified never-invoked on the size path, in both hybrid-strategy settings, and the boundary-unavailable scenario now evicts and clears the gauge instead of blocking.
  • The javadoc states the new semantics honestly, including that eviction order for undated-endTime segments falls back to creation/push time (the updated hybrid test pins invalidEndTime evicting first by its creation time).
  • RetentionManagerTest: 57 tests, 0 failures, 0 errors on JDK 25.

One inline note on the operator-facing consequence; otherwise LGTM.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed 4292097 — a one-line, comment-only change that resolves the round-8 ask exactly: the plain-addition line now states the accepted wraparound trade-off in place, so future readers won't mistake the asymmetry with the other fail-safe guards for an oversight or re-add the rejected checked addition. No code or test behavior changed, so nothing to re-run.

The only item still open is the optional round-9 doc suggestion (hybrid sizing guidance in the retentionSize javadoc — "size the cap so the REALTIME table holds all data newer than the boundary"). That's a take-it-or-leave-it improvement, not a blocker. Everything else across all rounds is resolved — LGTM.

@xiangfu0 xiangfu0 left a comment

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.

Final pass on the updated PR description (no code changed since 4292097). The rewrite now describes the shipped design rather than the v1 design, and I verified it the same way as the code rounds — checking claims against the implementation, not just reading them:

  • Eviction rules 1–6 are accurate against the current head: IdealState-before-lineage read order, single planning snapshot, IN_PROGRESS no longer skipping the table, shadow-copy exclusion from accounting but not from barrier placement, newest-OFFLINE and last-LLC protections, the timestamp fallback chain with name tie-break, zero-byte skip, the under-lock full-map recheck (including its deliberate over-rejection), and the response check before accepting reductions.
  • The monitoring section matches the gauge's actual lifecycle (single publish per pass including exceptions, absent when disabled, removed on leadership loss/table deletion).
  • The cleanup-window paragraph is correct, including the subtle point that lineageEntryCleanupRetentionPeriod does not add a one-day delay to COMPLETED entries — those release on the pass after their sources leave IdealState.
  • The validation claim holds with margin: I ran all seven named classes in full at this head on JDK 25 — RetentionManagerTest 57, TableConfigUtilsTest 84, RebalanceCheckerTest 7, TableConfigBuilderTest 5, SegmentLineageCleanupTest 3, ControllerZkHelixUtilsTest 2, ControllerGaugeTest 1 — 159 tests, 0 failures, 0 errors, 0 skips, a superset of the 78 targeted tests cited.
  • The #19773 attribution and release note are accurate.

All three items from the maintainer's simplification review (plain long, in-place filtering with HashSet last-LLC lookup, test consolidation + null-guard removal) are confirmed addressed at this head as well. The only open item remains the optional round-9 hybrid sizing sentence for the javadoc — non-blocking. From my side this PR is complete and consistent: code, tests, docs, and description all describe the same system. LGTM.

Keep enough REALTIME data past the routing boundary for RealtimeToOffline. Otherwise queries can silently lose rows when size retention removes that input.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed 7c1945b — a three-line javadoc addition that resolves the last open item (the round-9 hybrid sizing guidance): operators now get the actionable rule (size the REALTIME cap to retain everything newer than the routing boundary) plus the concrete failure mode if they don't (data deleted ahead of RealtimeToOffline, queries silently returning no rows from either side). Doc-only change, nothing to re-run.

With this, every item raised across all review rounds is resolved and nothing is outstanding. LGTM — ready to merge from my perspective.

@Jackie-Jiang Jackie-Jiang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Re-reviewed 7c1945b; no blocking findings. The independent hybrid size-retention behavior is documented and covered by tests. All 13 reported CI checks passed.

One optional performance cleanup inline.

@xiangfu0 xiangfu0 left a comment

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.

Reviewed b53e33f3. Clean refinement: the newest-OFFLINE-segment removal previously went through List.remove(Object), which invokes SegmentZKMetadata's value-based equals — a full toMap() materialization and comparison per scanned element — when the intent was "remove this exact instance". The identity-based removeIf is both semantically precise and cheaper. I checked the correctness angle: the old form wasn't actually buggy (segment names are unique within the list, so deep equality could only ever match the selected instance), so this is a robustness/efficiency improvement rather than a bug fix, and no behavior changes.

RetentionManagerTest at this head on JDK 25: 57 tests, 0 failures, 0 errors. Still LGTM — nothing outstanding.

@xiangfu0
xiangfu0 merged commit dd2c3cd into master Oct 11, 2026
13 checks passed
@xiangfu0
xiangfu0 deleted the xiangfu0/codex2/table-size-retention branch October 11, 2026 08:52
xiangfu0 added a commit to pinot-contrib/pinot-docs that referenced this pull request Oct 12, 2026
Follow-up to apache/pinot#19770. Documents retentionSize, accounting,
protected segments, lineage barriers, and hybrid migration safety. Local
documentation validation passed (10 existing anchor warnings).

Co-authored-by: Xiang Fu <xiangfu@Xiang-mac-mtv-2.local>
@xiangfu0

Copy link
Copy Markdown
Contributor Author

Documentation follow-up merged: pinot-contrib/pinot-docs#1096. Covers retentionSize configuration, compressed-size accounting, protected segments and lineage barriers, and the hybrid-table migration safety caveat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New functionality release-notes Referenced by PRs that need attention when compiling the next release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants