HBASE-30357 OpenRegionProcedure#restoreSucceedState ignores persisted transitionCode, forcing OPEN even after a real FAILED_OPEN - #8622
Conversation
2203eb4 to
e2cde5d
Compare
|
@virajjasani @apurtell @Apache9 Can you please help with review. |
… transitionCode, forcing OPEN even after a real FAILED_OPEN On master-failover restore, RegionRemoteProcedureBase.stateLoaded() calls restoreSucceedState() for any region whose remote-open report was already persisted with state REGION_REMOTE_PROCEDURE_REPORT_SUCCEED, but before this change OpenRegionProcedure ignored the real persisted TransitionCode and always forced the region to OPEN. If the RS had actually reported FAILED_OPEN and the master crashed before persisting that to hbase:meta, the region would come back as a "phantom" OPEN region on restart: no procedure watching it, no automatic retry, and invisible to the RegionInTransition tracker since OPEN is the only non-RIT terminal state. Widen RegionRemoteProcedureBase#restoreSucceedState to also receive the persisted TransitionCode, and make OpenRegionProcedure branch on it: on FAILED_OPEN, call AssignmentManager#regionFailedOpen(regionNode, false), mirroring what the live (non-restart) reportTransition path already does. This leaves the region non-OPEN so TransitRegionStateProcedure#confirmOpened sees it and drives the normal retry loop instead of finishing silently. CloseRegionProcedure's override is updated to match the new signature but ignores the parameter, since CLOSE has no failure variant. Add TestOpenRegionProcedureRestoreFailedOpen, which reproduces the restore timing directly (invoking the same package-private stateLoaded() hook that a real master restart triggers) and asserts the region is not left in OPEN state after a FAILED_OPEN report.
ded91c9 to
89a3dfb
Compare
|
@Apache9 can you please help in reviewing. |
virajjasani
left a comment
There was a problem hiding this comment.
Nice one, this is worth fixing
| if (transitionCode == TransitionCode.FAILED_OPEN) { | ||
| // will not persist to meta if giveUp is false, matches the live reportTransition path |
There was a problem hiding this comment.
Is FAILED_OPEN the only case where we have this problem? What about split/merge reverted states?
There was a problem hiding this comment.
Good question — I checked both cases:
CloseRegionProcedure: no analogous bug possible. CloseRegionProcedure.checkTransition only ever accepts CLOSED (CloseRegionProcedure.java:99-106) — there's no FAILED_CLOSE/revert code in the protocol at all (the RS aborts instead of reporting a close failure). So transitionCode is structurally guaranteed to be CLOSED whenever it's persisted for this procedure, and restoreSucceedState can't mis-restore something that only ever has one valid value.
Split/merge: these don't go through RegionRemoteProcedureBase/OpenRegionProcedure/CloseRegionProcedure/restoreSucceedState at all — they're driven by SplitTableRegionProcedure/MergeTableRegionsProcedure and reported via a separate switch in AssignmentManager.reportRegionStateTransition, which routes READY_TO_SPLIT/READY_TO_MERGE to their own handlers. Those handlers reject SPLIT/MERGED/SPLIT_REVERTED/MERGE_REVERTED outright — those codes predate AMv2 (from the 1.x ZK-less-assignment era, HBASE-11059) and exist only as a rolling-upgrade compatibility guard against an old (<2.0) RS still reporting the legacy codes. No current RS code ever sends them.
So FAILED_OPEN is the only live instance of this restore-path gap — nothing further to fix here.
| public class TestOpenRegionProcedureRestoreFailedOpen extends TestAssignmentManagerBase { | ||
|
|
||
| /** | ||
| * On the first open attempt, reports FAILED_OPEN and then immediately simulates a master restart |
There was a problem hiding this comment.
So how do we simulate the master restart here? I haven't seen any restart in the test.
There was a problem hiding this comment.
Good question. We don’t actually restart the master in this test.
The test simulates the relevant part of a master restart by directly invoking TransitRegionStateProcedure#stateLoaded(), which is the hook reached when the procedure state is loaded during master recovery.
The important sequence we want to reproduce is:
- RS reports FAILED_OPEN.
- The procedure state containing REPORT_SUCCEED + FAILED_OPEN is persisted.
- Master crashes before the child procedure gets a chance to continue and update the region state/meta.
- On recovery, stateLoaded() restores the procedure and calls restoreSucceedState().
The test holds the RegionStateNode lock after reporting FAILED_OPEN and invokes stateLoaded() directly, so the child procedure cannot continue normally. This puts us at the same point in the procedure lifecycle as the restore path after a master restart.
I can make the test/comment more explicit about this, e.g. rename the comment from “simulates a master restart” to “simulates procedure recovery after master restart”, to avoid suggesting that the test actually restarts the master.
What changes were proposed in this pull request?
OpenRegionProcedure#restoreSucceedState()is called on master-failover restore, onceper region, via
RegionRemoteProcedureBase#stateLoaded(). It unconditionally forced theregion into
OPEN, regardless of the actual persistedtransitionCode, which can beFAILED_OPEN. The method's signature only receivedseqId, nottransitionCode, so itwas structurally unable to branch on the real outcome.
Concretely: an RS reports
FAILED_OPEN; the master persistsstate=REPORT_SUCCEED, transitionCode=FAILED_OPENon theOpenRegionProcedure, but thein-memory
RegionState.Stateis left untouched (stillOPENING, sinceAssignmentManager#regionFailedOpen(regionNode, false)on the live path does not updateRegionState.State). If the master crashes/restarts before this reachesconfirmOpened()and before
hbase:metais updated,restoreSucceedState()sees the region isn'tOPENyet and force-transitions it to
OPENanyway, then persists that (false)OPENstate tohbase:meta. There is no rollback anywhere in this procedure chain (by design -forward-only), so nothing downstream can detect or correct it; the region silently looks
healthy in meta while no RegionServer is actually serving it.
By contrast,
CloseRegionProcedure#restoreSucceedState()is safe doing the equivalentunconditional force, because CLOSE has no failure-variant transition code at the master
side (
checkTransition/updateTransitionWithoutPersistingToMetaboth asserttransitionCode == CLOSED).This is not a regression - the logic is unchanged (modulo spotless formatting) since it
was introduced in HBASE-22365 (2019-05-10).
The fix
RegionRemoteProcedureBase#restoreSucceedState()to also receive the persistedtransitionCode, passed through fromstateLoaded().OpenRegionProcedure#restoreSucceedState()now branches:FAILED_OPENcallsAssignmentManager#regionFailedOpen(regionNode, false), mirroring exactly what the livereportTransition/updateTransitionWithoutPersistingToMetapath already does for thesame transition code; the existing
OPENEDhandling is unchanged.CloseRegionProcedure#restoreSucceedState()accepts the new parameter and ignores it,since CLOSE has no failure variant.
After the fix, a restore-time
FAILED_OPENputs the region back into the sameretryable path (
OPENING, cleared location) thatTransitRegionStateProcedure#confirmOpened()already drives on the live path - the region gets reassigned/retried through the normal
flow instead of being falsely marked
OPEN.Why are the changes needed?
To prevent a region from being silently, durably marked
OPENinhbase:metaafter amaster restart, when in reality no RegionServer opened it. Since HBase has no rollback
mechanism for these forward-only assignment procedures, this bug is otherwise
unrecoverable except by manual detection and intervention.
Does this PR introduce any user-facing change?
No.
Is there a corresponding Apache JIRA?
Yes: HBASE-30357
How was this patch tested?
Added
TestOpenRegionProcedureRestoreFailedOpen, extendingTestAssignmentManagerBase. Acustom mock RS executor reports
FAILED_OPENon the first open attempt, then - whileholding the
RegionStateNodelock, simulating the point right after a crash where thechild procedure has not yet resumed its own
execute()- directly invokes theTransitRegionStateProcedure#stateLoaded()hook that a real master restart would trigger,and records the region's state immediately after. Confirmed the test fails against
unmodified code with the exact predicted
OPENstate, and passes after the fix. Also ranTestAssignmentManager,TestTransitRegionStateProcedure,TestOpenRegionProcedureHang,TestOpenRegionProcedureBackoff,TestRollbackSCP, andTestSCPGetRegionsRacewith noregressions.