Skip to content

Fix incremental restore to survive SchemeShard reboots and handle shard failures#36

Open
maximyurchuk wants to merge 28 commits into
main-for-35663from
pr-35663
Open

Fix incremental restore to survive SchemeShard reboots and handle shard failures#36
maximyurchuk wants to merge 28 commits into
main-for-35663from
pr-35663

Conversation

@maximyurchuk

Copy link
Copy Markdown
Owner

Fixes ydb-platform#22274
Related: ydb-platform#21487, ydb-platform#31068

Changelog entry

...

Changelog category

  • Not for changelog (changelog entry is not required)

Description for reviewers

...

Code review of the prior commits in this PR surfaced two correctness
defects in the shard-failure retry path plus several cleanup items.

Correctness fixes:

1. RetryNeeded overwrite race in CheckForCompletedOperations. Two
   TTxProgressIncrementalRestore runs can interleave: the first erases
   the failure flag from FailedIncrementalRestoreOperations and sets
   RetryNeeded=true; if a second run arrives before the retry branch
   executes, it sees no failures and overwrites RetryNeeded back to
   false, silently losing the retry signal. Changed assignment to |=.

2. Retry budget. The retry loop had no terminal condition — a
   permanently broken shard caused infinite scan re-creation. Added
   per-incremental retry counter (transient, reset on advance) capped
   at 5 attempts. On exhaustion, state is set to EState::Failed and
   persisted; get/list APIs report PROGRESS_DONE with GENERIC_ERROR.

Cleanups:

- Removed dead 1-arg TEvRunIncrementalRestore constructor (zero
  callers after the orphan-recovery fix in this PR).
- Extracted TrackIncrementalRestoreShardReply helper to deduplicate
  the ~26-line shard-reply tracking block that was identical between
  TProposeAtTable::HandleReply and NIncrRestore::TProposedWaitParts::HandleReply.

Test coverage:

- Added IncrementalRestoreShardFailureTriggersRetry: uses
  runtime.AddObserver to inject one TEvFinished failure and verifies
  the retry path recovers data integrity.
- Added TestIncrementalRestoreFinalizingStateRecoveryAfterReboot:
  exercises TTxInit resume from EState::Finalizing via a 5-incremental
  chain and bucket reboots.
- Hardened WaitForRestoreDone with required bool expectRegistered
  parameter — prior version silently passed when ListRestores returned
  empty entries, masking real registration regressions.
Add MaxIncrementalRestoreTablesInFlight ImmediateControl (default 32,
sentinel -1 = unbounded). The orchestrator now enqueues table and index
sub-ops onto PendingTables and DispatchPendingTables drains in waves
bounded by the cap, topped up after each completion. Prevents unbounded
fan-out on backup collections with many tables/indexes.
Funnel TrackIncrementalRestoreShardReply through TTableOperationState::
RecordShardResult so the first terminal report wins and re-deliveries
(DataShard restart replays, schemeshard reboot re-sends, interconnect
retransmits) are dropped. Prevents double-counting that would put a
shardIdx in both Completed and Failed sets and break AllShardsComplete's
sum invariant. Adds a contract unit test.
Line numbers in cross-file comments rot as soon as either file is edited.
The adjacent code is self-explanatory.
Add MaxIncrementalRestoreRetriesPerIncremental ImmediateControl (default
50, sentinel -1 = unbounded) to bound the orchestrator's per-incremental
retry attempts. Propagate a Retriable bit on TShardOpResult / TEvFinished
so non-retriable scan failures (anything other than Done/Term) short-
circuit the retry loop instead of burning the budget. Failed restores
now report GENERIC_ERROR via the list endpoint instead of the SUCCESS
default. Adds integration tests for backoff timing, budget enforcement,
unlimited-cap sentinel, non-retriable short-circuit, reboot survival,
and concurrent-completion de-duplication. Bumps reboot suite SPLIT_FACTOR
to 50 and adds explicit ram/cpu requirements.
Extract the per-entry TBackupCollectionRestore progress/status switch
into FillRestoreProgress in schemeshard_restore_incremental_progress.h
and call it from both __get and __list, so the two endpoints can't
diverge on Failed -> GENERIC_ERROR or progress percent.

Trim Retriable bit comments down to one-liners.
Move the ad-hoc "_full" / "_incremental" string literals scattered across
backup, restore, and init paths into FullBackupSuffix / IncrementalBackupSuffix
constants plus FullBackupDirName / IncrementalBackupDirName builders in
schemeshard__backup_collection_common.h. Eliminates the risk of one site
drifting from another when the convention ever changes.
…odies

Pull out RestoreIncrementalRestoreOpPathStates and ScheduleOrphanedIncrementalRestoreOp
from TTxInit::ReadEverything, HandleRetryPath and HandleAllOperationsComplete
from TTxProgressIncrementalRestore::Execute, ValidateProposePaths from
TNewRestoreFromAtTable::Propose, and TrackSubOpAndExpectedShards reused by
both CreateSingleTableRestoreOperation and CreateSingleIndexRestoreOperation.
Also tighten the reboot-tests file. Pure refactor — no behavior change.

The new TTxInit helper keeps using NBackup::FullBackupDirName /
IncrementalBackupDirName so the suffix centralization from 1102753 stays.
Pure comment cleanup across PR-touched files: collapse multi-paragraph
block comments to one or two short sentences, drop comments that just
paraphrase the next line of code, and move trailing end-of-line comments
to the line above. No code or behavior changes.
The InvolvedShards set was populated but never read — cleanup paths key
by OperationId. Remove the in-memory set, the TTxInit reload block, and
the per-sub-op insert; reserve proto tag 7 in TLongIncrementalRestoreOp
(formerly InvolvedShards) to prevent reuse.
Persist EState::Completed/Failed (with FinalStatus + FinalIssues) into
Schema::IncrementalRestoreState instead of deleting the row at finalize,
so Get/List can return the orchestrator's terminal verdict across reboots.
Add FinalizeTxId column so TTxInit can detect a Finalizing op whose finalize
sub-op was lost on reboot and reset to Running for re-issue.

Route full-only restores (no incrementals) through the same orchestrator
so they get a state row and are pollable like incremental restores.

Unify Get and List status semantics: outer envelope is always SUCCESS;
per-entry inner Status carries the real outcome. FillRestoreProgress now
layers the persisted FinalStatus over the SUCCESS default for Completed
and over GENERIC_ERROR for Failed.

Allow FORGET on Failed restores in addition to Completed/Finalizing.

Tests: expand ut_backup_collection (663 lines) and ut_incr_restore_reboots
(304 lines) covering durable Completed/Failed across reboot, Finalizing
recovery, full-only restore reboot, and FORGET semantics for terminals.
Comment pass: remove dev-process markers (Bug #N, RED on HEAD, T1.x
taxonomy) and trim wordy block comments across the restore files.

Small follow-ons that surfaced during the pass:
- TTxInit: skip RestoreIncrementalRestoreOpPathStates when the persisted
  state is already Completed or Failed (no point resetting path states
  for a terminal row).
- TTxProgressIncrementalRestore: clear TableOperations along with the
  rest of the per-incremental tracking on retry.
- __get: skip Reply() and SideEffects.Send directly so the per-entry
  Status set by FillRestoreProgress isn't clobbered by the default
  SUCCESS Reply path.
- Drop unused MaxRetriesPerIncremental constant.
Drop the header-only-helpers framing block and the inline comment in
IsScanRetriable; the function body is self-evident. Drop the
end-of-line "meaningful only when !Success" on Retriable too.
Disambiguates the helper name now that the backup side will get its
own analogous terminal-state persister.
…ExpectedShards

Same disambiguation as the previous rename — the backup side will get
its own analogous tracker.
Rename DispatchPendingTables, EnqueueIndexesRecursive, and
FindTargetTablePath to their PrefixedIncrementalRestore variants so
the backup side can grow analogous helpers without name collisions.
Same paraphrase in datashard_active_transaction.h and tx_datashard.proto;
the field name plus its sibling Success/Error fields make the meaning obvious.
Move WaitForIncrementalBackupDone and related polling/observer helpers
out of ut_incr_restore_reboots and into a new test_incremental_restore.h
under ut_helpers, shared with ut_incremental_restore. Lets the backup
tracking branch reuse the same primitives instead of forking them.
Reflects the FinalStatus, FinalIssues, and FinalizeTxId columns added
in the durable-terminal-state work.
Switch the wire-level scan-termination signal from a single Retriable
bool to a typed EOpEndStatus enum (END_UNSPECIFIED, END_SUCCESS,
END_TRANSIENT_FAILURE, END_FATAL_FAILURE). DataShard classifies the
cause via MapScanStatus; SchemeShard owns the retry policy via the
new ShouldRetryIncrementalRestore helper in schemeshard_incremental_-
restore_classify.h. Avoids the boolean overload where "is this
retriable?" had to encode the difference between transient and fatal
in one bit.

Touches: TShardOpResult proto, TEvFinished, TSchemaOperation,
TDataShard::NotifySchemeshard, the SS-side dispatcher and reboot
tests, plus the schema canondata picking up the new state columns.
Drop "Step E:" prefix and the multi-paragraph paraphrase of the reload
loop. Trim the function-doc blocks for EnqueueIncrementalRestoreItem
and CleanupIncrementalRestoreItems to one short line each. Tighten the
Finalizing-recovery rationale comment.
Each multi-line "Plan v4 T-Rn:" / "Test N (integration ...):" header
becomes a single sentence describing what the test asserts. Also drop
the TxProgressNotExecutedForFullBackupOnly tombstone block — git
history covers that.
Drop the EOL note next to reserved 7 in TLongIncrementalRestoreOp and
the 3-line Column<7> placeholder block in schemeshard_schema.h. The
proto still reserves tag 7; schemeshard_schema.h doesn't need a
matching //-comment.
FindPtr returns nullptr when the entry is absent, which is exactly
what the second branch passed explicitly. Collapse the if/else into
the single call.
Collapse the multi-line blocks at the retry path, finalize transition,
per-item cleanup, dispatch cap, CleanupIncrementalRestoreItems body,
and the AllocateResult tx into one or two short sentences each. Drop
the inline doc that paraphrased TTxProgressIncrementalRestoreAllocate-
Result's behavior.
The "tier" naming came from the design plan; in code, the distinction
is just "per-incremental retry budget" vs "allocator retry". Use those
terms directly so future readers don't need the spec context.
Drop redundant doc on BackupCollectionPathId / OriginalOperationId
(the names speak for themselves), collapse the multi-paragraph TItem /
PendingRequest preamble, and strip the trailing EOL note on WaitTxId.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants