Skip to content

Honor storage.compression on upgraded databases, and let a deployment select the codec - #2044

Draft
kriszyp wants to merge 8 commits into
mainfrom
kris/compression-bench
Draft

Honor storage.compression on upgraded databases, and let a deployment select the codec#2044
kriszyp wants to merge 8 commits into
mainfrom
kris/compression-bench

Conversation

@kriszyp

@kriszyp kriszyp commented Aug 1, 2026

Copy link
Copy Markdown
Member

rocksdb-js 2.6.1 (#2036) brought the codecs in and made lz4 the effective default. This fixes the case that default silently misses, and adds a way to choose a different codec.

The bug: upgraded databases ignore storage.compression

storage.compression defaults to true (config/defaultConfig.yaml), so essentially every existing table's metadata records "compression enabled". toRocksCompression mapped that to unset, on the documented assumption that unset means "use the build default (lz4)".

It doesn't — not for a column family that already exists. RocksDB persists the codec per family, and a reopen that requests nothing inherits what the family already has; the build default applies only when the family is being created. Measured across separate processes:

5.1 instance, compression 'none'          -> 34.2M SST
5.2 instance, no option passed
    open -> engine reports {"algorithm":"none"}    <-- inherited, not lz4
                                          -> 68.4M SST   (new writes uncompressed too)
full compact()                            -> 68.4M SST   (unchanged)

control: same records, lz4 from scratch   ->  7.1M SST

So a brand-new 5.2 database gets lz4 while every database upgraded from 5.1 keeps writing uncompressed indefinitely — both having asked for exactly the same thing. New databases only worked by accident, because unset on a non-existent family does fall through to the build default.

An enabled setting now resolves to an explicit codec. On the real metadata path ({ startingOffset, threshold }, what getDefaultCompression() returns when storage.compression is true):

old:      opened 'none' -> reports none -> 31.6M SST
upgraded: opened 'lz4'  -> reports lz4  -> 33.9M SST   (the next 60k records cost 2.3M, not 31.6M)

Existing data is deliberately left alone. An explicit codec governs newly written files only, and db.compact() won't convert the rest: rocksdb-js calls CompactRange with default CompactRangeOptions, whose bottommost_level_compaction is kIfHaveCompactionFilter, and Harper installs no compaction filter — the bottommost level, holding the bulk of the data, is skipped. Converting in place needs a forced bottommost compaction, which is expensive enough to deserve an explicit operator decision rather than happening during an upgrade.

Choosing a codec

HARPER_STORAGE_ROCKS_COMPRESSION selects the codec for every column family the process opens, validated against the build's supportedCompression so an unavailable name fails with a named error instead of a partial startup failure at the first open.

It's an env var and not a config key, and resolved once at module load, because RocksDB rejects reopening a family under a disagreeing codec and Harper's worker threads share one process-wide family registry — so every thread must agree. A config value doesn't achieve that; the main thread loads the storage layer during install, before the value is readable, while workers load it after:

pid=3591555 thread=0 resolved=undefined   <-- main thread, loads during install
pid=3591555 thread=1 resolved=zstd
pid=3591555 thread=2 resolved=zstd

__dbis__ is then opened at the build default and reopened as the configured codec, and Harper dies with The system database failed to load. Reproduced on a fresh install with the value in config only; it fails on every boot. Making it a config key needs the value resolvable before the first family opens — a boot-ordering change, deliberately not in this PR, and the constraint is recorded in DESIGN.md so it doesn't get "improved" into one.

copyDb's openRocksDb now resolves the codec the same way. DESIGN.md already called openRocksDatabase the single chokepoint and migration sat outside it — it creates the families the runtime later reopens.

Benchmark

benchmarks/compression/ compares codecs on identical data and workload, one instance per codec. On 1M document-shaped records:

codec data files vs none
none 846 MB 100%
snappy 434 MB 51%
lz4 454 MB 54%
zstd 351 MB 41%

Point reads get faster with compression — compressed blocks hold more records per block-cache slot and that outweighs decompression (lz4 nearly doubles point-read throughput at the storage layer). Sequential scans pay decompression with no cache-density offset and get slower.

Two traps the harness guards, both of which produce confidently wrong numbers: @harperfast/integration-testing installs under os.tmpdir(), which is tmpfs on most Linux hosts (every "on-disk" byte becomes a memory measurement); and Harper answers 503 once commits back up past storage.maxTransactionQueueTime — one run shed ~99% of its writes and still produced a throughput figure and a 67 MB "compressed" size, which reads as a spectacular win. Runs over an error-rate threshold are marked INVALID.

Open concerns for the reviewer

  • The toRocksCompression change alters behavior for every existing table, which is the point, but it's the line to scrutinize. false/'' still map to 'none'; an explicit { algorithm } still passes through untouched; only "enabled, unspecified" changes meaning.
  • Benchmark sizes are as the workload left them, not a compacted steady state — for the reason above, nothing outside the process can force a compaction. Codec-to-codec comparison is sound (identical workload each); the absolute ratio against logical bytes is inflated by update garbage. A storage-layer run using a real db.compact() agrees within 3 points.
  • Throughput numbers are single-sample, fixed codec order. REST-layer variance swamps the codec differences; the size columns are the reliable output. Noted in the README.
  • Reads never touched a device — the dataset fits in page cache. This measures decompression CPU and block-cache density, not I/O savings; an I/O-bound instance should look better.
  • openRocksDb is a behavior change on the migration path — right side of the stated invariant, but worth a second pair of eyes.

Follow-up worth doing: expose forced bottommost compaction in rocksdb-js so an existing database can be converted on an explicit operator decision.

Cross-model review (Codex + Gemini + Grok + Harper-domain) ran pre-push; its findings on a no-op compaction step, a scanBytesRead race, the copyDb bypass, missing validation, committed result JSONs and missing docs are all addressed.

Generated by Claude Opus 5.

🤖 Generated with Claude Code

…c benchmark

rocksdb-js 2.6.1 (harper#2036) brought the codecs and made lz4 the effective
default, but nothing lets a deployment choose a different one. Per-table metadata
only carries the LMDB-era boolean.

HARPER_STORAGE_ROCKS_COMPRESSION selects the codec for every column family the
process opens, validated against the build's supportedCompression so an
unavailable name fails with a named error rather than a partial startup failure
at the first open.

It is an environment variable, not a config key, and resolved once at module
load. RocksDB fixes a column family's codec while it is open and rejects a
reopen that disagrees, and worker threads share one process-wide column-family
registry, so every thread has to agree. A config value does not achieve that:
the main thread loads the storage layer during install, before the value is
readable, while workers load it after. Measured directly — same pid, thread 0
resolves undefined, threads 1 and 2 resolve zstd — after which __dbis__ is
opened at the build default, reopened as the configured codec, and Harper dies
with "The system database failed to load".

copyDb's openRocksDb now resolves the codec the same way. DESIGN.md already
called openRocksDatabase the single chokepoint; migration creates the column
families the runtime reopens, so creating them under the build default while the
runtime asks for a configured one wedges the next open.

The benchmark compares codecs on identical data and workload. On 1M
document-shaped records the data files come out at 51% (snappy), 54% (lz4) and
41% (zstd) of uncompressed, and point reads get faster rather than slower —
compressed blocks hold more records per block-cache slot and that outweighs
decompression. Sizes are as the workload left them, not a compacted steady
state: nothing outside the process can force a RocksDB compaction, and
storage.compactOnStart is an LMDB copy-compact that does nothing for RocksDB
stores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new RocksDB compression comparison benchmark and updates the database initialization logic to support selecting a RocksDB compression codec process-wide via the HARPER_STORAGE_ROCKS_COMPRESSION environment variable. The migration path in bin/copyDb.ts is also updated to resolve the codec consistently with the runtime. A high-severity issue was identified in the benchmark runner (benchmarks/compression/run.mts), where concurrent updates to scanBytesRead using += await create a race condition that leads to undercounting.

Comment thread benchmarks/compression/run.mts
Comment thread benchmarks/compression/run.mts Outdated
@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Reviewed the latest push (d88ccd4, resolving one codec per deployment via compressionForAllColumnFamilies). One blocker: the new default-codec fallback and compressionForAllColumnFamilies -- the mechanism the commit credits with the fix -- has no test coverage; see inline comment on resources/databases.ts. One non-blocking suggestion on a now-stale comment in bin/copyDb.ts.

kriszyp and others added 2 commits August 1, 2026 06:52
…pt a new default codec

Measured across separate processes rather than assumed. RocksDB persists the codec
per column family and a reopen without an explicit request inherits it, so a database
created before the prebuild carried any codecs stays uncompressed however new the
binary is — every 5.1 instance upgrading to 5.2 gets nothing from the lz4 default.

Setting the codec explicitly compresses subsequent writes only; db.compact() does not
convert the existing data, because rocksdb-js calls CompactRange with default
CompactRangeOptions, whose bottommost_level_compaction is kIfHaveCompactionFilter and
Harper installs no compaction filter, so the bottommost level is skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gnoring it

storage.compression defaults to true (defaultConfig.yaml), so essentially every
existing table's metadata records "compression enabled". toRocksCompression mapped
that to unset, on the assumption that unset means "use the build default (lz4)".

It does not, for a column family that already exists: RocksDB persists the codec per
family and a reopen requesting nothing inherits what the family has, applying the
build default only when creating it. So a new 5.2 database got lz4 while every
database upgraded from 5.1 kept writing uncompressed indefinitely — both having asked
for exactly the same thing.

An enabled setting now resolves to an explicit codec. Measured on the real metadata
path: a family created uncompressed reports lz4 after the upgrade open, and the next
60k records cost 2.3MB where the first 60k cost 31.6MB. Existing SSTs are left alone;
converting them needs a forced bottommost compaction, which is expensive enough to
deserve an explicit decision rather than happening during an upgrade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kriszyp kriszyp changed the title Let a deployment select the RocksDB compression codec, and add a codec benchmark Honor storage.compression on upgraded databases, and let a deployment select the codec Aug 1, 2026
…nment-only control

Replaces the env-only selector with `storage.rocks.compression`, registered as a real
config param, and fixes the bootstrap sequencing rather than routing around it.

install() calls mountHdb() — which creates the system column families — several steps
before createConfigFile() writes the config file, so the main thread resolved nothing
while workers resolved the configured codec and __dbis__ could not be reopened. The
installer now stages the value into the in-memory config before mountHdb(), mirroring
the STORAGE_ENGINE line beside it, and resolution is frozen at the first open so every
thread in the process agrees. Verified: config-only selection now boots clean on both a
fresh install and a restart, where both previously failed.

The copyDb rationale in DESIGN.md was wrong and is corrected: handles are closed before
the staging rename and rocksdb-js allows a codec change across close/reopen, so the
runtime would open the migrated database either way. The reason to thread the codec
through migration is that it writes the whole dataset at once, and those files then keep
their codec until traffic rewrites them.

Benchmark: fixed the scanBytesRead race both bots flagged (my earlier edit silently
no-op'd against reformatted source and I did not verify it); disk measurement now
requires a clean exit, since killHarper's 5s grace lets SIGKILL truncate the final flush
and a truncated flush reads as the codec being smaller; mutating requests are held to
zero failures, with the tolerance kept only for reads and scans, because a dropped
insert shrinks measured size while logicalBytes still counts it; warmup errors are no
longer discarded.

Tests now reach real RocksDB opens through the production resolution and assert the
codec the engine applied, using zstd so they discriminate against the build default.
The previous env-var block destructured an export that no longer exists and passed
vacuously — removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kriszyp

kriszyp commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

All five review points addressed in 3246ec162. Replying here rather than inline because the pending review blocks thread replies.

1. Config surface instead of an environment-only control — agreed, and the sequencing was fixable. storage.rocks.compression is now a registered config param; the bespoke process.env read is gone.

The ordering problem was install() calling mountHdb() (which creates the system column families) several steps before createConfigFile() writes the config file. installer.ts now stages the value into the in-memory config before mountHdb(), mirroring the STORAGE_ENGINE line directly above it, and resolution is frozen at the first open so every thread agrees. Verified on the case that previously failed on every boot:

--- boot 1 (fresh install, config only, NO env var) ---
boot1 route ready: true
boot1 write status: 204
--- boot 2 (restart, config file now exists) ---
boot2 route ready: true
boot2 write status: 204
=== any CF conflict? ===   (none)

Mounted YAML and HARPER_CONFIG/HARPER_SET_CONFIG now select the codec. The benchmark switched to config with them — worth noting it would otherwise have silently compared four identical configurations.

2. killHarper() 5s grace truncating the final flush — real, and it would bias exactly the wrong way. The runner now takes --shutdown-grace (default 120s), watches the child's exit, and fails the run if it was SIGKILLed, rather than measuring a partial flush. The CPU sample still precedes shutdown by necessity (the /proc entries vanish with the process), so it's labelled as workload CPU rather than including the final flush.

3. Zero tolerance for failed writes — agreed; size is the primary result. Mutating requests (warmup, insert, update) are now held to zero failures; the tolerance survives only for reads and scans, which can't change what's stored. Warmup errors are retained rather than discarded, since warmup rows land in the same table and count toward logicalBytes.

4. Tests that reach a real openunitTests/resources/rocksCompressionOpen.test.js stages a codec and opens real column families through the production resolution, asserting what the engine actually applied. It uses zstd rather than none so the assertions discriminate: with the forwarding removed these families open at the build default and the tests fail. Covers a table family and __dbis__ agreeing, the configured codec beating compression: false metadata, a conflict-free reopen, and the unsupported-codec rejection.

You were also right that the previous block was worthless — it destructured an export that no longer existed and passed vacuously. Removed.

I did not get multi-thread coverage into a unit test; the fresh-install-with-workers case is verified by the reproduction above but isn't automated. Happy to add it as an integration test if you want it gated in CI.

5. copyDb rationale inaccurate — correct, and reframed. copyDbToRocks() closes every target handle before the staging rename and rocksdb-js allows a codec change across close/reopen, so the runtime would open the migrated database fine either way. The actual reason to thread the codec through migration is that it writes the entire dataset in one pass, and those files then keep their codec until write traffic rewrites them — so it's the one moment the deployment's codec should apply.

Also fixed the scanBytesRead race both bots flagged. My earlier claim to have fixed it was wrong: the edit silently no-op'd against reformatted source and I didn't verify that specific replacement.

Not marking Ready.

Comment thread utility/install/installer.ts
Comment thread utility/install/installer.ts
kriszyp and others added 4 commits August 1, 2026 11:41
…aded databases with a stale codec

Two independent failures on this branch's CI:

1. unitTests/resources/rocksCompressionOpen.test.js assumed it was the first thing in the
   process to call getRocksCompression() (which resolves-and-freezes on first call). True in
   isolation, false under the full `test:unit:resources` glob — several earlier-alphabetical
   files open a RocksDatabase first and freeze it at `undefined` before this file runs, so its
   setProperty() calls have no effect. Added resetRocksCompression() (test-only un-freeze) and
   use it in before()/after() hooks, restoring both the singleton and the config property so
   this file's mutations don't leak into the rest of the suite.

2. Integration Tests (cross-version-upgrade-visibility): booting the current build over a
   database created by 5.1.x crashed with "Column family already open with compression none;
   cannot reopen it with lz4". Traced with temporary diagnostic logging against the real
   5.1.22 fixture: RocksDB's DB::Open opens every existing column family transitively in one
   call (there is no lazy per-CF open), so by the time initStores()'s per-table reconcile
   tries to apply the metadata-implied codec to an existing family, that family is already
   open at whatever was already persisted for it. Confirmed this isn't fixable from the
   caller's side today: explicitly requesting a codec on the root/default-CF open does not
   propagate to sibling CFs opened transitively alongside it.

   openExistingRocksPrimaryStore() now catches that specific conflict for an existing
   family's reconcile-open and falls back to the family's already-open codec with a warning,
   instead of a fatal boot failure. An explicit storage.rocks.compression override still
   fails loudly (the fallback retry re-resolves through getRocksCompression(), which takes
   precedence regardless). Filed HarperFast/rocksdb-js task rocksdb-js-cf-compression-api
   for the real fix (an API to change an already-open column family's codec without a full
   close) — this is the boot-safety net until that lands.

Verified: test:unit:resources (RocksDB) 1357 passing, test:unit:resources (LMDB) 1203
passing, test:unit:main 4221 passing, all 0 failing. cross-version-upgrade-visibility
integration test passes end-to-end against the real 5.1.22 fixture (24/24 rows visible on
every surface, pre- and post-upgrade, across two restarts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TUbqfkBvfRnC6RyvgBdfX
…not just the primary store

The pre-push review (codex + gemini + grok + harper-domain) confirmed my first fix was
incomplete: openExistingRocksPrimaryStore only wrapped the per-table primary-store open in
initStores()'s reconcile loop. The domain reviewer verified directly against the real
rocksdb-js binding that __dbis__ and every index column family hit the identical conflict —
they're opened with `compression: undefined` in their dbiInit, which resolves through
getRocksCompression() same as any other open, and are just as likely to already be
transitively open at a different codec. Setting storage.rocks.compression on any pre-existing
database (not just a cross-version upgrade) was still a hard boot failure.

Generalized the fallback into openExistingRocksColumnFamily() and applied it at every
reconcile-time open that can hit an already-open family: __dbis__ (both in initStores() and
in table()'s two call sites), the primary store (both initStores()'s reconcile and table()'s
create-branch, which can reopen a physical remnant left by an interrupted drop), index opens
in openIndex(), and the drop-remnant open in completeInterruptedDrop(). Left the bare
root/default-CF opens (readRocksMetaDb, database()) alone — those only ever run once per
path per thread, before anything else has touched that path in this process, so they can't
hit this conflict.

Also fixed two things the review flagged in the fallback itself: the warning now only logs
after the retry actually succeeds (previously it logged "keeping existing codec" and then,
for an explicit misconfigured override, the retry would fail right after — a false recovery
message), and it's deduped per column family instead of firing on every worker thread's
independent reconcile of the same table.

Second major finding: utility/install/installer.ts's stageRocksCompression() only read the
JSON-blob env vars (HARPER_DEFAULT_CONFIG/HARPER_CONFIG/HARPER_SET_CONFIG). The individual
STORAGE_ROCKS_COMPRESSION env var, the --STORAGE_ROCKS_COMPRESSION CLI flag, and an
--HDB_CONFIG file all resolve normally through createConfigFile() later, so any of those
forms would recreate the exact fresh-install race this function exists to prevent: install
succeeds (main thread creates __dbis__ at the build default), and the very next boot dies
once workers resolve the now-written config's codec against a family already fixed at a
different one. stageRocksCompression() now checks all three sources in the same precedence
createConfigFile() itself resolves them in.

Verified: test:unit:resources (RocksDB) 1357 passing, test:unit:main 4221 passing, both 0
failing. cross-version-upgrade-visibility integration test still passes end-to-end against
the real 5.1.22 fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TUbqfkBvfRnC6RyvgBdfX
Unused destructured binding and an unwrapped line prettier wanted split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011TUbqfkBvfRnC6RyvgBdfX
Replaces the boot-time fallback with the thing it was working around.

RocksDB opens every column family of a database in one DB::Open, and rocksdb-js
gives each family the caller did not name its persisted algorithm, applying the
request only to the target. So a family Harper never names individually keeps
whatever it was created with — which is why an upgraded 5.1 database stayed
uncompressed however new the binary was, and why reconciling a table afterwards
hit "already open with compression none; cannot reopen it with lz4". The previous
commit caught that and kept the old codec, which meant the upgrade fix could never
actually take effect.

Opens now pass compressionForAllColumnFamilies (HarperFast/rocksdb-js#743) with a
single codec resolved from configuration: storage.rocks.compression if it names
one, else storage.compression decides enabled-or-not and the build default supplies
the algorithm. Every family adopts it, so nothing conflicts and the fallback is
gone along with its warning.

The trade-off is deliberate: per-table compression metadata no longer selects a
codec. A table persisted as disabled inside a deployment that enables compression
would need its own, and there is nowhere to apply it — Harper cannot know a table's
codec before the open that already opened its catalog.

Verified end to end: 20k records written with compression off (9.6M SST), then the
same data directory rebooted with it on — boots clean with no conflict, and
compaction re-encodes to 1.6M. test:unit:resources 1357 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kriszyp

kriszyp commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Reshaped around rocksdb-js #743 (compressionForAllColumnFamilies) instead of the boot-time fallback. d88ccd4aa.

⚠️ Blocked on #743 being released — this branch needs a @harperfast/rocksdb-js that has the option. Verified locally against a linked build of that branch; the dependency floor still has to be bumped once it ships.

What changed

The fallback added in 20682c00f caught the already open with compression conflict and kept the family's existing codec. That kept boot working, but it meant the upgrade fix could never actually take effect: every pre-existing table stayed uncompressed, which was the entire point of the commit before it.

The root cause is that rocksdb-js gives every family the caller did not name its persisted algorithm and applies the request only to the target — so a family Harper never names individually keeps its original codec forever. #743 adds the opt-in that applies the codec to all of them.

Opens now pass that flag with a single codec resolved from configuration:

  • storage.rocks.compression if it names one
  • otherwise storage.compression (default true) decides enabled-or-not and the build default supplies the algorithm

Every family adopts it, nothing conflicts, and openExistingRocksColumnFamily and its warning are deleted.

Deliberate trade-off

Per-table compression metadata no longer selects a codec. A table persisted as compression: false inside a deployment that enables compression would need its own codec, and there is nowhere to apply it — Harper cannot know a table's codec before the open that already opened its catalog. Compression is a deployment setting now.

In practice per-table values came from the global storage.compression at table-creation time, so a mixed database is unusual; but it is a real behavior change and it is why the fallback is unnecessary rather than merely bypassed. If we ever want genuine per-table codecs, that needs #742 (setCompression()), which is parked in draft for exactly that case.

Verified

20k records written with storage.compression: false (9.6M SST), then the same data directory rebooted with it on:

phase 1 (compression off): 9.6M SST
phase 2 (compression on):  1.6M SST total

Boots clean, no conflict, no fallback — and compaction re-encoded the existing data. test:unit:resources 1357 passing.

Generated by Claude Opus 5.

Comment thread resources/databases.ts
Comment on lines +296 to +298
const databaseCodec = getRocksCompression();
legacyOptions.compression = databaseCodec;
if (databaseCodec) (options as { compressionForAllColumnFamilies?: boolean }).compressionForAllColumnFamilies = true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test exercises compressionForAllColumnFamilies or the new default-codec fallback

What: This commit makes getRocksCompression() resolve to a real codec (via the new readDatabaseCodec()) even when storage.rocks.compression is unset — falling back to getDefaultCompression() — and, whenever that resolves truthy, sets compressionForAllColumnFamilies = true on every open. That flag is the entire mechanism the commit message credits with fixing the bug ("every family adopts it, so nothing conflicts"), replacing the deleted openExistingRocksColumnFamily fallback.

Neither the flag nor the new default-fallback branch has any direct test coverage. unitTests/resources/rocksCompressionOpen.test.js (unchanged by this commit) still opens RocksDatabase directly via a hand-rolled resolveCompression = getRocksCompression() ?? toRocksCompression(metadata) helper — the old openRocksDatabase() logic — and never calls openRocksDatabase() itself or passes compressionForAllColumnFamilies. Its own docstring's claim to test "the same resolution openRocksDatabase() uses" is now stale.

Why it matters: This is exactly the upgraded-database scenario the whole PR chain exists to fix (a pre-existing family, never individually named, that must adopt a newly-resolved codec without conflicting). Without a test that opens a family through the real openRocksDatabase(), closes it, reopens the same directory under a different resolved codec, and asserts the untouched sibling family also converts, a regression here (or the upstream compressionForAllColumnFamilies behaving differently than assumed) would silently reintroduce the "already open with compression X; cannot reopen it with Y" boot failure with no test catching it.

Suggested fix: Add a case to rocksCompressionOpen.test.js that goes through openRocksDatabase() (not a hand-resolved compression option), opens two named families in one directory under one codec, closes, reopens under a different codec, and asserts both the named and the previously-unnamed family report the new codec.

Comment thread bin/copyDb.ts
Comment on lines +338 to +342
// Migration writes the column families the runtime will later reopen, so it has to resolve the
// codec exactly as openRocksDatabase does. Creating them under the build default while the
// runtime asks for the configured one leaves the next open unable to reopen them.
const legacyOptions = options as { compression?: unknown };
legacyOptions.compression = getRocksCompression() ?? toRocksCompression(legacyOptions.compression);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion (non-blocking): This comment ("resolve the codec exactly as openRocksDatabase does") and the getRocksCompression() ?? toRocksCompression(...) fallback it describes are now stale — the sibling openRocksDatabase() in resources/databases.ts was changed by this same PR to drop the ?? toRocksCompression(...) fallback and to additionally set compressionForAllColumnFamilies whenever the resolved codec is truthy. Migration is safe today only because copyDbToRocks always wipes and recreates the staging directory (no pre-existing family to reconcile), so the missing flag isn't a live bug — but update the comment (and confirm the two chokepoints intentionally diverge) so a future reader doesn't assume they still match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant