{{ message }}
Repository navigation
HBASE-30484 QuotaObserverChore may fail to retry enabling a table after an enable operation fails - #8791
Open
yurongfu wants to merge 1 commit into
Open
HBASE-30484 QuotaObserverChore may fail to retry enabling a table after an enable operation fails#8791yurongfu wants to merge 1 commit into
yurongfu wants to merge 1 commit into
Conversation
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: HBASE-30484
Description
A table space quota with the
DISABLEviolation policy disables the table whenits usage exceeds the limit. On the active Master,
QuotaObserverChoreperiodically recomputes each table's quota snapshot and transitions the table
between violation and observance.
For example, a table over its limit reaches state similar to:
When the operator raises the limit (or usage drops) so the table is no longer
in violation,
QuotaObserverChore.updateTableQuota()previously performed thetransition in this order:
Both the persisted snapshot (
transitionTable()writes tohbase:quota) andthe in-memory snapshot (
setCurrentState()) are advanced before the table isenabled.
QuotaUtil.enableTableIfNotEnabled()callsAdmin.enableTable(),which can fail before an enable procedure is submitted — for example, a
failing
preEnableTablecoprocessor hook or a failed quota-table read inHMaster.enableTable(). The exception propagates tochore(), which logs:Because both snapshots were already advanced, the failure leaves this state:
On the next chore run,
getTargetState()computes the same non-violatingsnapshot, so
currentSnapshot.equals(targetSnapshot)is true andupdateTableQuota()takes the no-op branch ("remains in observance ofquota"). The "Will retry" log message only means the chore runs again; the
failed table operation is never retained or retried. The table stays
DISABLEDindefinitely, and client operations fail:The insufficient-region-reports path in
_chore()has the same ordering: atable in "limbo" moving out of a
DISABLEviolation callssetCurrentState()beforeenableTableIfNotEnabled(), with the sameconsequence on failure. The disable direction (moving into violation) is
symmetric: a failed
disableTableIfNotDisabled()leaves the table enabledwhile both snapshots already record the violation.
Solution
Keep the write to
hbase:quotafirst —HMaster.enableTableconsults thepersisted snapshot, and
transitionTable()is a plain idempotentPut, sore-running it on retry is safe. Move the in-memory
setCurrentState()callafter the table enable/disable operation, to the end of the transition branch:
If
enableTableIfNotEnabled()/disableTableIfNotDisabled()throws, theIOExceptionpropagates beforesetCurrentState(), so the old in-memorysnapshot retains the pending transition. The next chore run sees
currentSnapshot != targetSnapshot, re-enters the branch, and retries thetable operation. The helpers already suppress
TableNotDisabledException,TableNotEnabledException, andTableNotFoundException, so a retry against atable that has since reached the requested state is a no-op.
The insufficient-region-reports path is reordered the same way:
transitionTable()→enableTableIfNotEnabled()→setCurrentState().updateNamespaceQuota()is unchanged: itssetCurrentState()is precededonly by idempotent snapshot writes, with no table enable/disable on that path.
This fix covers retries while the same Master retains its in-memory state.
Recovery across a Master restart in the window between the persisted snapshot
write and the enable operation would require a durable record of the pending
table-state change and is out of scope.
Tests
Added:
TestQuotaObserverChore.testRetryTableEnableOnFailureTestQuotaObserverChore.testRetryTableDisableOnFailureBoth are small mock-based unit tests (no mini-cluster). The enable test
performs this sequence:
QuotaObserverChorewith a mockedConnectionreturninga mocked
Admin, and a mockedSpaceQuotaSnapshotNotifier.initializeSnapshotStores()so the realTableQuotaSnapshotStoreiswired to the chore's in-memory snapshot map.
DISABLEpolicy andbuilds a non-violating target snapshot.
Admin.enableTable()to throw anIOExceptionon the first call andsucceed on the second.
updateTableQuota()and asserts it throwsIOException, that thein-memory snapshot is still the violating one, and that
enableTable()wasinvoked exactly once.
updateTableQuota()again with the same current/target snapshots(what the next chore run would compute) and asserts no exception, that
enableTable()was invoked a second time, and that the in-memory snapshothas advanced to the target.
testRetryTableDisableOnFailureis symmetric: it injects one failure fromAdmin.disableTable()while moving into aDISABLEviolation and verifiesthe disable is retried before the snapshot advances.
Without the fix, the assertion in step 5 fails: the in-memory snapshot has
already advanced to the non-violating target before the injected enable
failure, which is exactly the state that caused the production chore to skip
the retry and leave the table permanently disabled.
TestQuotaObserverChoreRegionReports(mini-cluster, covering theinsufficient-region-reports path) continues to pass with the reorder.