Repository navigation
Ship both PerfContext-enabled and PerfContext-disabled RocksDB libraries in every prebuild archive - #27
Merged
cb1kenobi merged 6 commits intoOct 8, 2026
Conversation
PR #26 compiled PerfContext out for every consumer of the published archives, which is a one-way choice: releases are immutable, so a consumer that later needs the counters has to build from source. Each archive now carries both libraries instead. The PerfContext-enabled build stays at lib/librocksdb.a (lib/rocksdb.lib on Windows), so the historical path keeps its historical meaning, and the compiled-out build lands at lib/no-perf-context/ alongside it. The public headers, the dependency libraries and the CMake and pkg-config packages are shared. The release still publishes ten archives, one per platform target. The variant is a vcpkg feature rather than an environment variable because a feature is part of the port's ABI hash, so the second install cannot be served the first one's binary-cache entry. Both installs run the same portfile, which is what keeps them identical apart from WITH_PERF_CONTEXT. Verification runs against a fresh extraction of the finished archive: the two libraries must be present and distinct, must reference rocksdb::perf_context in opposite directions, and - on the seven targets whose runner can execute their own output - must record a non-zero and a zero user_key_comparison_count respectively. The portfile reads NPERF_CONTEXT back out of the generated build system, which covers the three cross-compiled targets the other two checks cannot reach. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The symbol check read an nm failure as zero references, so an unreadable disabled library passed the one check that was supposed to catch it. nm and grep exit statuses are now separated, with grep's status 1 the only accepted zero. Windows archives had no compiled-artifact check at all: nm covers eight targets and the probe seven, leaving both windows-arm64 targets resting on a build-system string search. Perf-counter names only reach .rodata through PerfContext::ToString(), which NPERF_CONTEXT compiles away, so searching the archived library for user_key_comparison_count separates the variants with no toolchain and no matching architecture. That check now runs on all ten targets, in both directions, with a control string present in both libraries guarding the search itself. The portfile's build-system check matched the macro name anywhere; it now matches the definition form the option actually generates (-DNPERF_CONTEXT, verified as the only form CMake emits here). Also: the design note promised exactly two libraries while the archive retains the enabled debug build, the README's CMake override did not say that Debug configurations have no second variant to select, and several new comments narrated code rather than stating a constraint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The counter-name check added in the previous commit would have failed every Windows target on its first run. user_key_comparison_count is not only the string literal PerfContext::ToString() emits, it is also a PerfContextBase field name, and vcpkg's MSVC release flags include /Z7 (scripts/toolchains/windows.cmake), so CodeView type information carries that name into the .lib whether or not PerfContext is compiled in. Both variants declare the struct identically, so the literal is exactly the enabled library's margin: the check now requires strictly more occurrences in the enabled library than the disabled one, which is unaffected by a debug-info contribution both of them share. Verified by appending the name to both libraries of a Windows-shaped archive, which passes, and to a swapped pair, which still fails. The portfile's definition regex also matched NPERF_CONTEXT_EXTRA; it now requires a non-identifier character or end of line after the name, and accepts the MSBuild PreprocessorDefinitions form after '>' as well as '=', ';' and '"'. Checked against the real generated build.ninja for both variants and against seven hand-written forms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Alpine's grep is BusyBox, whose binary-input handling and -o occurrence counting the marker comparison cannot rely on. The verify container now installs the grep package, which puts GNU grep at /usr/bin/grep, ahead of BusyBox's /bin/grep on Alpine's default PATH. The control-marker guard already made a grep that could not read the libraries fail the job rather than pass it, so this closes a false-failure path, not a false-pass one. Also corrects the design note: six of the ten targets are non-Windows and get the nm reading, not eight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two candidates covered a single-config generator on Unix and a multi-config one on Windows, but not Ninja on Windows, where the executable is perf-context-probe.exe at the top of the build directory. That left the step depending on MSYS implicitly resolving the .exe suffix for [[ -x ]]. All three locations are now tried explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both scripts were mode 644 and took five and four required environment variables with no usage output, so running either directly failed with either "permission denied" or bash's own "parameter null or not set" naming one variable at a time. They are now executable, report every missing variable at once with a usage block, and derive what can be derived: WORKSPACE from the script's own location, VCPKG_ROOT and VCPKG_CMD from WORKSPACE, PROBE_DIR from the repository root, SCRATCH from mktemp and RUN_PROBE from its only sensible local default. Those derivations produce exactly the values the workflow passes explicitly, so CI behaviour is unchanged. Running it by hand then exposed a real defect. vcpkg treats an installed superset as satisfying a request, so `vcpkg install rocksdb` against a tree already holding rocksdb[no-perf-context] reports "already installed" and does nothing — staging the PerfContext-disabled library at the enabled path. The staging hash check caught it, which is the check working, but the job failed where it should have succeeded. The enabled install is now preceded by a tolerant `vcpkg remove rocksdb`, so it is authoritative about its own feature set rather than depending on the tree being pristine. CI clones vcpkg per job and never saw this; a re-run, a warm tree or a local invocation does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cb1kenobi
marked this pull request as ready for review
October 8, 2026 21:21
This was referenced Oct 9, 2026
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.
⊙ Problem
#26 Compile out RocksDB PerfContext to remove per-comparison TLS overhead sets
-DWITH_PERF_CONTEXT=OFFin the overlay portfile, so the single library every release publishes has PerfContext compiled out. That is a one-way choice for every consumer of the archives:get_perf_context()counters become permanently zero,PerfContext::ToString()returns an empty string, androcksdb.db.mutex.wait.microsis never recorded. Published releases are immutable, so anyone who later needs the counters — a support engineer diagnosing read amplification on a shipped build, say — has to build RocksDB and rocksdb-js from source. #26's own description raises this as an open question rather than a settled decision.💡 Solution
Each of the ten release archives now contains two RocksDB static libraries instead of one. The archive count, the release naming, the revision handling and the publication behaviour are all unchanged.
lib/librocksdb.a,lib/rocksdb.liblib/no-perf-context/librocksdb.a,lib/no-perf-context/rocksdb.libOne copy of the public headers, one set of compression dependency libraries, and one CMake package and pkg-config file are shared between them. Both libraries are built from the same source, the same patches, the same architecture and the same configure options, differing only in
WITH_PERF_CONTEXT.The compatibility story is the thing to read carefully. Relative to the releases published today,
lib/librocksdb.akeeps exactly the meaning it has always had. Relative to #26's branch, this PR moves the default path back to the instrumented library — so #26's measured gain is no longer obtained by linking the default path. It is obtained only once rocksdb-js selectslib/no-perf-context/. That is a deliberate trade: the default stays compatible, and the consumer opts in.rocksdb-js is unmodified here, as scoped. It links the prebuild directly rather than through the CMake package, so selecting the variant is a change to the one RocksDB library path it hands the linker —
lib/no-perf-context/librocksdb.ain place oflib/librocksdb.a, with its include and dependency-library flags untouched. Archives published before this change have nolib/no-perf-context/directory, so a consumer asking for it must fail loudly rather than fall back tolib/librocksdb.aand silently link the library it was trying to avoid. The README states this.⚖️ Alternatives
perf_contextis not athread_localat all — this attacks the actual root cost rather than the counters. Rejected on a fact: the macros inmonitoring/perf_context_imp.hare used at 293 sites in 37 files in v11.8.1, so it is a permanent downstream fork of a core header that every RocksDB upgrade must re-validate, against an upstream option that already exists.84e36ef9…andcac1ee72….share/tree and a second set offind_dependencycalls, for one consumer that links the file path directly. Documented in the README instead.WITH_PERF_CONTEXT:BOOLcache-entry check to catch an upstream rename — replaced, not rejected as unworkable. The planning review held that CMake's-Dcreates a cache entry whether or not the project declares the variable, so the check could not detect a rename; that is half right, since an undeclared-Dlands asUNINITIALIZEDand a declaredoption()asBOOL. The replacement reads the generated build system for the-DNPERF_CONTEXTthe option exists to produce, which checks the effect rather than the declaration.🔧 Changes
vcpkg-overlays/rocksdb/vcpkg.jsonno-perf-contextfeature — the entire variant-selection mechanism.vcpkg-overlays/rocksdb/portfile.cmakeWITH_PERF_CONTEXTthroughINVERTED_FEATURESso the option is derived once, not written twice. Adds a compression-feature assertion and the-DNPERF_CONTEXTread-back between configure and install. The hard-coded-DWITH_PERF_CONTEXT=OFF#26 added is gone..github/scripts/build-rocksdb-variants.sh(new, 116 lines)include/trees, and fails before any archive exists if either library is missing, if the staged enabled library moved, or if the two hash alike. Both build jobs call it, so they cannot drift..github/scripts/verify-prebuild-archive.sh(new, 175 lines)nmsymbol reading with tool failures fatal, the probe, skipped explicitly where the runner cannot execute the target, and the three places a probe binary can land across single- and multi-config generators.tools/perf-context-probe/probe.cc(new, 100 lines)PerfLeveland resets, then readsuser_key_comparison_countafter 2000 point lookups and a full iteration.tools/perf-context-probe/CMakeLists.txt(new, 25 lines).github/workflows/build.ymlrun_probeto the matrix, routes both build jobs through the shared script, raises the build timeout to 120 minutes for the second compile, and adds the archive-verification step with a musl variant that runs in the Alpine container, because musl binaries cannot run on the glibc runner and BusyBox supplies neitherbashnor agrepthat can count markers in a binary. #26's symbol-only step is removed, superseded. The release body now describes both variants.README.mdIOStatsContextis unaffected, and how rocksdb-js would select it..github/DESIGN.md(new),DESIGN.md(new)There are no test files: this repository's only test surface is the workflow itself, which is why the verification is build steps rather than a suite.
Product and architecture tour
One portfile, two libraries
What stops the two libraries differing in anything but
WITH_PERF_CONTEXT?Nothing in this change writes a second set of build flags. The variant is a vcpkg feature, so the same portfile runs twice and the only thing that differs between the runs is the one option the feature controls. That makes "configuration-identical" a structural property rather than a second flag list somebody has to keep in sync.
A feature is also part of vcpkg's port ABI hash, which is what makes the second install safe. An environment variable would not be, and the second install could then be served the first one's binary-cache entry — producing an archive with the same library in both places, passing every structural check. Locally the two resolved to
84e36ef9…andcac1ee72…, and only RocksDB was rebuilt: every dependency kept its hash and was reused.Producing one archive
vcpkg install rocksdb, exactly as before this PR.dist/, exactly as before, so the historical layout is produced by the historical code path.vcpkg install 'rocksdb[no-perf-context]' --recursereplaces RocksDB in the same vcpkg root. Dependencies stay installed and are not rebuilt.include/trees must be byte-identical, because the archive ships one copy.dist/lib/no-perf-context/, and the job fails here if either library is missing, if the staged enabled one changed, or if the two hash alike.What a consumer sees
Does an existing consumer have to do anything?
lib/librocksdb.ais the only RocksDB library, and on #26's branch it has PerfContext compiled out.lib/librocksdb.ais unchanged and PerfContext-enabled;lib/no-perf-context/librocksdb.ais the compiled-out build, selected by path.No existing consumer changes.
-L<prefix>/lib -lrocksdbkeeps resolving to the enabled library,find_package(RocksDB CONFIG)andpkg-config rocksdbboth describe it, and the shared headers compile against either. The alternate library is opt-in, by naming its path.The consequence worth ruling on is that #26's performance gain is now opt-in too. On #26's branch the default path is the fast one; here it is the compatible one. Nothing in production gets faster until rocksdb-js links
lib/no-perf-context/, which is deliberately out of scope for this PR and is the rollout question in the description.Which check reaches which target
The archive is immutable once published and the two files look alike — so what proves which is which, on targets whose binaries the runner cannot even run?
Verification coverage by target
Four independent checks, deliberately overlapping. Only the counter-name comparison reaches all ten targets, which is why the two cross-compiled Windows targets rest on it.
flowchart LR cfg[Configure read-back] --> all[All 10 targets] names[Counter-name count] --> all nm[nm symbol check] --> six[6 non-Windows] probe[Behavioural probe] --> seven[7 native]The configure read-back runs inside the portfile, between configure and install, and asserts
-DNPERF_CONTEXTis present exactly when the feature is on. It exists because vcpkg only warns when a-Dnames a variable the project never declared, so an upstream rename ofWITH_PERF_CONTEXTwould otherwise flip the variant in silence. Verified by renaming the option upstream, which failed the build in 2.1 seconds, before any compilation.The counter-name check is the one that needs no toolchain and no matching architecture. Perf-counter names reach
.rodataonly throughPerfContext::ToString(), whichNPERF_CONTEXTcompiles away. It is a count comparison rather than a presence test because the same name is also aPerfContextBasefield, and vcpkg compiles MSVC release objects with/Z7, so on Windows CodeView type information carries that name into the.libwhichever variant it is. Both variants declare the struct identically, so the literal is exactly the enabled library's margin. A presence test would have failed all four Windows targets on their first run.Every check asserts in both directions, because a one-sided check still passes when the two libraries are swapped. A control string present in both libraries guards the search itself: a
grepthat could not read the files would otherwise report zero matches, and zero is the answer an absence test is looking for.✅ Verification
Route: a real local build of both variants for
darwin-arm64(macOS 26, AppleClang 21, RocksDB 11.8.1, vcpkg pinned at 2026.06.24), packaged into an archive, then that archive verified by the same script CI runs — plus eight fail-closed tests.Build. Both variants built from the one portfile. vcpkg resolved them to distinct ABI hashes (
84e36ef9…enabled,cac1ee72…disabled) and rebuilt only RocksDB for the second install, reusing every dependency.rocksdb[no-perf-context]retained all five compression default features. The portfile reportedWITH_PERF_CONTEXT=ON, NPERF_CONTEXT defined=FALSEandWITH_PERF_CONTEXT=OFF, NPERF_CONTEXT defined=TRUE. Installed public headers compared byte-identical.Archive.
lib/librocksdb.a27,848,096 bytes;lib/no-perf-context/librocksdb.a27,725,440 bytes. Verification against the extracted archive: counter-name occurrences 1 vs 0;rocksdb::perf_contextundefined-symbol references 59 vs 0 (the 59 matches what #26 measured on the shipped macOSv11.8.1prebuild); probeuser_key_comparison_count40,959 vs 0.Archive size. 94,658,072 → 95,885,400 bytes, +1,227,328 bytes (+1.3%), 90.3 MiB → 91.4 MiB, measured by packing the same
dist/tree with and without the second library. The two libraries are nearly identical, andxz -9's dictionary spans both, so the cost is far below a second library's size.Fail-closed tests. Each of these was built and run, and each failed the job: the same library staged twice; the disabled library missing; the two libraries swapped; a zero-length library (caught by the control-string guard, which is what stops a failed search reading as an absence); an upstream rename of
WITH_PERF_CONTEXT(caught at configure, in 2.1s, before any compilation); a compression feature dropped from the port's defaults; and — simulating Windows/Z7— the counter name appended to both libraries of a Windows-shaped archive, which passes, and to the same pair swapped, which still fails.Lint.
shellcheckclean on both new scripts.actionlintonbuild.ymlreports 12 pre-existing shellcheck findings, all in code this PR does not touch, down from 18 on the base branch; none in the new steps.What did not run, and why it cannot run here.
build.ymltriggers only onscheduleandworkflow_dispatch, so this PR gets no CI at all —gh pr checks 27reports none, and a green PR is not evidence of anything. The nine targets other thandarwin-arm64are therefore unexercised: both musl container paths, all four Windows targets, and the probe on the six other native targets. The only way to exercise them is aworkflow_dispatchrun from this branch, and that publishes a prerelease GitHub release, which is outside what I was asked to do here. The rocksdb-js test suite was not run against these libraries, and no hot-path benchmark was repeated — #26 owns that measurement and this PR changes neither library's code.🚧 Remaining limitations
lib/no-perf-context/, Compile out RocksDB PerfContext to remove per-comparison TLS overhead #26's measured gain is not realised by anything in production — see the rollout call below.windows-arm64andwindows-arm64-static-mdcannot run a probe on an x64 runner, anddarwin-x64cannot run one on an arm64 runner. They are covered by the configure-timeNPERF_CONTEXTguard and the counter-name check;darwin-x64additionally bynm./Z7behaviour were verified from vcpkg's toolchain file and simulated locally, but no real.libhas been through it. It fails loudly rather than silently if the margin does not hold.README.mdrather than in HarperFast/documentation. If rocksdb-js later exposes a user-visible choice between the variants, that is where the docs change belongs.🤖 Generated by Claude (Anthropic); posted via @cb1kenobi.
Related PRs: #26 overlaps (this PR is stacked on it — it targets
kris/disable-perf-contextand must merge into it, not intomain), #13 independent (merged tomain; touches the patch-preparation step, not the build or verification steps changed here —git merge-tree origin/main HEADis conflict-free), #24 independent (same file, same reason, also conflict-free)Complexity: complicated
Review-Coverage: authored=claude; ran=gemini,codex,cursor-composer,cursor-muse; adjudicated=domain; blocked=cursor-grok(failed); declined=cursor-kimi; rounds=4; full=2 @ a4e0f72
Review-Attention: deep ~35m (critical: build-rocksdb-variants.sh, portfile.cmake +1; decisions: enabled-default-path, one-archive-two-libs, windows-arm64-evidence, disabled-debug-build, do-less-alternative) @ a4e0f72