Repository navigation
Forward port #14057: fix backup-epoch livelock that strands old TLog generations - #14087
saintstack wants to merge 3 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Looks good. I'll let @neethuhaneesha have a look too since it's related to backup worker. |
A backup worker displaced before its first upload leaves its epoch with no progress record, so monitorBackupProgress retries forever and the epoch permanently pins oldestBackupEpoch. That blocks generation purging, and because finalUpdate requires an empty oldTLogData, one stranded generation keeps the cluster out of FULLY_RECOVERED. Also trace the three points where this stalls silently. Forward port of apple#14057 (release-7.4).
fc16c6e to
dc577d5
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
neethuhaneesha
left a comment
There was a problem hiding this comment.
In the description it was mentioned "monitorBackupProgress then retries forever, so the epoch never completes and permanently pins oldestBackupEpoch',
But looks like this will not runs for old epochs.
I can understand the some old generation are not able to purge because there is no error monitoring control. But, I quite didn't get how the fix is help in purging old generations. We can discuss offline.
Four comments overstated or misattributed what they annotated. monitorBackupProgress watches recruitedEpoch, the current epoch, and only publishes latestBackupWorkerSavedVersion. It cannot pin oldestBackupEpoch; that is setBackupWorkers and removeBackupWorker. Its missing-tag condition is a symptom of the same displacement that strands an old epoch, so say that instead of claiming this retry is the wedge. The seed's comment claimed the written value is always startVersion - 1 and advertises no data, which reads as an argument that the write is inert. onBackupChanges runs before uploadData is launched, so an old-epoch worker has already raised savedVersion. State the part that makes presence matter at all: a tag with no record is re-recruited unconditionally, while one at or past epochEnd - 1 retires the epoch, and the per-(epoch, tag) max merge is what lets rounds accumulate. toCoreState's retain branch tests only storageRecovered and remoteLogPrefixRecovered. The backup-worker gate is in the incremental purge path, not here. The recoverAt bail-out comment asserted it freezes recoveredVersion and so prevents purging. That was disproven: with RECORD_RECOVER_AT_IN_CSTATE on, the bail-out stopped firing and retention was unchanged. Record the measured result so the claim is not chased again.
Result of foundationdb-pr-clang-ide on Linux RHEL 9
|
Result of foundationdb-pr-macos-m1 on macOS 14.x
|
Result of foundationdb-pr-clang-arm on Linux RHEL 9
|
Result of foundationdb-pr-clang on Linux RHEL 9
|
Result of foundationdb-pr on Linux RHEL 9
|
Result of foundationdb-pr-macos on macOS 14.x
|
Thanks for the review @neethuhaneesha. You are right. monitorBackupProgress only looks at current. The PR description needs to do better job describing the problem:
I just updated the comments in the PR too to be clearer on what is going on. Here are line numbers to help review.... A tag whose record reaches epochEnd - 1 is dropped from the recruit set (epochEnd is endVersion in the below method) from BackupProgress: A tag not in 'progress' -- has no progress -- makes it out of BackupProgress::updateTagVersions and then gets unconditionally re-added at the epoch's begin version later in BackupProgress::getUnfinishedBackup (:137-138). An epoch with no record can never be retired by recovery — only by a worker that survives long enough to write one. But in this test we are trapped in recoveries that are triggering every 9.6 seconds... Backup worker can't land an update before 14 seconds have passed (the 4second pause at start then after the first 10second update cadence elapses). This fix updates progress record once for the worker when uploadData starts, before the loop's first delay. shouldBackupWorkerExitEarly runs onBackupChanges before uploadData is launched. For an old-epoch worker that has saved nothing yet that advances savedVersion to minVersion, the lowest version any active backup still needs (BackupWorker.cpp:409-415). Now the tag has a version. The erase quoted above ' 57 auto n = tags->erase(tag);' now works. See what you think. |
Result of foundationdb-pr-cluster-tests on Linux RHEL 9
|
Forward port of #14057 (release-7.4), a nightlies BackupOldAndNewRestore failure. Same change, translated for main's layout (
backupworker/BackupWorker.cpp,logrouter/LogRouter.cpp,logsystem/LogSystem.cpp) and native coroutines. Main has all three gaps unchanged: the LogRouter counter bug atLogRouter.cpp:430, no progress seed inuploadData, and a silent return inmonitorBackupProgress.Here is the 7.4 branch commit message:
A backup worker displaced before its first upload cycle leaves its epoch with
no progress record. monitorBackupProgress then retries forever, so the epoch
never completes and permanently pins oldestBackupEpoch. That blocks
generation purging, and since finalUpdate requires an empty oldTLogData, a
single stranded generation keeps the cluster out of FULLY_RECOVERED. In
simulation the test simply never finishes.
Fix: seed a progress record when uploadData starts. The seeded value is
savedVersion (= startVersion - 1), so it advertises no data.
Also included: a SevWarnAlways in LogRouter that fired on every successful
peek (noPrimaryPeekLocation = present ? 0 : ++n; if (!(n % 4)) — 0 % 4 == 0),
plus traces at the three points where this wedge was previously silent.
Not fixed: recoverAt is still not persisted to cstate, which is a second,
independent prerequisite for old-generation tracking. Enabling
RECORD_RECOVER_AT_IN_CSTATE activates an ordering assertion at
TagPartitionedLogSystem.actor.cpp:358 -- newState.oldTLogData[j].recoverAt < oldestGenerationRecoverAtVersion won't. -- that has never executed, and it fails
immediately (KillRegionCycle.toml seed 90374059). Will address separately — the
wedge remains reachable; this change only reduces how often it is entered.
Review note: saveProgress does not check stopped, so a worker displaced
inside this first commit window now blocks teardown on a commit that needs the
new recovery to complete. Previously it exited locally. Deliberately not coded
around — the alternatives either change saveProgress for all callers or drop
the record this fix exists to write.
20260918-021822-stack_forward_port-3f64643f10834290 compressed=True data_size=39591687 duration=4300037 ended=100000 fail_fast=10 max_runs=100000 pass=100000 priority=100 remaining=0 runtime=0:28:21 sanity=False started=100000 stopped=20260918-024643 submitted=20260918-021822 timeout=5400 username=stack_forward_port