Skip to content

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
apache:masterfrom
nirdosh0110:HBASE-30433
Open

nirdosh0110 wants to merge 9 commits into
apache:masterfrom
nirdosh0110:HBASE-30433

Conversation

@nirdosh0110

Copy link
Copy Markdown
Contributor

JIRA: https://issues.apache.org/jira/browse/HBASE-30433

Summary

Seed the master's flushedSequenceIdByRegion watermark 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 orphaned recovered.edits.

Why

The master already seeds this watermark with openSeqNum at region OPEN. That is the correct primary fence and fires on every reopen path, but it leaves one narrow graceful-close window uncovered:

  1. A region is gracefully closed on the source RS (memstore flushed to the close marker).
  2. The source RS dies before the target RS finishes OPEN, so the OPEN-time seed has not fired.
  3. The source RS's SCP splits its WAL in that window, filtering against a still-stale flushedSequenceIdByRegion, and writes a (harmless-but-present) recovered.edits file.

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 RegionStateTransition message already carries an optional openSeqNum field and the CLOSED transition is already routed through the same master-side handler.

  • RegionServer — CloseRegionHandler and UnassignRegionHandler report HRegion.getMaxFlushedSeqId() on the CLOSED transition (was HConstants.NO_SEQNUM). HRegionServer.createReportRegionStateTransitionRequest now sets the wire openSeqNum field for CLOSED too (when >= 0), not just OPENED.
  • Master — AssignmentManager seeds the watermark on the CLOSED transition via serverManager.reportRegionOpen(regionInfo, seqId) when seqId >= 0 (the existing atomic max-merge).

Test

Adds TestGetLastFlushedSequenceId#testFlushedSequenceIdSeededOnRegionClose: write + flush past openSeqNum, disableTable (a graceful close that keeps the region — unlike delete/split/merge, disable does not call ServerManager#removeRegion), then assert the watermark reflects the post-close flushed seqid.

Local run (JDK17): TestGetLastFlushedSequenceId 3/3 and TestServerManager 5/5 green; spotless:check on hbase-server clean.

Note on stacking

This builds on #8584 (HBASE-30335), which introduces the ServerManager.reportRegionOpen seed 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.

nirdosh.yadav and others added 9 commits August 28, 2026 15:11
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant