Restore size-based periodic flush on remote-store shards - #22865
Restore size-based periodic flush on remote-store shards#22865shourya035 wants to merge 6 commits into
Conversation
On remote-store enabled shards the translog based periodic flush condition (index.translog.flush_threshold_size) is ineffective: RemoteFsTranslog.getMinUnreferencedSeqNoInSegments() returns minSeqNoToKeep, which is advanced by every successful segments upload, so the referenced translog size never grows. Shards can therefore go arbitrarily long without a commit, pinning the soft-deletes retention policy to a stale commit point and accumulating delete tombstones through merges. This restores an equivalent size-based flush signal scoped to the broken code path only. RemoteStoreRefreshListener already builds a map of post-refresh local segment file names to sizes for every upload cycle; after a successful segments sync (the same callback that trims the translog) it now publishes the total size of segment files not yet referenced by the last commit point to the engine, stamped with the commit generation it was computed against. The engine flushes once that size breaches the threshold. A stale stamp (e.g. right after a flush) can never re-trigger a flush, and the computation adds no I/O on either the write path (plain volatile read) or the refresh path (in-memory set diff over the existing map). Introduces two dynamic index settings: - index.remote_store.flush_on_uncommitted_segments.enabled (default true) controls the condition; when disabled the accounting is neither published nor consulted - index.remote_store.flush_on_uncommitted_segments.threshold_size defaults to the current value of the index's index.translog.flush_threshold_size via setting fallback Signed-off-by: Shourya Dutta Biswas <114977491+shourya035@users.noreply.github.com>
Adds an end-to-end integration test reproducing the scenario from issue opensearch-project#22802: an update-heavy workload with regular refreshes on a remote-store shard. Asserts that the uncommitted-segment-bytes condition fires periodic flushes, the safe commit advances past the workload, and a non-flushing expunge-deletes merge can reclaim the soft-delete tombstones. The test pins index.soft_deletes.retention.operations=0 (the random index template may inject values that retain the whole workload) and uses a zero translog buffer interval plus a fast-tracking retention lease so each iteration completes in about 1.5 seconds. Verified across 50 randomized seeds. Signed-off-by: Shourya Dutta Biswas <114977491+shourya035@users.noreply.github.com>
PR Reviewer Guide 🔍(Review updated until commit af4ae90)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to af4ae90 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 09889d0
Suggestions up to commit 3df1ff1
Suggestions up to commit d3c52b2
|
Snapshot lastCommittedSegmentInfos into a local in shouldFlushOnUncommittedSegmentBytes() so the null check and the generation comparison observe the same commit point, matching the guard already present on the publish side. Also document why the publish-side race window is benign: the stamp and the file set come from one snapshot, so a flush landing between snapshot and publish only makes the stamp unmatchable (suppressing the trigger until the next successful segments sync), never a spurious flush. Signed-off-by: Shourya Dutta Biswas <114977491+shourya035@users.noreply.github.com>
|
Persistent review updated to latest commit 3df1ff1 |
The threshold setting accepted any parseable byte size including 0b and -1, which would trigger a flush on every successful segments sync -- the opposite of what a user reaching for -1 (the common disable idiom) intends, and disablement already has its own explicit setting. Bound the value to [1b, Long.MAX_VALUE] via a new byteSizeSetting overload that combines a fallback setting with min/max validation, mirroring the existing bounded-default overload. Also adds settings-level test coverage for the fallback semantics: default inherited from index.translog.flush_threshold_size, dynamic updates of only the fallback reflected live, explicit value winning over the fallback, and rejection of zero/negative sizes. Signed-off-by: Shourya Dutta Biswas <114977491+shourya035@users.noreply.github.com>
|
Persistent review updated to latest commit 09889d0 |
|
❌ Gradle check result for 09889d0: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit af4ae90 |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #22865 +/- ##
============================================
- Coverage 71.58% 71.57% -0.02%
+ Complexity 77341 77301 -40
============================================
Files 6170 6170
Lines 359775 359824 +49
Branches 52478 52490 +12
============================================
- Hits 257541 257539 -2
+ Misses 81770 81766 -4
- Partials 20464 20519 +55 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Description
Problem
For remote-store indices, successful remote segment uploads suppress the size-based flush condition, as described in #22802. Every successful segments sync advances
minSeqNoToKeepto the last refreshed checkpoint;RemoteFsTranslog#getMinUnreferencedSeqNoInSegments()returns that moving boundary, soInternalTranslogManager#shouldPeriodicallyFlush()measures translog bytes above an ever-advancing floor and theindex.translog.flush_threshold_sizecondition never trips. Under sustained writes with regular refreshes the safe commit falls arbitrarily far behind the current sequence number,SoftDeletesPolicy#getMinRetainedSeqNo()stays pinned to the stale safe commit,SoftDeletesRetentionMergePolicycarries the protected soft deletes through merges, anddocs.deletedgrows continuously under update-heavy or delete-heavy workloads.As laid out in #22805, this is the compound effect of two deliberate changes: #7383 detached remote translog retention from the safe commit (for failover latency), and #10761 aligned the flush-size check with that retention boundary (to bound retained translog for relocation). Together they reinterpreted
flush_threshold_sizefrom "bytes written since the last commit" to "currently retained translog bytes" — which serves the disk/recovery-bounding job of the size-based flush but drops the commit-lag-bounding job.Solution
Instead of reinstating commit-relative translog byte accounting (the approach drafted in #22805), this change bounds the safe-commit lag directly with a segment-size signal, scoped to remote-store shards, and leaves all translog accounting untouched — so the retention and flush-sizing behaviors introduced by #7383/#10761 are structurally unaffected.
RemoteStoreRefreshListeneralready builds a map of post-refresh local segment file names to sizes on every upload cycle. After a successful segments sync (the same callback that advancesminSeqNoToKeep), it now computes the total size of segment files not yet referenced by the last commit point (an in-memory set diff againstlastCommittedSegmentInfos.files(false)— no additional I/O) and publishes it to the engine. The file-level diff intentionally counts per-segment update files (live docs, doc-values updates) written onto already-committed segments, so tombstone accumulation contributes to the signal.InternalEngine#shouldPeriodicallyFlush()gains a condition that triggers the existing async flush path when the published bytes breach the threshold. The published value is stamped with the commit generation it was computed against and is only honored while that generation is current: a stale stamp (right after a flush, before the next sync republishes) can never re-trigger a flush, so flush loops are impossible by construction. Hot-path cost is a volatile read plus two comparisons.index.remote_store.flush_on_uncommitted_segments.enabled(defaulttrue): when disabled, the accounting is neither published (listener short-circuits) nor consulted (engine re-checks live, so disabling takes effect immediately)index.remote_store.flush_on_uncommitted_segments.threshold_size: defaults to the index's currentindex.translog.flush_threshold_sizevia setting fallback — so the operator-configured flush threshold regains its "flush after this much work since the last commit" meaning on remote store, with segment bytes as the measureOnce the threshold is exceeded the shard flushes, the safe commit advances, and subsequent merges can reclaim eligible soft-deleted documents — the expected behavior from #22802.
Document-replication shards and replicas (
NRTReplicationEngine) are structurally unaffected: the accounting is only ever published on remote-store primaries, and the engine condition additionally requires the enabled index setting.Notes
minSeqNoToKeepalso stops advancing in that regime, which re-arms the existing translog-size condition; the two signals hand off to each other.segments_Nfiles are excluded from the accounting.Related Issues
Resolves #22802
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.