[ 
https://issues.apache.org/jira/browse/HBASE-30335?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated HBASE-30335:
-----------------------------------
    Labels: pull-request-available  (was: )

> Master's flushedSequenceIdByRegion is not seeded with openSeqNum on region 
> OPEN, allowing WAL split to write stale recovered.edits for edits already 
> durable in HFiles
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HBASE-30335
>                 URL: https://issues.apache.org/jira/browse/HBASE-30335
>             Project: HBase
>          Issue Type: Bug
>          Components: master
>    Affects Versions: 2.6.1, 2.5.10
>            Reporter: Nirdosh Kumar Yadav
>            Assignee: Nirdosh Kumar Yadav
>            Priority: Minor
>              Labels: pull-request-available
>
> h3. Problem Statement
> When a region is drain-moved from RS-A to RS-B and RS-A crashes shortly 
> afterwards while its WALs still contain (already-durable) edits for that 
> region, the subsequent ServerCrashProcedure WAL split writes a stale 
> recovered.edits/<seqId> file under RS-B's already-online region directory. 
> The file contains edits that are already in HFiles, but downstream guards 
> treat any recovered.edits/<numeric> file as an unreplayed durability risk. 
> The next MergeTableRegionsProcedure (or SplitTableRegionProcedure) on that 
> region trips AssignmentManagerUtil.checkClosedRegion() with "Recovered.edits 
> are found in Region: … abort split/merge to prevent data loss" and leaves the 
> region stuck in RIT until an operator intervenes.
> h3. Root Cause
> WALSplitter decides whether to write an edit to recovered.edits/ by comparing 
> the edit's sequenceId against the region's lastFlushedSequenceId returned by 
> the master (WALSplitter.java:358-360). The master's 
> ServerManager.flushedSequenceIdByRegion map is populated only by RegionServer 
> heartbeats (ServerManager.java:207-223). During a drain-move:
>  * The source RS drops the region from onlineRegions after CLOSE and stops 
> heartbeating for it.
>  * The destination RS has just OPENed the region and has no new writes to 
> flush, so it has nothing to report.
>  * No code path refreshes the master's cache from the OPEN transition.
> Consequently, when WALSplitter later asks the master for the region's 
> lastFlushedSequenceId, it gets NO_SEQNUM and falls back to -1L at 
> WALSplitter.java:358-360:
> {code:java}
> if (lastFlushedSequenceId == null) { lastFlushedSequenceId = -1L; // Treat as 
> "never flushed" — write everything. } {code}
> Every edit in the crashed server's WAL for that region is now above the 
> fallback watermark and gets written to recovered.edits/, even though the 
> region's on-disk .seqid marker already proves those edits are durable.
> The single master-side transition point to State.OPEN is 
> AssignmentManager.regionOpenedWithoutPersistingToMeta() 
> (AssignmentManager.java:2150), where regionNode.getOpenSeqNum() is already 
> available from the RS's OPEN transition report. That value is greater than 
> every durable seqId at the previous CLOSE and is exactly the watermark 
> WALSplitter needs — but it is never propagated to 
> ServerManager.flushedSequenceIdByRegion.
> h3. Key Observations 
>  * Region 112d9f08181e80aaae01e68c9229c3cb was gracefully drained from rs-132 
> → rs-81 at 16:36:34.001 UTC; rs-81's openSeqNum=4997750282. The close on 
> rs-132 had already flushed all edits.
>  * ~25 seconds later rs-132 was declared dead as part of a broader RS drain.
>  * WAL-split worker rs-34 wrote recovered.edits/0000000004997750280 at 
> 16:37:12.712 UTC — 38.7 seconds after rs-81 was live on the region. The 
> edit's seqId=4997750280 was already durable in HFiles (durable watermark ≥ 
> 4997750281).
>  * The orphan sat dormant for 1h 55m until MergeTableRegionsProcedure 
> pid=46990253 tripped checkClosedRegion at 18:32:33.293 UTC and the region 
> stayed in RIT for 48m 56s.
>  * When rs-120 eventually replayed the file after operator intervention, 
> HBase logged Applied 0, skipped 1 — direct proof that the edit was already 
> durable and the guard was a false-positive.
> h3. Proposed Solution
> Seed the master's flushed-sequence cache from the OPEN transition. In 
> AssignmentManager.java:2150:
> {code:java}
> void regionOpenedWithoutPersistingToMeta(RegionStateNode regionNode) throws 
> IOException { regionNode.transitionState(State.OPEN, 
> STATES_EXPECTED_ON_OPEN); RegionInfo regionInfo = regionNode.getRegionInfo(); 
> regionStates.addRegionToServer(regionNode); 
> regionStates.removeFromFailedOpen(regionInfo); + 
> master.getServerManager().reportRegionOpen(regionInfo, 
> regionNode.getOpenSeqNum()); } {code}
> Companion in ServerManager.java (adjacent to updateLastFlushedSequenceIds at 
> line 207):
>  
> {code:java}
> public void reportRegionOpen(RegionInfo regionInfo, long openSeqNum) { if 
> (openSeqNum == HConstants.NO_SEQNUM) return; byte[] encodedRegionName = 
> Bytes.toBytes(regionInfo.getEncodedName()); // openSeqNum > every durable 
> seqId at CLOSE time — preserves the // "keep the max" invariant that 
> updateLastFlushedSequenceIds already uses. 
> flushedSequenceIdByRegion.merge(encodedRegionName, openSeqNum, Math::max); } 
> {code}
> Effect. At OPEN time the master learns the correct durable watermark 
> synchronously with the state transition. When WALSplitter later queries 
> ids.getLastFlushedSequenceId(), it gets openSeqNum instead of NO_SEQNUM; the 
> skip check openSeqNum >= edit.seqId evaluates true for every already-durable 
> edit, and no recovered.edits/* file is written. The 48-min RIT class of 
> failure is eliminated at its source — no relaxation of the downstream 
> recovered.edits guard is required. 
>  
> {code:java}
> if (lastFlushedSequenceId == null || lastFlushedSequenceId == 
> HConstants.NO_SEQNUM) { Path regionDir = FSUtils.getRegionDirFromTableDir( 
> CommonFSUtils.getTableDir(walRootDir, entry.getKey().getTableName()), 
> encodedRegionNameAsStr); long onDiskMaxSeqId = 
> WALSplitUtil.getMaxRegionSequenceId(walFS, regionDir); lastFlushedSequenceId 
> = (onDiskMaxSeqId != HConstants.NO_SEQNUM) ? onDiskMaxSeqId : -1L; } {code}
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to