HBASE-30335 Seed master flushedSequenceIdByRegion with openSeqNum on region OPEN - #8584
HBASE-30335 Seed master flushedSequenceIdByRegion with openSeqNum on region OPEN#8584nirdosh0110 wants to merge 6 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>
| regionStates.removeFromFailedOpen(regionInfo); | ||
| // HBASE-30335: seed the master's flushed sequence cache with openSeqNum so a subsequent | ||
| // WAL split (e.g. source RS crashes after drain-move) recognizes already-durable edits | ||
| // instead of writing orphaned recovered.edits. |
There was a problem hiding this comment.
It would be good to add tests.
…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>
|
I think this is an optimization for not splitting unnecessary wal edits, but why it will lead to merge stuck? After opening a region, we should have removed all the recovered.edits files? |
Thanks @Apache9 for looking into it. Below is incident brief explanation. I've also raised PR8583 to reopen parent region on rollback of MERGE_TABLE_REGIONS_CHECK_CLOSED_REGIONS linked to this incident. What Happened:
Where it got stuck: At 18:32:33 UTC, MergeTableRegionsProcedure (pid=46990253) unassigned the region successfully. |
apurtell
left a comment
There was a problem hiding this comment.
Approved, with two suggestions
| if (openSeqNum == HConstants.NO_SEQNUM || openSeqNum < 0) { | ||
| return; | ||
| } | ||
| flushedSequenceIdByRegion.putIfAbsent(regionInfo.getEncodedNameAsBytes(), openSeqNum); |
There was a problem hiding this comment.
A max merge is more correct and equally safe, because at OPEN time a region cannot have flushed past its own openSeqNum.
public void reportRegionOpen(final RegionInfo regionInfo, final long openSeqNum) {
if (openSeqNum < 0) { // NO_SEQNUM == -1
return;
}
flushedSequenceIdByRegion.merge(regionInfo.getEncodedNameAsBytes(), openSeqNum, Math::max);
}| // longer NO_SEQNUM before the first flush - it is the region's openSeqNum, which must | ||
| // still be strictly less than the memstore's earliest unflushed edit. | ||
| assertNotEquals(HConstants.NO_SEQNUM, ids.getLastFlushedSequenceId()); | ||
| assertTrue(ids.getLastFlushedSequenceId() < storeSequenceId); |
There was a problem hiding this comment.
This assertion is fragile.
It is technically off by one, but because the region open marker consumes one seq id, the test will currently pass.
Maybe assertNotEquals(NO_SEQNUM, ...) instead?
…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.
Then basically there are two problems. We can do these changes in a separated issue. Thanks. |
I might not be right here but what I understand from the code is having recovered.edits files once region is open is unacceptable, and I think that was right. Even if we check that the edits are all below the persistent seqNum, we might not be sure that we didn't miss some edits. But we might not have any other way here, anyway rollback and open will do the same. |
@Apache9 Totally agree — we should have tolerance built into the merge and split procedures. I've filed a separate bug to track the issue pointed |
Summary
flushedSequenceIdByRegionis only updated via periodic RegionServer heartbeats. On a fresh region OPEN, the entry is absent, soServerManager.getLastFlushedSequenceIdreturnsNO_SEQNUMandWALSplittertreats every WAL edit as un-flushed. If the source RS of a drain-move crashes before its next heartbeat, this produces orphanedrecovered.editsfiles containing already-durable edits, which then surface as false-positive "data loss" warnings during subsequent merge/split operations and leave regions stuck in RIT.ServerManager.reportRegionOpen(regionInfo, openSeqNum)and call it fromAssignmentManager.regionOpenedWithoutPersistingToMetaso the master seeds its flushed-sequence cache synchronously with theopenSeqNumreported on the OPEN transition.putIfAbsentis used so a heartbeat-supplied value (which may reflect flushes after open) is never regressed.JIRA: https://issues.apache.org/jira/browse/HBASE-30335
Test plan
TestAssignmentManagerandTestServerManagersuites pass.recovered.editsare produced for already-flushed sequence numbers and no downstream stuck RIT on a follow-up merge.