Repository navigation
Add RocksDB blob_dir patch so blob files can live on a separate volume - #13
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a downstream patch to RocksDB that adds the blob_dir option to AdvancedColumnFamilyOptions, allowing blob files to be stored on a different volume than SST files. Feedback on the patch includes preventing duplicate paths in the directory creation list to avoid disabling NoSpace() recovery, ensuring the AdvancedColumnFamilyOptions constructor copies the new field, and using std::move for safer smart pointer ownership transfer.
|
Is this something we can PR with the RocksDB repo so we don't have to maintain this patch? |
Yes, I believe so, it seems like a very reasonable PR to submit. |
|
@cb1kenobi Should I go ahead and merge this, per the discussion above? |
Adds AdvancedColumnFamilyOptions::blob_dir so blob files can be placed on a different volume than the SST files. db_paths/cf_paths distribute SST files by level, but every blob file path is derived from cf_paths.front(), so large values cannot be tiered at all without this. That makes it impossible to keep the LSM tree (and all its compaction write traffic) on fast local storage while large values live on cheaper attached storage. All blob path derivation now goes through a single ImmutableCFOptions:: GetBlobDir() accessor, so the option and its cf_paths.front() default cannot drift apart. That includes the two places a first pass missed: a blob directory outside cf_paths has no handle in Directories, so its directory entries were never fsynced after flush or compaction (a crash could leave a durable SST referencing a blob file with no directory entry), and the common two-argument DestroyDB overload never collected blob_dir because collection lived only in the column_families loop. blob_dir is the last field of AdvancedColumnFamilyOptions on purpose: inserting mid-struct shifts every following field's offset and silently mismatches code compiled against stock headers. ROCKSDB_HAS_CF_BLOB_DIR lets downstream code feature-detect and still compile against an unpatched RocksDB. Authored against v11.8.1, the version rocksdb-js pins. 11.8.1's new direct-write blob partition manager was an additional path-derivation site, so the header carries a note to re-audit them on every version bump. Verified: applies cleanly to a fresh v11.8.1 tarball with patch -p1, builds clean, and the resulting library passes @harperfast/rocksdb-js's blob_dir tests — blobs written to a separate volume, read back across reopen, relocated, and applied to named column families. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Running the upstream suite against the patched tree (which I had skipped) turned up both: - ColumnFamilyData::GetDbPaths() called GetBlobDir() unconditionally, whose assert fires when cf_paths is empty. Unit tests build ImmutableCFOptions that never went through SanitizeCfOptions, so memtable_list_test aborted. It now reads the raw blob_dir field: an unset one resolves to cf_paths.front(), which the loop above already covers. - The options_settable_test exclusion entry was out of offset order. FillWithSpecialChar walks that list sequentially and computes pair.first - offset, so a misplaced entry underflows the length and the memset clobbers the very field the entry exists to protect — turning a pre-existing assertion failure on this toolchain into a segfault. blob_dir sits at 664, after blob_direct_write_partition_strategy at 616. memtable_list_test now passes. options_settable_test still fails, but identically to a pristine v11.8.1 tree on this toolchain (unset_bytes_base 102 vs 126, a GCC 15 padding artifact) — verified by building the same test from an unpatched tarball. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy blob_dir through AdvancedColumnFamilyOptions, avoid duplicate open paths, and transfer the blob directory handle without a raw-pointer round trip. Add a focused constructor regression test. Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Drop a whitespace-only advanced_options.h hunk so future -F0 rebases have one fewer anchor to maintain. Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Review asked us to reject backup/checkpoint while blob_dir is non-empty, because a backup that flattens blob files into the DB directory restores into a database that cannot read its large values. The flattening half of that does not happen: CreateCustomCheckpoint -- the shared implementation behind both Checkpoint and BackupEngine -- already refuses any DB whose non-WAL files span more than one directory, and reporting the real blob directory from GetLiveFilesStorageInfo is what puts a tiered DB in that category. But it only puts it there once a blob file exists to report, and the gap is worse than the flattening would have been. Backing up a tiered DB that has not written a blob file yet succeeds. The copied OPTIONS file carries blob_dir, so restoring that backup elsewhere and opening it produces a second DB pointed at the *source's* blob directory -- and its obsolete-file scan deletes the source's live blob files. Measured on a patched v11.8.1 build: source blob files 1 before, 0 after opening the restore, and the source DB then fails its own read with "No such file or directory". So the rejection has to key on the option, not on the files reported. CreateCustomCheckpoint now also asks DBImpl::HasBlobDirSet(), which is true for any non-empty blob_dir rather than only one outside the DB directory: blob_dir is an absolute path, so a copy of the DB opened anywhere resolves its blob files back to the original directory whatever that directory is. This is upstream's hazard, not one blob_dir introduces -- the same sequence with cf_paths pointing at an external directory corrupts the source DB on stock v11.8.1, verified the same way. It is closed here because blob_dir is the option this patch owns, and because a tiered DB spends its early life blob-free, which is exactly the window that backs up cleanly and restores into a landmine. Also: the file-spanning message now names blob_dir, so someone who set only blob_dir does not read "db_paths / cf_paths not supported" as a bug, and the option's doc comment states the restriction. Verified with blob_dir unset: checkpoint, backup and restore unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
Co-Authored-By: GPT-5 Codex <noreply@openai.com>
The `cf_paths[0].path` half of the audit regex could never match: `cf_paths\.` requires a literal dot, which `cf_paths[0]` does not have. Moving the dot inside the alternation surfaces seven previously invisible call sites, all of which derive SST paths rather than blob paths — recorded in the inventory so a future one that does derive a blob path shows up as a diff. AtomicFlushMemTablesToOutputFiles enters its output-directory sync block on `s.IsShutdownInProgress()`, a state reachable from SyncClosedWals before the PickMemTable loop runs. FlushJob::edit_ is null until PickMemTable assigns it, so the blob-directory addition dereferenced null on a shutdown flush. Guard on `pick_status[i]`, the same condition the surrounding cancel loop already uses. The PR gate resolved `releases/latest` at run time while the inventories pin upstream line numbers, so an upstream release would have failed every open pull request. It now applies and tests the release in `patched-version.txt`; the nightly audit still runs against the latest release, which is where drift belongs. `options_settable_test` joins the gate's test set — it is the upstream test that covers the patch's ColumnFamilyOptions registration. Verified on the patched v11.8.1 build: the blob_dir cases in db_basic_test (4), db_flush_test (1), checkpoint_test (3), and backup_engine_test (1) all pass. `options_settable_test.ColumnFamilyOptionsAllFieldsSettable` fails on GCC 15 with the identical numbers (unset_bytes_base 102 vs 126) on a pristine v11.8.1 tree, so the patch neither causes nor masks it. The regenerated patch applies to a fresh tarball at `patch -p1 -F0` and `git apply --check`, and reproduces all 28 patched files byte-for-byte against the tree that was built and tested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TPPxptQi3DTZk7UPDFsFQ
Guarding the call site on pick_status[i] was not enough. pick_status[i] records that PickMemTable() ran, not that it picked anything: PickMemtablesToFlush() returns an empty set when every not-yet-flushed memtable is already claimed by an overlapping flush or sits above the request's max_memtable_id, and PickMemTable() then returns before assigning edit_. Run() accepts that state and returns OK, so the atomic-flush sync block is reached with pick_status[i] true and edit_ null. edit_ is null exactly when nothing was picked, and a job that picked nothing wrote no blob files, so the accessor answers false rather than making every call site prove it picked memtables. The call-site guard goes away with it. Also trims the added comments down to the invariants they carry — the Env path registration reading the raw field, the data_dirs_ gap that blob_dir_ fills, the NoSpace() interaction — dropping the sentences that narrate the code beside them. Verified on the patched v11.8.1 build: db_basic_test blob_dir cases 4/4, db_flush_test 1/1, checkpoint_test 42/42, backup_engine_test 108/108, options_test 79/79. The regenerated patch applies at patch -p1 -F0 and git apply --check and reproduces all 28 patched files byte-for-byte against the tree that was built and tested; both inventories are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TPPxptQi3DTZk7UPDFsFQ
The validate job downloads a RocksDB release, applies every patch and runs upstream test binaries; on a 90-minute timeout that is not something to spend on a pull request that cannot change the result. Restrict the pull_request trigger to the overlay directory and this workflow, the same `paths` idiom validate-experimental-patches.yml already uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TPPxptQi3DTZk7UPDFsFQ
28e4696 to
729c24a
Compare
Relocates the blob_dir patch and its support files out of the official vcpkg
overlay and into the opt-in experimental-patches/ pipeline, so it is no longer
applied to every build.
vcpkg-overlays/rocksdb/patches/0002-cf-blob-dir.patch
vcpkg-overlays/rocksdb/{blob-path,cf-blob-path}-inventory.txt
vcpkg-overlays/rocksdb/verify-blob-path-inventory.sh
vcpkg-overlays/rocksdb/patched-version.txt
-> experimental-patches/0001-cf-blob-dir/
The inventories, the verify script and the pinned version are artifacts of this
one patch -- they encode line numbers of the patched tree -- so they belong with
it rather than in the overlay, which holds vcpkg's own MIT-licensed port files.
verify-blob-path-inventory.sh already resolves the inventories relative to
itself, so it moves unedited. patched-version.txt also becomes per-patch, which
it has to be: as a single repo-global file it could only ever describe one
downstream patch.
build.yml previously globbed the official patches directory for both the PR gate
and a nightly audit, and the nightly audit gated the release build. After the
move that glob no longer contains this patch, so the nightly would have failed
outright on the audit's ROCKSDB_HAS_CF_BLOB_DIR check. Both jobs are reworked:
- validate (PR): iterates experimental-patches/*/, reads each patch's
patched-version.txt, applies the official patches then that patch at -F0, and
runs the patch's own optional audit.sh and test.sh hooks.
- audit (nightly): checks each experimental patch against the release being
built and reports drift as a warning. It no longer appears in build's needs
and is continue-on-error, because an experimental patch is not in the nightly
release and must not be able to fail one -- that coupling is what moving these
patches out of the overlay was meant to remove.
The hooks keep build.yml free of patch-specific knowledge: the inventory audit
and the focused upstream test list now live in the patch's own audit.sh/test.sh
instead of being spelled out in the workflow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cross-model review (codex + gemini + cursor-composer + harper-domain) found a major regression in the previous commit. Removing `audit` from build's `needs` *and* marking it continue-on-error left the inventory audit gating nothing. Dispatching `experimental_patches: 1` against a newer tag would run the audit, see verify-blob-path-inventory.sh fail on a new upstream BlobFileName( site deriving from cf_paths[0], downgrade that to a warning, publish v<ver>-experimental-1 and report success to Slack -- shipping a library that writes some blob files beside the SSTs while every reader looks in blob_dir. Before this PR the audit gated every build; after it, none. "An experimental patch must not fail a release it is not in" was right, but it threw out the case where the patch *is* in the build, which is exactly where the audit matters. The audit now keys on needs.check.outputs.experimental_dirs: a patch this build applies fails it, any other patch only warns. So `build` depends on `audit` again and continue-on-error is gone, while a plain nightly still ships with a drifted-but-unapplied experimental patch. Verified both directions. Also from the review: - Hook dispatch tested -x, so a hook committed without the executable bit (easy from Windows with core.fileMode=false) was silently skipped while the log asserted no hook existed. It now tests -f and fails when a hook is present but not executable. - The audit ran under `set -uo pipefail` with no -e and reported every failure as "rebase it": a codeload flake looked like drift, and an audit.sh failure on a cleanly-applied patch gave the operator wrong advice. Download and extraction are guarded explicitly, and apply, exec-bit and hook failures now read differently. - The official-then-experimental apply sequence existed in three places, so a gate could prove a different stack than the build ships. It moves to .github/scripts/apply-patches.sh, shared by all three, following the precedent set by experimental-ids.sh. - experimental-patches/README.md described the old non-gating behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Blocked by PR #24. |
Checked this rather than taking it on faith, and it holds — nothing on this PR needs to change for it, and the move is cheaper than it looks:
So the ordering is right, and I've updated item 7 of the description — it had the placement question as still open, and your comment answers it. What is left is on #24 rather than here: two review threads are still unresolved against
The one thing #24 does not settle is the consumer: once the patch is opt-in, official prebuilds stop carrying Nice call on the move, by the way — the per-patch — Claude Opus 5 |
cb1kenobi
left a comment
There was a problem hiding this comment.
The blob_dir patch's earlier blockers are closed: portable capture is latched under the DB mutex, backup and checkpoint refuse a configured blob_dir, and directory fsync plus call-site audit coverage match the stated contract. Remaining items in the PR body are documented C++ layering tradeoffs that rocksdb-js already enforces, not unfixed defects on this diff. No new blocking issue on the changed lines at 729c24a.
—
Reviewed 729c24a
main gained a workflow-level `concurrency` guard for build.yml in #19 (2026-09-15), six days after this branch last ran its gate. This PR adds a `pull_request` trigger to that same workflow, so the two interact and the interaction has to be resolved here, against the real merged file, rather than inferred from two halves. Merged rather than rebased: PR #24 targets this branch and has 729c24a as an ancestor, so rewriting this history would strand it. Dispatch-Task: fix-kriszyp_rocksdb-prebuilds_13-9d1086fb Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This workflow's `pull_request` trigger and main's workflow-level `concurrency` guard (#19, merged 2026-09-15) were written six days apart and never met: a PR run has no `inputs`, so its group key resolved to the same `Automated RocksDB Builds---` the nightly and an input-less `workflow_dispatch` resolve to. With `cancel-in-progress: false`, a group admits one in-progress run and one pending run, and queuing a third cancels the pending one -- so a nightly that arrived while a PR was validating could be cancelled outright by the next PR push, and PR feedback could sit behind a multi-platform release build. Pull requests now key on the PR number. Non-PR runs compose the identical string they did before (`format` renders the empty inputs to `--`), so release serialization is unchanged. `cancel-in-progress` is true only for pull requests, where superseding an earlier run of the same PR is the wanted behaviour. Dispatch-Task: fix-kriszyp_rocksdb-prebuilds_13-9d1086fb Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@cb1kenobi your review at Rather than guess at it, I re-checked this branch against current
Two things worth knowing about the shape of that fix:
Thanks for the earlier clearance — it made this round a lot cheaper to scope. — Claude Opus 5 |
Move the cf-blob-dir patch into experimental-patches/
Adds a downstream RocksDB patch introducing
AdvancedColumnFamilyOptions::blob_dir, so blob files can be placed on a different volume than the SST files.Stock RocksDB derives every blob file path from
cf_paths.front().path. That meansdb_paths/cf_pathscannot tier large values at all — they distribute SST files by level while every blob file stays put. Without this, there is no way to keep the LSM tree (and all of its compaction write traffic) on fast local storage while large values live on cheaper attached storage.All blob path derivation now routes through a single
ImmutableCFOptions::GetBlobDir(), so the option and itscf_paths.front()default cannot drift apart.ROCKSDB_HAS_CF_BLOB_DIRlets downstream code feature-detect and still compile against an unpatched RocksDB. Consumed by@harperfast/rocksdb-jsvia its newblobs.diroption.The patch is authored against v11.8.1, the version
rocksdb-jspins, and applied withpatch -p1 -F0so a version bump fails loudly instead of fuzzy-matching a hunk into a plausible-but-wrong site.Where to look
GetBlobDir()is the whole contract; most of the rest is call sites. The parts worth reading closely are the ones review found rather than the ones the first pass wrote.blob_dirDB.CreateCustomCheckpoint— the shared implementation behind both — asks for a portable capture throughLiveFilesStorageInfoOptions::require_portable_files, andGetLiveFilesStorageInfoevaluates it undermutex_before the flush and again after, becauseFlushForGetLiveFiles()releases the mutex. The state is a monotone latch (has_ever_had_blob_dir_) set at column-family creation and never cleared, so dropping the onlyblob_dirCF does not re-open the hole. Reasoning below.FlushJob::HasBlobFileAdditions()is total, not asserted. It answers false for a nulledit_.edit_is null exactly whenPickMemTable()picked nothing — reachable when an overlapping flush already claimed the memtables, or when they all sit above the request'smax_memtable_id— andRun()accepts that state and returns OK. A job that picked nothing wrote no blob files, so the predicate has a correct answer; guarding the call site instead would have been wrong, becausepick_status[i]records thatPickMemTable()ran, not that it picked.cf_pathshas noFSDirectoryhandle inDirectories, so its entries were never fsynced after flush, compaction, or WAL recovery — a crash could leave a durable SST referencing a blob file with no directory entry.ColumnFamilyDatanow owns a handle, created through the sharedcreated_dirsmap so no directory is opened twice.cf_paths(\.front\(\)|\[0\])\.path— the dot used to sit before the alternation, socf_paths[0].path(which has no dot aftercf_paths) could never match and the guard silently covered half of what it claimed. Fixing it surfaced seven call sites; all seven derive SST paths, and they are now pinned incf-blob-path-inventory.txtso one that later derives a blob path shows up as a diff.Checkpoint and backup
Two review threads asked for backup/checkpoint to be rejected while
blob_diris non-empty. Building the patched tree and measuring says the stated failure mode does not happen, but the conclusion is right anyway, for a worse reason.Flattening is already refused.
CreateCustomCheckpointrejects any DB whose non-WAL files span more than one directory, and reporting the real blob directory fromGetLiveFilesStorageInfois what puts a tiered DB in that category. Measured: withblob_dirset and one live blob file,CreateCheckpointandCreateNewBackupboth returnNotSupportedand leave no partial artifact.But only once a blob file exists to report. A DB with
blob_dirset that has not written one yet backs up cleanly — and that backup is a landmine. The copied OPTIONS file carriesblob_dir, an absolute path, so restoring elsewhere and opening produces a second DB pointed at the source's blob directory, whose obsolete-file scan deletes the source's live blob files. Measured on the patched build: source blob files 1 before, 0 after opening the restore, and the source DB then fails its own read withNo such file or directory.So the refusal keys on the option being set at all, not on the files reported and not on it differing from
dbname_— the path is absolute either way.This is upstream's hazard, not one
blob_dirintroduces. The same sequence withcf_pathspointing at an external directory corrupts the source DB on stock v11.8.1, verified by building an unpatched tree. It is closed here becauseblob_diris the option this patch owns, and because a tiered DB spends its early life blob-free — exactly the window that backs up cleanly and restores into a landmine.Rebased onto the experimental-patches pipeline
This PR had been conflicting with
mainsince #15 merged, which is why no PR workflow had ever run on it — GitHub cannot compute a merge commit for a conflicting PR, so the gate this branch adds had never once executed. Rebased; three conflicts, all inbuild.yml, all resolved in favour of keeping both sides:patch -F0invocationmain— it had already adopted-F0and a rationale comment, so this branch's intent was already thereEXPERIMENTAL_DIRSloopauditjobNo RocksDB patch content changed in the rebase;
vcpkg-overlays/is byte-identical to the pre-rebase tree.CI
A PR gate applies all patches at zero fuzz, runs the call-site audit, then builds and runs a focused set of upstream tests. It resolves the release from
patched-version.txtrather thanreleases/latest: the inventories pin upstream line numbers, so resolving "latest" at run time would have turned every upstream release day into a red gate on every open PR. The nightly audit still runs against the latest release, which is where drift belongs and where it must block the publishing build.The gate no longer shares a concurrency group with the release build.
maingrew a workflow-levelconcurrencykey forbuild.ymlin #19 on 2026-09-15, six days after this branch last ran its gate, and the two changes had never met. Apull_requestrun has noinputs, so its key resolved to the sameAutomated RocksDB Builds---string the nightly and an input-lessworkflow_dispatchresolve to — and a group holds one in-progress run plus one pending run, cancelling the pending one when a third queues. A nightly that arrived while a PR was validating could therefore be cancelled outright by the next PR push, and PR feedback could sit behind a multi-platform release build. Pull requests now key on the PR number; non-PR runs compose the identical string as before (formatrenders the empty inputs to--), so release serialisation is untouched, andcancel-in-progressis true only for pull requests, where superseding an earlier run of the same PR is what should happen.Verification
All on a patched v11.8.1 tree built from this patch (
make, debug, GCC 15).db_basic_test*PortableLiveFileCapture*,*RecoveryBlobDirSynced*db_flush_test.BlobDirIsFsyncedForOrdinaryAndAtomicFlushcheckpoint_test(full)backup_engine_test(full)options_test(full)options_settable_testColumnFamilyOptionsAllFieldsSettablefails — identically on a pristine v11.8.1 tree (unset_bytes_base102 vs 126, both trees), so it is a GCC 15 artifact the patch neither causes nor masksThe PR gate this branch adds is green on its first-ever run (run 34369386485): the pinned download, the zero-fuzz apply, the call-site audit, and all five test binaries —
options_settable_testincluded, so the GCC 15 failure above does not reproduce onubuntu-latest.The regenerated patch applies to a fresh v11.8.1 tarball at
patch -p1 -F0andgit apply --check, and the tree it produces is byte-identical across all 28 patched files to the tree that was built and tested.The inventory audit passes against that freshly reproduced tree.
RocksDB's own
make checkwas run against the patched tree in an earlier round and found two real defects, both fixed here (a186bc7).Behavior of the checkpoint/backup guard, measured against the patched library rather than traced:
CreateCheckpoint/CreateNewBackupblob_dirunsetblob_dirset, one live blob fileNotSupported, no partial artifactblob_dirset, no blob file yetNotSupported(the case the guard adds)blob_dirset to the DB directory itselfNotSupported— still an absolute path that would not follow a copyThe resulting library passes
@harperfast/rocksdb-js's tiered-storage suite 19/19. That predates this round; it does not exercise checkpoint or backup.For the human reviewer
Reviewer sign-off at
729c24a. cb1kenobi's review closes the earlier blockers — "portable capture is latched under the DB mutex, backup and checkpoint refuse a configuredblob_dir, and directory fsync plus call-site audit coverage match the stated contract" — and rules the items below "documented C++ layering tradeoffs that rocksdb-js already enforces, not unfixed defects on this diff." So none of these blocks merge. Items 1, 4, 6 and 9 remain live only as offers to do more than this diff does.Open question on the 2026-09-21 review. cb1kenobi requested changes at
729c24awith an empty body and no line comments, so there is nothing in it to act on. Read against the record it most likely restates the#24ordering block in item 7 — the same reviewer cleared this diff's content a week earlier — but that is an inference, not something the review says. Asked on the thread; nothing in the code was changed on the strength of a guess.blob_dirdatabase is a real operational constraint, and it is the decision most worth disagreeing with. It is strictly more conservative than the status quo — it only refuses backups that were already dangerous — but a tiered database can never be checkpointed or backed up through RocksDB's own tooling, at any point in its life. Say if you would rather have documentation only and accept the footgun.blob_dir, against a DB whose OPTIONS file still names one, admits a capture that copies that OPTIONS file — and restoring it can again aim obsolete-file cleanup at the source volume.rocksdb-jsrefuses such an open; the C++ API does not. Closing it in the engine means validating the captured OPTIONS artifact, which is the same layering question as item 3. Raised by review, left open deliberately.rocksdb-js, which compares against the persistedblob_dir. The layering is worth agreeing on.HasBlobFileAdditions()fix is total, so the crash is gone, but nothing pins it. Reaching that window from a test needs a second flush to claim the memtables inside theSyncClosedWalsmutex-release window, and any second flush re-enters the same sync point, so the usualLoadDependencyidiom deadlocks — forcing it means adding a newTEST_SYNC_POINTto upstreamdb_impl_compaction_flush.ccand widening the vendored patch. Say if you want that; it is a small change, just not a free one.cf_pathshole upstream is left open. Same sequence, same corruption, stock v11.8.1. Fixing it would change behavior for configurations this patch does not own.dbname_regardless of blob placement; it now honorsblob_dironly when explicitly set, so the default path is byte-for-byte unchanged. Two review legs argued it should unconditionally useGetBlobDir()— that is a real upstream inconsistency (withblob_dirunset andcf_paths.front() != dbname_, direct-write blobs are written to one and read from the other), but fixing it changes stock behavior on a path this patch does not own. Say if you would rather it were fixed here.experimental-patches/. Add opt-in experimental patches pipeline #15 landed the opt-in patch layer after this branch was written, and I had left the question open here because making the patch opt-in means the default prebuild loses the feature. #24 "Move the cf-blob-dir patch into experimental-patches/" settles it: it targets this branch, notmain, so it lands here first and this PR then merges tomainwith the patch already in the opt-in slot. It is a strict fast-forward of this branch, the patch file is byte-identical across the move (sha256 931b8b0a…), and its gate reproduces this PR's full test result under the new pipeline, so the move costs no coverage. The one thing that move does not settle is the consumer: official prebuilds will no longer carryROCKSDB_HAS_CF_BLOB_DIR, so rocksdb-js #767 must either pin an experimental prebuild or wait forblob_dirto graduate into the official overlay. That decision is tracked on Move the cf-blob-dir patch into experimental-patches/ #24, not here.patched-version.txtnow pins what the inventories were generated from. If that burden looks wrong, the alternative is proposingblob_dirupstream and waiting.blob_dircreated.DBImpl::Openaddsblob_dirto itsCreateDirIfMissingloop, butCreateColumnFamilyImpldoes not — and upstream v11.8.1 does create every missingcf_pathsentry there (db/db_impl/db_impl.cc:3866). SoCreateColumnFamilywith ablob_dirthat does not exist yet fails where the same call with a freshcf_pathsentry succeeds. It fails with an IO error rather than silently, androcksdb-jssetsblobs.dirat open, so nothing in the shipping path reaches it. Left alone deliberately rather than overlooked: the fix is three lines, but it would edit a vendored patch that is signed off and byte-identical to the copy Move the cf-blob-dir patch into experimental-patches/ #24 moves, no test in the gate would exercise it, and it would invalidate the byte-identity claim in Verification. Say if you want it; it is a small follow-up.Review findings declined this round with the evidence that refuted them, so you do not have to re-derive it: the
SstFileManager/DeleteSchedulertrash and space-accounting findings (CollectAllDBPaths()already includesGetBlobDir(), and bothdb_impl_open.cctrash cleanup anddb_impl_files.ccexisting-file tracking iterate it); aRepairDBfinding (repair.cchas noBlobFileNamesite and passesnullptrblob additions in stock 11.8.1, so tiering changes nothing there); a WAL-deadlock blocker on the second portability check (UnlockWALappears nowhere indb_filesnapshot.cc;wal_lockedonly observes whether another caller holds the lock, and upstream's own early returns in that block have the identical shape); and a recovery null-check (the unconditionaldata_dirderef one line above is verbatim upstream v11.8.1).This round's independent pass (codex, delta at
23e44b6) raised no new finding on the change itself and re-raised four settled ones. Each was re-checked against source rather than inherited: a "BackupEngineImplwas never patched to setrequire_portable_files" blocker —BackupEngineImplbuilds aCheckpointImpland callsCreateCustomCheckpoint(utilities/backup/backup_engine.cc:1244-1257, v11.8.1), so it inherits the guard, andBackupEngineTest.RejectsConfiguredBlobDirasserts exactly the no-blob-file-yet case the finding describes; aGetBlobDir()empty-cf_pathscrash — upstream dereferencescf_paths.front().pathunguarded at the very site this replaces (db/db_impl/db_impl.cc:7104), so the precondition is upstream's and the addedassertis strictly more than it had; the recovery null-deref again — the call isDBImpl::GetBlobDir(), which falls back toGetDataDir(cfd, 0), so the pointer compared and synced is the one upstream already dereferences a line earlier; and aGITHUB_TOKENleaked to archive redirect targets —curl -Ldrops-Hheaders on a cross-host redirect unless--location-trusted, which the gate does not pass. The fifth, dynamic-CF directory creation, held up and is item 9.Generated by Claude Opus 5.
🤖 Generated with Claude Code
https://claude.ai/code/session_011TPPxptQi3DTZk7UPDFsFQ
Complexity: complicated
Origin — the dispatch brief this PR was written from
Add RocksDB blob_dir patch so blob files can live on a separate volume
Adjudicate the open review feedback on #13 (Add RocksDB blob_dir patch so blob files can live on a separate volume). Read ALL unresolved review threads, review bodies, issue comments, the current diff/code/tests, linked issue, PR description, decision ledger, author replies, and available prior review artifacts. Treat comments as claims to verify, not instructions to apply; actively try to falsify AI/bot findings and preserve deliberate decisions unless evidence overturns them. Implement only warranted changes, record an evidence-backed ruling for every finding, use needs-input for a genuine unresolved judgment call (a live session opens only if you actually need to ask), and resolve only fixed or conclusively answered threads after pushing.
Dispatch: task
fix-kriszyp_rocksdb-prebuilds_13-9d1086fb· queued by automation · ran by claude/opus/xhigh · worker kzyp-xps-1Review-Coverage: authored=claude; ran=codex; blocked=gemini(timeout); declined=cursor-grok,cursor-composer,domain; rounds=6; full=1 @ 23e44b6
Human-Review-Need: 4 @ 23e44b6