HBASE-30433 Seed the master's flushedSequenceIdByRegion watermark with the region's final flushed seqid on region CLOSE - #8707
Open
nirdosh0110 wants to merge 9 commits into
Open
nirdosh0110 wants to merge 9 commits into
nirdosh0110 wants to merge 9 commits into
Conversation
…region OPEN When a region is opened, the master does not populate flushedSequenceIdByRegion until the hosting RegionServer's next heartbeat delivers a flush report. If the source RegionServer of a drain-move crashes before that heartbeat, ServerManager. getLastFlushedSequenceId returns NO_SEQNUM (-1) for the region, and WALSplitter conservatively writes already-durable edits into recovered.edits. Those orphaned edits then trigger false-positive "data loss" warnings during subsequent merge/split operations and leave regions stuck in RIT. Add ServerManager.reportRegionOpen(regionInfo, openSeqNum) and call it from AssignmentManager.regionOpenedWithoutPersistingToMeta so the watermark is established synchronously at OPEN time. putIfAbsent is used so a subsequent heartbeat with a higher completedSequenceId is never regressed by a stale open value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…estGetLastFlushedSequenceId New unit test TestServerManager covers reportRegionOpen behavior: - seeds flushedSequenceIdByRegion with the supplied openSeqNum; - putIfAbsent semantics prevent regressing a higher watermark that was already established (by an earlier open or a heartbeat); - NO_SEQNUM and negative openSeqNum are ignored (no-op). TestGetLastFlushedSequenceId previously asserted the pre-flush lastFlushedSequenceId was NO_SEQNUM. That assumption is invalidated by the fix (openSeqNum is now seeded synchronously on OPEN); the assertion is updated to require the watermark be present but strictly less than the memstore's earliest unflushed edit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t assertion Per review from @apurtell: - ServerManager.reportRegionOpen: switch putIfAbsent to merge with Math::max so a stale-low prior heartbeat value is lifted to openSeqNum rather than ignored. Safe because at OPEN a region cannot have flushed past its own openSeqNum. Guard simplified to openSeqNum < 0 (NO_SEQNUM == -1, so the disjunct was redundant). - TestGetLastFlushedSequenceId: drop the strict assertTrue(lastFlushed < storeSequenceId) - it holds only because the region-open marker consumes one seqId, so the assertion is coupled to an incidental accounting detail rather than the contract being tested. The assertNotEquals(NO_SEQNUM, ...) above captures the load-bearing invariant. Reflow adjacent javadoc block for spotless.
The seed added in reportRegionOpen (openSeqNum via Math::max) exposed a long-standing test-fixture issue: AbstractTestDLS.makeWAL uses a fresh MultiVersionConcurrencyControl that stamps WAL edits starting at seqid 1, inconsistent with the seqid sequence a real WAL preserves for the region. With the seed in place, the splitter correctly filters those low-seqid edits as already-durable and testMasterStartsUpWithLogSplittingWork loses 5/1000 rows. Advance the local MVCC past the max openSeqNum of the target regions before stamping edits, so the injected WAL entries get seqids a real region would have assigned.
…eqNum seed The test previously asserted flushedSequenceIdByRegion is byte-for-byte identical across cluster shutdown+restart. After HBASE-30335 the master seeds this map on region OPEN via merge(openSeqNum, Math::max). openSeqNum is monotonic across close/open cycles, so a region reopened after restart can carry a strictly higher value than what was persisted at shutdown, and the equality assertion no longer holds. Assert the preserved invariant instead: every region persisted at shutdown is loaded on restart (same keyset) and no watermark regresses (after[r] >= before[r]). This validates persist/load correctness without conflicting with the new seed-on-open semantic.
…lobber the OPEN seed updateLastFlushedSequenceIds did a non-atomic get-then-put on flushedSequenceIdByRegion. A stale in-flight heartbeat from the soon-to-be-dead source RS could read null/a low value, race past the reportRegionOpen seed (merge/Math::max), and then put its lower value on top - reintroducing the stale-fence bug this PR fixes, in a narrower window (flagged by Copilot's review). Replace both the region- and store-level updates with an atomic compute that keeps the same "never lower the watermark" rule. Every writer on the live serving path is now an atomic max-merge, so the stored value is monotonic non-decreasing and a concurrent seed can no longer be clobbered. Add TestServerManager.testConcurrentStaleHeartbeatDoesNotClobberOpenSeed, which drives the OPEN seed and a stale heartbeat concurrently over 500 rounds. It fails on the pre-fix code and passes with the fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h the region's final flushed seqid on region CLOSE On graceful region CLOSE the RegionServer now reports the region's durable flushed seqid (HRegion.getMaxFlushedSeqId()) on the CLOSED transition, and the master lifts its flushedSequenceIdByRegion watermark from it via the existing monotonic reportRegionOpen(merge(Math::max)) seed. This narrows the graceful-close window where a subsequent WAL split of a crashed source RS could otherwise treat already-durable edits as unflushed and write orphaned recovered.edits. No proto/RPC change: the RegionStateTransition message already carries an optional openSeqNum field and the CLOSED transition is already routed through the same master-side handler. HRegionServer now also sets that field for CLOSED (not just OPENED), and AssignmentManager seeds the watermark on CLOSED when seqId >= 0. Adds TestGetLastFlushedSequenceId#testFlushedSequenceIdSeededOnRegionClose.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
JIRA: https://issues.apache.org/jira/browse/HBASE-30433
Summary
Seed the master's
flushedSequenceIdByRegionwatermark with the region's final flushed seqid on region CLOSE, so a subsequent WAL split of a drained/crashed source RS recognizes already-durable edits instead of writing orphanedrecovered.edits.Why
The master already seeds this watermark with
openSeqNumat region OPEN. That is the correct primary fence and fires on every reopen path, but it leaves one narrow graceful-close window uncovered:OPEN, so the OPEN-time seed has not fired.flushedSequenceIdByRegion, and writes a (harmless-but-present)recovered.editsfile.Seeding on CLOSE advances the watermark to the region's reported flushed seqid before the source RS dies, closing this sub-window for the graceful-close case. This is additive: it does nothing for the pure-crash / never-gracefully-closed path (there is no close report), so the OPEN-time seed remains. Both writers go through the same monotonic
merge(Math::max), so they compose safely and never regress a higher value.Change
No protobuf/RPC change — the
RegionStateTransitionmessage already carries an optionalopenSeqNumfield and theCLOSEDtransition is already routed through the same master-side handler.CloseRegionHandlerandUnassignRegionHandlerreportHRegion.getMaxFlushedSeqId()on theCLOSEDtransition (wasHConstants.NO_SEQNUM).HRegionServer.createReportRegionStateTransitionRequestnow sets the wireopenSeqNumfield forCLOSEDtoo (when>= 0), not justOPENED.AssignmentManagerseeds the watermark on theCLOSEDtransition viaserverManager.reportRegionOpen(regionInfo, seqId)whenseqId >= 0(the existing atomic max-merge).Test
Adds
TestGetLastFlushedSequenceId#testFlushedSequenceIdSeededOnRegionClose: write + flush pastopenSeqNum,disableTable(a graceful close that keeps the region — unlike delete/split/merge, disable does not callServerManager#removeRegion), then assert the watermark reflects the post-close flushed seqid.Local run (JDK17):
TestGetLastFlushedSequenceId3/3 andTestServerManager5/5 green;spotless:checkonhbase-serverclean.Note on stacking
This builds on #8584 (HBASE-30335), which introduces the
ServerManager.reportRegionOpenseed this reuses. #8584 is not yet merged, so this PR currently includes its commits; it should be merged after #8584. Once #8584 lands, I will rebase so only the CLOSE-time commit remains.