Reject --incremental combined with the bulk modes - #14200
Open
saintstack wants to merge 5 commits into
Open
saintstack wants to merge 5 commits into
saintstack wants to merge 5 commits into
Conversation
added 4 commits
September 24, 2026 20:46
BackupS3BlobBulkLoadRestoreMultiRange asserted "Data outside backup ranges is not affected", but BackupS3BlobCorrectness::_setup appended normalKeys to backupRanges and restoreRanges unconditionally, after the constructor had built the random ranges from backupRangesCount. With normalKeys in the list there is no data outside the backup ranges, so that assertion could not fail and backupRangesCount had no observable effect. Drop the append and scope the test to a single range, which is the widest coverage a bulkdump/bulkload job can express. Sentinel keys are planted outside the backup range and rewritten once the backup has stopped, so a restore that widens beyond the range reverts them and fails. The rewrite has to follow the backup: a value the backup captured would be restored to the value the check expects, hiding the widening. The other tomls using this workload all set backupRangesCount = -1 and already take normalKeys from the constructor branch, so the append was redundant for them. Renamed to BackupS3BlobBulkLoadRestoreKeyRange.toml: it no longer tests multiple ranges, and multi-range bulk backup is not expressible.
The bulk backup/restore integration ignored the caller's key ranges: createBulkDumpJob, createBulkLoadJob and the log-replay key-version map were all hardcoded to normalKeys. A range-scoped bulkdump therefore dumped the whole keyspace, and a bulkload restore wrote all of it back, overwriting keys the caller never named. restoreRanges was read and then never used. Pass the caller's range to all three sites. A bulk job carries exactly one key range and the cluster admits one bulk job at a time, so submitBackup and submitRestore reject any other range count rather than silently widening; supporting several ranges needs a change to the persisted bulk job metadata. The rejections are SevWarnAlways, not the SevError used by the neighbouring option-conflict checks: a simulation workload can legitimately submit a multi-range bulkdump, and SevError would fail the run for input that was correctly refused.
--incremental and the bulk modes make contradictory requests, and both sides resolved the contradiction silently and in opposite directions. On backup, StartFullBackupTaskFunc skips the entire snapshot block when incrementalBackupOnly is set, so --mode bulkdump --incremental produced a log-only backup with no SSTs. Nothing reported this; the operator learned of it only when a later bulkload restore aborted with "backup has no bulkdump data" and appeared to blame the restore. On restore, StartFullRestoreTaskFunc selects the bulkload branch from useRangeFileRestore alone, without consulting the logs-only flag, so --mode bulkload --incremental ingested the whole snapshot the caller had asked to skip. It then set onlyApplyMutationLogs itself, overwriting the caller's value so even status misreported the restore. Reject both pairs at submit time rather than picking a winner. A logs-only restore is --mode rangefile --incremental, which is unaffected. The remaining assignment in the bulkload branch is sequencing, not user intent: the SSTs carry the range data, so the dispatch that follows must replay logs only. With the new guard it can only ever overwrite false.
The guards added in the previous commit had no test: no toml combines --incremental with a bulk mode, so neither submit-time rejection was ever exercised. The opposite direction was already covered incidentally -- tests/fast/IncrementalBackup.toml proves a rangefile incremental backup is still accepted -- but nothing proved the rejections actually fire. Add an opt-in probe to the workload that attempts both rejected pairs and asserts the error: submitBackup with --incremental and snapshotMode 1 or 2 must throw backup_error, and submitRestore with --incremental and bulkload must throw restore_error. The restore probe runs after the real backup so the container is describable and a throw can only come from the guard. This lives in its own toml rather than in the key-range test. The backup probe creates a container as a side effect, and the key-range test is the regression gate for the --keys data-correctness bug; a side effect there would be paid for in the one place we can least afford it.
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.
The test failed 10/10 on Joshua with FailReason="ProducedErrors" and no failed assertion: both guards fired correctly and both probes passed, but the guards write their user-facing message to stderr, and TestHarness2 scores stderr at severity 40 by default. The test was failing on exactly the output it exists to provoke. Set StderrSeverity = 30, the mechanism seven existing tests already use for the same reason (FuzzApiCorrectness, WriteDuringRead and friends). Also pass the real lockUID to the restore probe instead of a fresh random one. The database is locked by then, so had the guard not fired the probe would have died in checkDatabaseLock with database_locked rather than reporting anything useful about the guard.
Contributor
Result of foundationdb-pr-clang-ide on Linux RHEL 9
|
Contributor
Result of foundationdb-pr-clang-arm on Linux RHEL 9
|
Contributor
Result of foundationdb-pr-clang on Linux RHEL 9
|
Contributor
Result of foundationdb-pr on Linux RHEL 9
|
saintstack
marked this pull request as ready for review
October 2, 2026 05:49
Contributor
Result of foundationdb-pr-cluster-tests on Linux RHEL 9
|
Contributor
Result of foundationdb-pr-macos-m1 on macOS 14.x
|
Contributor
Result of foundationdb-pr-macos on macOS 14.x
|
This was referenced Oct 2, 2026
This branch has not been deployed
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.
The bug
--incrementalmeans "logs only, no base snapshot" — one CLI flag setting bothincrementalBackupOnlyand
onlyApplyMutationLogs(fdbbackup/backup.cpp:3727-3730). The bulk modes exist to produce(bulkdump) and consume (bulkload) exactly the snapshot that
--incrementaldeclines, so thecombination is incoherent.
Both sides accepted it silently and resolved it in opposite directions:
Backup.
StartFullBackupTaskFuncskips the entire snapshot block whenincrementalBackupOnlyisset (
FileBackupAgent.cpp:4175), so--mode bulkdump --incrementalproduced a log-only backup withno SSTs. Nothing warned. The operator found out only when a later bulkload restore aborted with
"backup has no bulkdump data", which reads as a restore fault but is a backup-side omission hours
earlier.
Restore.
StartFullRestoreTaskFuncselects the bulkload branch fromuseRangeFileRestorealone(
:6824), without consulting the logs-only flag, so--mode bulkload --incrementalingested thewhole snapshot the caller asked to skip. It then set
onlyApplyMutationLogsitself (:6858),overwriting the caller's value so even
statusmisreported the restore.The change
Reject both pairs at submit time rather than picking a winner, matching the range-count guards added
in #14199. A logs-only restore is
--mode rangefile --incremental, which is unaffected.The remaining assignment at
:6858is sequencing, not user intent — the SSTs carry the range data, sothe dispatch that follows must replay logs only. With the guard in place it can only ever overwrite
false, and it is now commented as such.SevWarnAlwaysrather thanSevError, for the same reason as #14199: a simulation workload canlegitimately submit the rejected combination, and
SevErrorwould fail the run for correctly-refusedinput.
Test status
The second commit adds an opt-in probe (
expectBulkIncrementalRejection) and a dedicated toml thatattempts both rejected pairs and asserts the error codes. It is kept separate from
BackupS3BlobBulkLoadRestoreKeyRange.tomldeliberately: the backup probe creates a container as aside effect, and that test is the regression gate for the
--keysdata-correctness bug in #14199.It first failed 10/10 (
20261001-230113-stack-cf51b914e5414d23). The diagnosis, from the failurerecords:
FailReason="ProducedErrors", no failed assertion and noSeverity=40event, and bothguard messages present in
StdErrOutput. So both guards fired and both probes passed — TestHarness2simply scores anything on stderr at severity 40, and these guards write their user-facing message
there. The test was failing on precisely the output it exists to provoke.
Fixed in the third commit with
StderrSeverity = 30, the mechanism seven existing tests already usefor the same reason (
FuzzApiCorrectness,WriteDuringReadand friends). That commit also passes thereal
lockUIDto the restore probe rather than a fresh random one; the database is locked by then, sohad the guard not fired, the probe would have died in
checkDatabaseLockwithdatabase_lockedinstead of reporting anything useful.
Verified locally that the backup guard fires as intended
(
BS3BCW_IncrementalBackupRejection … ErrorCode="2300" Accepted="0") and that the toml option reachesthe run (
StderrSeverity … NewSeverity="30"). A full run of this toml cannot be done on Apple Silicon—
WITH_ROCKSDB=OFFis mandatory there and makesnewRocksDBSstFileWriter()returnnullptr, whichwriteKVSToSSTFiledereferences — so the end-to-end confirmation is a Joshua rerun.Resolved: the restore-side guard is reachable
An earlier version of this description flagged a risk that the restore guard might be unreachable,
since
restore()callsgetRestoreSet(..., onlyApplyMutationLogs, ...)beforesubmitRestoreandthrows
restore_invalid_versionwhen the set is absent. That concern is settled and was wrong:the failing run's own output contains the restore guard's message, so
submitRestoreis reached andthe guard fires. Both halves of the first commit are confirmed working.
Commit note
The three commits here are kept separate on purpose: the guards, their coverage, and the harness fix
for that coverage. The third exists only because the second was wrong about how stderr is scored, and
squashing it would hide that the guards were never at fault.