1
0
mirror of https://github.com/facebook/rocksdb.git synced 2026-10-23 08:09:02 +03:00

1974 Commits

Author SHA1 Message Date
Yoshinori Matsunobu
d6a7847d37 Add a DB option to log long-held global mutexes and release sites (#15436)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15436

## Why
Long-held global mutexes can stall foreground and background work. Existing mutex statistics measure acquisition waits, not continuous ownership. This adds opt-in logging to identify the mutex, owning thread, operation and release site.

## Enable for an investigation
Provide an INFO logger and shared `Statistics` object when opening the DB, then:
- Set the shared statistics level to `StatsLevel::kAll`.
- Set `mutex_hold_log_threshold_us` through `DB::SetDBOptions`; `100000` means 100 ms.
- Restore threshold 0 and the previous statistics level afterward.

The threshold defaults to **0 (disabled)**. `kAll` alone does not enable these hold probes; threshold 0 leaves existing statistics timers unchanged.

## Output
Illustrative records, not production observations:
```
SLOW_GLOBAL_MUTEX mutex=DBImpl::mutex_ mutex_addr=0x12340000 owner_thread_id=12345 thread_operation=Compaction hold_us=200000 threshold_us=100000 observed_end_us=1792205400000000 buffer_dropped_records=0 unlock=db_impl_compaction_flush.cc:4660 BackgroundCallCompaction()
SLOW_GLOBAL_MUTEX mutex=DBImpl::wal_write_mutex_ mutex_addr=0x12340100 owner_thread_id=12346 thread_operation= hold_us=35000 threshold_us=25000 observed_end_us=1792205401000000 buffer_dropped_records=0 scope_exit=db_impl_write.cc:2250 PreprocessWrite()
```
The first proves a completed 200 ms DB-mutex ownership interval on a thread tagged `Compaction`. The second uses a 25 ms threshold; an empty operation tag means none was available. Neither record reports acquisition wait or proves the cause of a stall.

Release attribution uses compiler-embedded strings available in optimized, stripped builds, with a GCC/Clang fallback where standard source locations are unavailable. Direct unlock and condition wait identify their call sites; scope exit and temporary unlock identify guard declarations, not closing braces. No stack unwinding or symbol lookup.

## Scope and safety
- Track six selected mutexes: DB, WAL, point-lock wait graph, two prepare-log bookkeeping mutexes and transaction name map. This is not comprehensive synchronization tracing.
- Acquisition waits and condition-variable release gaps are excluded from hold duration. Configuration is pre-first-use; repeated setup retains the first valid context.
- Sample at most once per mutex per second, with eight deferred records per thread. `buffer_dropped_records` counts capacity overflow only; rate limiting, pause and emission contention can omit samples.
- Emit after covered outer lock scopes release. Emission is nonblocking best-effort; shutdown drains/pauses emission and stops diagnostics before the owned logger closes. Closing from that logger's own callback is unsupported.
- No data-file format change. The mutable option is persisted in OPTIONS and wired into bench/stress tools. Typed C binding is deferred; string DB-option APIs are available.

Disabled bookkeeping still has a cost. Release A/B measurements found about 3 ns added per selected mutex pair at level 1, and about 70 ns for hold timing over `kAll`/0. Get and reused-iterator Seek showed no resolved small probe effect; fresh iterator+Seek at 16 threads showed +8.7% CPU/query and -9.4% QPS. No logs were emitted in those benchmarks; logging cost and broader workload qualification remain open.

Reviewed By: xingbowang

Differential Revision: D125790955

fbshipit-source-id: 1096163f3bd25ffee585dc6ec8ebfd7f1759851a
2026-10-22 09:50:20 -07:00
zaidoon
553b52b4a7 Wire IndexFactory through block-based tables (#14963)
Summary:
Build block-based table indexes through `IndexFactory`, with modes for writing both indexes or only the custom index and a per-read index selector. Built-in reads retain their existing cache and prefetch paths.

The review fixes preserve the existing UDI behavior at the migration and rollback boundaries:

- Reopening an SST with index caching enabled reuses the serialized custom block. Each table reader constructs its own factory reader over those bytes and passes `NewReader()` a `Slice` view stored in the table reader. That view stays valid for the reader's lifetime, so a legacy reader can advance it or keep a pointer to it without changing the cached block. The view and the raw block are destroyed after the custom reader.
- `IndexFactoryReader::ApproximateMemoryUsage()` keeps its documented meaning: the reader's own allocations, excluding the serialized block. `MemoryUsageIncludesIndexBlock()` defaults to false. A reader whose estimate includes the block opts in, and the wrapper subtracts the block size from that estimate.
- Copying factory options and clearing a legacy UDI flag now rolls back that flag's policy. Legacy-derived options keep the requested default and resolve their effective mode without changing the inputs. Explicit factory modes retain precedence through cloning, failed updates, reconstruction, and the C API, including an explicitly selected `kStandardDefault`.
- OK empty custom-index output still produces a zero-size meta block and reads through the full standard index. This covers point data and files containing only range tombstones. Custom-only mode rejects empty output because it has no standard fallback.
- Default primary reads retain the table factory's precedence over a legacy per-read factory pointer. Explicit `kPreferCustom` still checks a supplied factory name; `kBuiltin` ignores that pointer.
- OPTIONS files write equivalent legacy booleans for `kStandardDefault`, `kStandardRequired`, and `kCustomDefault`. Older releases retain those policies when loading newer files with `ignore_unknown_options=true`; the explicit-mode marker remains authoritative for current readers.

Custom-only SSTs carry a footer feature bit so older readers reject them. Reject unsupported filter/index combinations before constructing filters. Trie size estimates account for shared prefixes, and block offsets or sizes beyond the 32-bit format limit return `NotSupported` instead of truncating.

## Safety and compatibility

- Custom-only files require format version 6 or later and the factory. Rewrite every custom-only SST with a full standard index before removing the factory or downgrading. The other modes have no equivalent legacy configuration beyond the three listed above.
- A raw C++ options struct cannot distinguish an explicitly assigned `kStandardDefault` from its default value. Configure the factory to make that choice explicit. `GetEffectiveIndexMode()` reports the resolved policy while preserving legacy inputs for copy-and-clear rollback.
- A custom factory with user-defined timestamps is rejected in every mode except `kStandardOnly`, which ignores the factory.
- Trie construction rejects a data-block offset or size greater than `UINT32_MAX`, including in `kStandardDefault`. That can fail flush, compaction, or `SstFileWriter::Finish()`; total SST size above 4 GiB alone is not the rejection condition. File-size targets do not split L0 or `SstFileWriter` outputs.
- Custom-index cache hits, misses, and adds are reported under `BLOCK_CACHE_INDEX_*` instead of `BLOCK_CACHE_DATA_*`. The existing cache setting still controls ownership and cache use.
- A table reader now charges a custom index block it owns (`cache_index_and_filter_blocks=false`); before, that block was not charged anywhere. Cached blocks are still charged only to the block cache. A reader whose `ApproximateMemoryUsage()` includes the serialized block, such as IntegerTrie or TimeSeries, must override `MemoryUsageIncludesIndexBlock()` to return true, or that block is charged twice.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14963

Test Plan:
For this commit, on macOS: `table_test`, `trie_index_test`, `trie_index_db_test`, and `block_based_table_reader_test` pass on the full stack (11,416 cases), and the full `table_test` passes at this PR's head alone (6,969 cases). The four custom-reader compatibility tests pass 100 times with `ASSERT_STATUS_CHECKED=1` and `COERCE_CONTEXT_SWITCH=1`, and 84 UDI table cases pass under ASAN with stack-use-after-return detection. Release-mode syntax checks with `-DNDEBUG -fno-rtti` on the changed sources, source checks, formatting checks, and `git diff --check` pass.

Two new regressions cover the latest review, and both were checked failing before the fix:

- `RetainedReaderSliceOutlivesTableOpen`: a reader keeps a pointer to the `Slice` it received, the stack is overwritten after open, and the reader then serves a seek and a full scan. It covers all three custom-index modes with owned and cached blocks, including a cached reopen. Without the fix, the retained `Slice` no longer matched the block.
- `AuxiliaryReaderMemoryIsFullyCharged`: a reader that implements only the older reader methods reports a fixed allocation of half and twice the raw block size. Table usage must grow by that amount with owned and cached blocks, and a strict cache sized just below the required reservation must reject the open with `MemoryLimit`. Without the fix, usage fell short by `min(allocation, raw block size)` and the strict-cache open succeeded.

The consuming-reader and strict-cache accounting tests are kept, now on shared helpers.

From the previous revision: 498 focused C++ cases passed (82 table/factory, 156 trie, and 260 trie DB), along with the C API test and generated C API verification. Coverage includes both comparators, copied-options rollback, C factory reconstruction, explicit modes, OPTIONS serialization, SST Get/iteration, point and range-only flushes, compaction, reopen, cache disabled/enabled/evicted, and legacy factory precedence. The rollback, empty-output, primary-selector, and OPTIONS regressions were checked failing before their fixes. The cache test measures file reads at the custom block's actual offset: one on a cold open, zero on cached reopens, and one after eviction or with caching disabled. Disabling custom-block cache reuse makes that assertion fail.

The full suite and Linux/Windows/TSAN coverage are left to CI. Earlier full-suite runs had failures/timeouts (173/2095 jobs normally and 152/2088 with Status checking); that broad suite was not rerun for this commit.

## Series

Previous: https://github.com/facebook/rocksdb/issues/14962 (landed through import). Next: https://github.com/facebook/rocksdb/issues/14965.

Reviewed By: jaykorean

Differential Revision: D122796629

Pulled By: xingbowang

fbshipit-source-id: 4c9d6f3f548c588a37b08fd94de25e8c0b5ef743
2026-10-21 20:20:38 -07:00
Xingbo Wang
7454655969 Small fix and stress coverage for MANIFEST-bound OPTIONS recovery (#15431)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15431

A filesystem failure during column family creation can leave the MANIFEST and OPTIONS snapshots at different points in the column family lifecycle. Recovery must select the snapshot consistent with the MANIFEST without deleting a newer valid legacy snapshot.

Fix: When the MANIFEST committed an older tracked OPTIONS file, recovery previously rejected the highest-numbered candidate without reading its tracking marker. Writable open then deleted a newer legacy file, or an unreadable highest file, as a rejected tracked orphan; read-only open also reported the older file number. Recovery now inspects every higher candidate. A newer legacy snapshot supersedes the older committed snapshot, while an unreadable higher candidate is retained rather than deleted.

Add: Crash-test invocations randomize `track_options_file_number_in_manifest`. Before writable open can replace evidence, db_stress compares the public latest-OPTIONS choice with read-only recovery, using only currently live column family descriptors and running before requested missing families are appended. After open, it verifies the persisted tracking marker and live column family set. TransactionDB still skips the unsupported read-only probe, but it now reads the MANIFEST first so an existing commit enables the post-open verification. The regression test also checks that writable open preserves the newer legacy file before creating another column family.

Change: Reopen MANIFEST-avoidance checks use the loaded `options_` value directly. This is a behavior-preserving simplification, not a separate correctness fix.

Safety and limitations: There is no API, file-format, or production hot-path change. Ordinary writable open still receives the caller options and complete requested column family set. The extra read-only open is stress-only and runs when tracking verification is active. The pre-open comparison remains skipped for best-efforts recovery, TransactionDB, and stacked BlobDB; post-open verification remains skipped for best-efforts recovery, TTL, and stacked BlobDB. Random crash coverage is probabilistic, and this parent diff does not add randomized column family operations; the dependent lifecycle diff adds that profile.

Reviewed By: pdillinger

Differential Revision: D125029493

fbshipit-source-id: 5e291deff735e3cbe6d7df0513f940e84b20f387
2026-10-20 18:28:13 -07:00
Peter Dillinger
8121c7e254 Expose kLatestSupportedFormatVersion in BlockBasedTableOptions (#15397)
Summary:
Before this change, the highest supported block-based table format_version was only available as the internal constant kLatestBbtFormatVersion in table/format.h. Applications that accept a format_version setting from users or configuration (especially for SetOptions()) had no public way to validate it against the linked RocksDB build before applying it.

This adds the public constant
BlockBasedTableOptions::kLatestSupportedFormatVersion (currently 8) and removes kLatestBbtFormatVersion, redirecting all internal and test uses to the new constant. Supported-version checks and option validation are unchanged: format_version above the constant is still rejected as InvalidArgument, and draft format_versions remain usable only under the TEST-only TEST_AllowUnsupportedFormatVersion() hook.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15397

Test Plan: Same value and same checks, so no new tests. Existing format_version validation coverage (e.g. TableTest.UnsupportedFormatVersionConfigTest) and the FormatLatest test instantiations now go through the public constant.

Reviewed By: rban1

Differential Revision: D125117976

Pulled By: pdillinger

fbshipit-source-id: 67e0c5b7eade4bf649957435aa0b28b187793f7c
2026-10-19 06:32:23 -07:00
Jay Huh
54732b0c8f Tolerate CacheLib startup INFO in crash tests (#15416)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15416

Why the change is required:
A crash test using `secondary_cache_uri=cachelibwrapper://...` constructs and resets CacheLib/Navy. Sandcastle suppresses CacheLib `XLOG(INFO)` by patching the fbcode XLOG header, but commit `831f00db6a47` enabled the exported xplat header unit. The build therefore consumed an unpatched XLOG header, normal CacheLib startup INFO reached stderr, and `db_crashtest` exited 2 before it could accept the intentional whitebox SIGTERM.

Previous behavior:
Any residual child stderr was fatal. `sanitize_known_stderr()` covered expected SIGTERM and io_uring diagnostics but not CacheLib construction/reset INFO, so an otherwise healthy secondary-cache whitebox iteration failed.

New behavior and implementation:
When `secondary_cache_uri` starts with `cachelibwrapper://`, the sanitizer moves only INFO-severity lines from an explicit CacheLib construction/reset source allowlist to stdout. Warnings, errors, fatal logs, unrelated INFO, and identical lines without the CacheLib URI remain fatal.

Safety and compatibility:
Default and non-CacheLib configurations are unchanged. No API, ABI, persisted format, database behavior, or performance-sensitive engine path changes.

___

landed-with-radar-review

Differential Revision: D123695714

fbshipit-source-id: 844ecd2cbf1b3c1415629d787748b5edeb63d456
2026-10-16 19:40:01 -07:00
Josh Kang
014e1c43c1 Apply readability fixes (isolate decls, braces) (#15394)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15394

## Summary

RocksDB's `.clang-tidy` enables several readability checks that existing
code still violates: `readability-isolate-declaration` (476 findings),
`readability-braces-around-statements` (49),
`readability-redundant-string-init` (25), `modernize-use-using` (10),
`misc-definitions-in-headers` (3) and `readability-duplicate-include`
(1). They keep getting flagged whenever nearby lines change. This diff
fixes 553 of the 564; upstream has since fixed the other 11.

What changed:
- Multi-variable declarations such as `std::string a, b;` are split into
  one declaration per variable. Pointer and reference declarators are kept
  per variable.
- Braces are added around single-statement `if`/`else`/`for`/`while`
  bodies, and empty spin loops get an empty `{}` body.
- `std::string s = ""` / `member("")` initializers are removed.
- Remaining `typedef`s in the touched C++ files become `using`. C headers
  and `extern "C"` code are untouched.
- `inline` is added to non-template function definitions in test headers.
- A duplicate `#include "util/udt_util.h"` in `db_impl.cc` is removed.

These are clang-tidy's own fix-its, applied mechanically and then
clang-formatted on the changed lines. Three kinds of broken fix-it output
were repaired by hand. Removing a `member("")` initializer left a dangling
comma in constructor initializer lists (`ExternalSstFileInfo`,
`ColumnFamilyMetaData` and others). The function-type typedefs in
`block_test.cc` came out malformed. Empty `while (cond);` spin loops got a
`{;` body. Splitting declarations also exposed two vectors in
`db_etc2_test.cc` to `performance-inefficient-vector-operation`, so a
`reserve()` was added for each.

The lint bot reports every finding in a file a diff touches. This diff
splits declarations in `options/configurable_test.cc`, so it also fixes
that file's 14 older `cert-err58-cpp` and
`cppcoreguidelines-avoid-non-const-global-variables` findings, which come
from seven mutable file-level maps. Six are now `const` and built where
they are used (function-local statics, or locals in their only user).
The unused `imm_struct_option_info` is deleted.

Compatibility: no behavior change. In public headers, the only edits are
removed redundant `""` initializers, split declarations and added braces.

Reviewed By: rban1

Differential Revision: D123186310

fbshipit-source-id: f9c8e88b00e997db0196f8725c7c8d880cf798da
2026-10-13 14:06:55 -07:00
Josh Kang
4b983da055 Spell out special member functions (rule of five) (#15387)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15387

## Summary

RocksDB's `.clang-tidy` enables `cppcoreguidelines-special-member-functions`,
`cppcoreguidelines-virtual-class-destructor` and the noexcept-move checks,
and clang warns `-Wdeprecated-copy-with-user-provided-dtor`. A full scan
reports 476 findings. In most of them, a class declares a destructor (or
copy operations) but leaves the rest of copy/move implicit. That has two
costs. Readers can't tell whether a class may be copied or moved. And
resource-owning classes keep an implicit copy that would double-free if
anyone ever used it. This diff resolves 453 of the 476.

Approach, picked per class from least to most invasive:
1. An empty user-provided destructor becomes `= default`. This covers most
   interface classes, such as `virtual ~X() {}` -> `= default`. If the
   class is still copied, that would only rename the deprecated-copy
   warning, so the destructor is removed instead when nothing needs it
   (`Customizable`, `TableFactory` and `InternalKeyComparator` inherit a
   virtual one; `IteratorWrapperBase` isn't polymorphic), and
   `MemTableRep::KeyComparator` gets defaulted copy and move.
2. Classes whose copy is already deleted get explicitly deleted move
   operations too. Move was already unavailable, so this only documents
   it.
3. Resource-owning classes that are never copied get deleted copy and move
   operations. Examples: the C API wrapper structs, which run a
   `destructor_` callback (edited in `tools/c_api_gen/c_base.cc` and
   regenerated into `db/c.cc`); `ShardedCache`, which frees its shards;
   iterator wrappers; and RAII handles.
4. Value types that are copied or moved get the missing operations as
   `= default`, which keeps their current behavior.
5. Exception specifications of existing functions are unchanged, except
   for `WriteBatch`'s move assignment (below): no other `noexcept` is
   added or removed. Only the move operations this diff
   newly declares (`Comparator`, `FileSampledStats` and the thread-local
   `Entry`) are `noexcept`; they copy only scalars or atomics.

`WriteBatch`'s copy and move assignment destroyed `*this` and
placement-new'ed a new `WriteBatch` over it. That is undefined behavior
when `*this` is a subclass, and if the copy constructor threw, `*this`
was left destroyed and was later destroyed again. Move assignment now
assigns member-wise and ends in the same state as the move constructor,
which leaves `has_key_with_ts_`, the timestamp-size tracking and the
other fields it doesn't copy at their defaults. `rep_` is swapped through
a temporary, because a plain string move-assignment can keep the old
buffer (e.g. a large batch reset with `batch = WriteBatch()` would keep
its memory). Nothing in it can throw, so it is now `noexcept`. Copy
assignment copies into a temporary and moves it in, so a throwing copy
leaves `*this` unchanged.

Bugs this exposed and fixed:
- `RestoreAfterCopyOrCreateWorkItem` (backup engine): the hand-written
  move constructor and assignment moved only some fields, so `from_file`
  and `to_file` were empty after `push_back(std::move(...))`. A restore
  that hit a checksum mismatch then reported "While restoring  -> : ..."
  without file names. It now uses the compiler-generated moves, which
  are still `noexcept`.
- `ExternalSstFileIngestionJob` lived in a `std::vector`, and its
  destructor frees raw-owned pointers, so the implicit copy used on
  vector reallocation would double-free. Its file batches also point into
  its own inline file storage, so neither a copy nor a move is safe after
  `Prepare()`. Copy and move are now deleted, and `DBImpl` holds jobs
  through `std::unique_ptr`.

Compatibility: no public class loses an operation users can call. Public
interfaces mostly only get `virtual ~X() = default;`.
`WriteBatch`'s move assignment becomes `noexcept`, which is not part of
the mangled name, so the ABI doesn't change.
`SubcompactionJobInfo` and `CompactionJobInfo` intentionally stay C++20
aggregates, so users' aggregate and designated initializers keep
compiling. `Configurable`, `CompareInterface`, `WriteBatchBase`,
`ObjectLibrary::Entry` and `MemTableRep::KeyComparator` declare defaulted
copy and move, and `Customizable` no longer declares a destructor. As a
result, `Configurable` and `Customizable` subclasses that declare no
destructor or copy operations of their own now move their
registered-options list instead of copying it. In-tree, these are the
mutable-options adapters, the table factories, the memory allocators and
test classes; all are held through smart pointers or used in place, and
`Clone()` copies, so none is moved by value.

Not changed (23): `IODebugContext` (custom deep copy plus a
`std::shared_mutex`), `DBWithColumnFamilies` in db_bench (stored in a
`std::vector` with non-movable members), `Snapshot`'s protected virtual
destructor (intended), `ManagedSnapshot`, the `SubcompactionJobInfo` and
`CompactionJobInfo` aggregates, and the 16 noexcept-move-constructor and
noexcept-swap findings on existing functions, including `autovector`'s
move assignment (adding `noexcept` would change their exception
specifications). Copying `ManagedSnapshot` already double-releases the
snapshot. Fixing that needs a real move implementation and is a behavior
change, so it is left for a separate diff.

Reviewed By: rban1

Differential Revision: D123186309

fbshipit-source-id: 6f8e57e233e12199a007407130bb9a1f6d52e8d1
2026-10-12 16:00:09 -07:00
Xingbo Wang
1334e4dd52 Add offline OPTIONS binding repair (#15310)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15310

Why the change is required:

The committed OPTIONS number in MANIFEST gives persisted-options readers a
precise source of truth. If that file is missing or corrupt, or an operator
must deliberately replace it with a known-good snapshot, persisted-options
loading can fail before a normal writable open can publish a replacement.
RocksDB therefore needs a supported offline recovery path that obeys the
binding and global file-number rules.

Previous behavior:

ldb update_manifest could rewrite file-temperature metadata but could not
repair an OPTIONS binding. Operators could not safely bind a selected snapshot
while accounting for the current MANIFEST boundary, every file-number
allocation root, recoverable WAL output, and crash durability. Blindly
selecting or reusing an existing OPTIONS number could bind the wrong snapshot
or collide with another RocksDB file.

New behavior:

ldb update_manifest --rebind_options_file --options_file=OPTIONS-<number>
performs an offline repair from an explicitly selected trusted snapshot whose
tracking marker is enabled. Optional --file_number_paths arguments identify
external allocation roots that are not serialized in OPTIONS.

The command locks the DB and validates that the selected OPTIONS snapshot
contains every live MANIFEST column family. Extra OPTIONS CFs are accepted as
a safe superset and are filtered by binding-aware persisted-options APIs; a
snapshot missing any live CF is rejected. The command raw-copies the selected
bytes to a newly allocated canonical OPTIONS number, syncs that file and its
directory entry, and commits the new number with one MANIFEST record. It
rewrites the current MANIFEST only through the last completely decoded record,
appends the commit and next-file boundary to a temporary copy, syncs it,
atomically renames it over the original, and syncs the MANIFEST directory.

The crash contract has one publication point. Before MANIFEST replacement,
the previous commit remains authoritative and the copied tracked OPTIONS file
is only an ignorable orphan. After replacement, the new snapshot is
authoritative.

Implementation:

- Parse the selected snapshot for validation but copy it byte-for-byte so
  unknown or newer option fields are preserved.
- Replay MANIFEST to recover live CF membership, the committed number, next
  file number, and last valid logical-record boundary; discard a torn physical
  tail during the bounded-memory rewrite.
- Scan the DB root, WAL/archive directories, serialized roots, and
  operator-supplied roots for allocated numbers.
- Parse recoverable WALs and reserve conservative headroom for recovery
  outputs, missing-column-family creation, persistent statistics, asynchronous
  WAL creation, MANIFEST rollover, and subsequent writable-open allocations.
- Reject file-number overflow, missing allocation roots, corrupt or unreadable
  WALs, invalid input names, missing-live-CF snapshots, and simultaneous
  temperature rewriting.
- Preserve the existing update_temperatures behavior and result text.

Safety boundaries and unsupported cases:

- This is an explicit offline mutation. It never auto-selects a snapshot and
  refuses to run while another process holds the DB lock.
- The selected input must be a canonical OPTIONS-<number> basename in the DB
  directory, must parse successfully, must enable MANIFEST tracking, and must
  contain all live MANIFEST CFs. Extra CFs are allowed but missing live CFs are
  not.
- The command does not repair snapshot contents or alter topology.
- OPTIONS does not serialize all DB/CF data roots. Operators must provide every
  external root through --file_number_paths; the command fails closed when a
  supplied or inferred root cannot be scanned.
- Conservative allocation headroom can reject a repair near kFileNumberMask
  even when a particular future open would allocate fewer files.
- Normal DB open and core publication do not depend on this maintenance
  command.

Validation coverage and known limitations:

Coverage includes parsing and incompatible modes, the DB lock, tracking and
membership validation, safe-superset acceptance, missing-live-CF rejection,
byte-preserving copy, torn MANIFEST tails, one-record commit publication,
file-number exhaustion and writable-open headroom, external and missing roots,
WAL recovery bounds, asynchronous WAL and persistent-stat allocations,
MANIFEST rollover, writable reopen, and later CF creation. The end-to-end test
confirms safe-superset rebinding and rejection of a higher tracked snapshot
that omits a live CF, verifies the final committed MANIFEST number, and runs
the real ldb scan --try_load_options subprocess. Cross-platform CI is not part
of local validation.

Reviewed By: pdillinger

Differential Revision: D123104846

fbshipit-source-id: 8cf7c54c1c7a360411034469614eed8aaffdcf8e
2026-10-11 13:13:46 -07:00
Josh Kang
da82001d81 Mark overriding methods and destructors override (#15366)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15366

## Summary

RocksDB's `.clang-tidy` enables `modernize-use-override`, and clang
reports `-Winconsistent-missing-destructor-override` when a class marks
its other overrides `override` but not its destructor. A full scan finds
190 `modernize-use-override`, 100
`inconsistent-missing-destructor-override` and 2 `suggest-override`
findings in existing code. Each one comes back whenever a nearby line is
edited. They also leave classes where it isn't obvious which methods are
overrides and which introduce new virtuals.

This diff marks every overriding member function and destructor
`override` and drops the redundant `virtual` on them. It also changes
`final override` to plain `final`, following the check's default. Methods
that introduce a new virtual keep `virtual`. Most edits are clang-tidy's
own fix-its, applied mechanically and then clang-formatted.

Compatibility: no signatures change. In public headers the only effect is
`override` on existing overrides (mostly destructors, plus
`Cache::Name()` and `Cache::GetPrintableOptions()`, whose base
declarations are unconditional), so user code that subclasses these types
is unaffected. Every touched class now marks all of its overrides
consistently. This matters because a partially-marked class would make
clang emit `-Winconsistent-missing-override` in user builds.

Reviewed By: rban1

Differential Revision: D123186308

fbshipit-source-id: 1f489ea32d938e3ec0faf9abc4f7ff49e042d37a
2026-10-09 14:39:14 -07:00
Peter Dillinger
72beaf8dfb Update RocksDB main for 11.12 release (#15372)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15372

Why: keep internal RocksDB main ready for 11.13.0 development while the 11.12 release is cut.
Previous behavior: Conveyor opened a GitHub pull request for this update, which could be missed and was not stacked with the internal release diff.
New behavior: Conveyor owns an internal Phabricator diff that the release-import diff stacks on top of. The release manager exports this diff manually with ExportIt.

Implementation:
- Bump version.h from 11.12.0 to 11.13.0
- Add `11.12.fb` to `check_format_compatible.sh`
- Sync HISTORY.md `## 11.12.` section from `11.12.fb`
- Delete 24 consumed `unreleased_history/` note file(s) from main

Safety and unsupported cases:
- Only version.h, check_format_compatible.sh, HISTORY.md, and consumed release-note files are allowed.
- Open legacy GitHub update-main PRs block this flow; this automation never modifies, exports, or lands them.
- Concurrent direct CLI invocations are unsupported; Conveyor serializes the Release Cut node to one active run.
- The tool refuses unrelated commits, mismatched release metadata, and non-canonical GitHub repositories.

Known limitations:
- ExportIt remains manual. Bot create permission for internal_repo_rocksdb/repo is a rollout prerequisite.

Reviewed By: archang19

Differential Revision: D124370612

fbshipit-source-id: fcd3864695f118bf8be316878d92be5bf8ace43c
2026-10-09 12:32:51 -07:00
Josh Kang
928527b869 Fix bugs and false positives from clang-analyzer/bugprone (#15325)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15325

## Summary

RocksDB's `.clang-tidy` enables the `clang-analyzer-*` and `bugprone-*`
checks. A full scan of the repo still reports 78 findings from these
checks in existing code. Each time a nearby line is edited, they come
back as noise, and a few of them are real defects. This diff resolves 73
of the 78; upstream has since fixed one more, and the four left are
listed under "Intentionally not changed". Some fixes repair real bugs.
The rest make the code's intent explicit to the analyzer without
changing behavior.

Real bugs fixed:
- `port/mmap.cc`: `MemMapping::operator=(MemMapping&&)` ran the
  destructor on `*this`, `memcpy`'d over a non-trivially-copyable object,
  then placement-new'd the source. That is undefined behavior, and `*this`
  can be the base subobject of a `TypedMemMapping`. It now moves the
  members one by one, and the previous mapping is released through a
  local `MemMapping`.
- `port/mmap.h`: `TypedMemMapping::operator=(MemMapping&&)` had no
  `return *this;`, so calling it was undefined behavior.
- `cache/typed_cache.h`: `if constexpr (sizeof(TCreateContext) > 0)` is
  always true, so the branch meant for empty create contexts (for example
  `BlobContentsCreator`) could never run. The check is now
  `!std::is_empty_v<TCreateContext>`. Existing instantiations still call
  the same `Create` function.
- `cassandra_row_merge_test.cc`: the test reused a vector after
  `std::move`-ing it into `RowValue::Merge(std::vector<RowValue>&&)`. That
  only worked because `Merge` happens not to consume its input. The test
  now rebuilds the input.

Behavior-preserving changes:
- Virtual calls in constructors and destructors are now explicitly
  qualified (for example `DBImpl::CloseImpl()` in `~DBImpl`). During
  construction and destruction, dispatch already resolved to these
  functions.
- Stale `/*name=*/` argument comments now match the parameter names.
- `unique_ptr::release()` after a successful cache `Insert` (where the
  cache takes ownership) uses `[[maybe_unused]] T* released = x.release();`,
  the pattern already used in `table_cache.cc`.
- Removed `class Mutex;` and `class StaticMeta;` forward declarations
  that sat in the wrong namespace.
- Added default member initializers for the
  `TablePropertiesCollectorFactory::Context` fields that its default
  constructor left uninitialized.
- Smaller cleanups: a redundant `cond ? 3 : 3` in a test,
  `sizeof(std::string)` in a memory estimate, and an explicit
  `Cleanable` base move via `up_cast`. Tests that intentionally use
  moved-from objects now carry `NOLINT(bugprone-use-after-move)`,
  matching the existing convention.

Intentionally not changed: enum casts in tests that check invalid-value
handling; three `clang-analyzer-core.NullDereference` reports the
analyzer can't prove safe (the C API timestamp out-parameters in
`db/c.cc`, which are only written when the caller asked for a
timestamp, and the waiter-list walk in `util/async_file_reader.cc`);
bit-flag enums in public headers (`FileOpenContract`,
`OptionTypeFlags`), where OR-combined values are valid; ownership
transfers the analyzer can't see across translation units
(`partitioned_index_reader.cc`, `fs_posix.cc`); and the intentional
two-separator check in `file/filename.cc`.

Reviewed By: rban1

Differential Revision: D123186307

fbshipit-source-id: 623e871286b1c73263b1309cb194692194199e0f
2026-10-08 17:19:47 -07:00
Ranjan Banerjee
dec67990f0 Add db_stress coverage for WriteBufferManager flush policies (#15048)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15048

Adds a validated db_stress flag for selecting the WriteBufferManager flush policy and randomizes it in db_crashtest. Crash-test generation covers all three policies; kFlushLargestAcrossDBs automatically uses two DBs sharing one manager so the cross-DB path is exercised.

Reviewed By: pdillinger

Differential Revision: D114638570

fbshipit-source-id: 00f664445614e083a4c59d4e2bb0896731d4bea7
2026-10-07 16:30:03 -07:00
Ranjan Banerjee
0561153b73 Add configurable largest-memtable flush policies to WriteBufferManager (#15047)
Summary:
Motivation

A WriteBufferManager can be shared by many RocksDB instances and column families. Under the existing policy, the DB whose writer first observes memory pressure flushes its own oldest mutable memtable. That DB might own only a small fraction of the reclaimable memory, so workloads with many DBs or CFs can generate many small flushes while the largest memory consumer remains untouched. Scanning every registered DB while holding process-wide or DB mutexes would improve selection accuracy but would serialize unrelated write paths and is not viable for deployments with thousands of DBs.

Previous behavior

kFlushOldest was the only behavior: the write-group leader that noticed WBM pressure synchronously selected the oldest eligible mutable memtable in its own DB. The WBM had no per-DB view of reclaimable memory and could not direct reclamation to another DB sharing the same budget.

New behavior

This change adds configurable WriteBufferFlushPolicy modes:

- kFlushOldest preserves the existing behavior and remains the default.
- kFlushLargest selects the largest eligible flushable column family in the writing DB.
- kFlushLargestAcrossDBs selects the DB advertising the most reclaimable memory across all DBs sharing the WBM, then flushes its largest eligible CF. Atomic-flush DBs advertise and flush the aggregate eligible memory across their CFs.

High-level implementation

Each writable DB registers a lightweight FlushInitiator with the shared WBM. Per-DB accounting publishes two approximate values using atomics: total mutable memory and the largest flushable CF contribution, including eligible immutable memtables waiting for flush. Allocator reporting is batched, so the allocation hot path avoids registry scans and DB mutexes.

A dedicated WBM coordinator thread maintains an approximate ranked snapshot of registered DBs. Refreshes are driven by accounting changes, threshold crossings, and periodic pressure checks. Ranking and O(number of DBs) work happen on that coordinator thread; writers only update counters and signal the coordinator. Registration lifetime is protected independently so DB close can safely drain an in-flight handoff.

Threading and flush handoff

The coordinator reserves and pins one ranked candidate at a time, without holding a registry lock while invoking the DB. The target DB uses TryLock on its DB mutex and rejects the handoff if it is closing, paused, stopped, stalled, already handling a WBM job, or no longer has flushable memory. Rejected candidates do not block consideration of the next DB.

An accepted handoff queues one LOW-priority WBM job. The LOW worker joins the target DB write thread and revalidates eligibility before switching the memtable. The resulting ordinary flush is then scheduled through RocksDB's existing flush machinery on the HIGH-priority flush pool. Keeping the write-thread join off the requesting writer avoids making one DB's write path wait on another DB's mutex or write queue.

Only one manager-wide remote handoff is active at a time. Completion or cancellation clears the handoff and requests a rerank. If memory reaches the hard limit while remote coordination is delayed, a rate-limited local fallback lets a writer seal a local memtable to preserve forward progress. Write stalls, shutdown, WaitForCompact, policy changes, and DB deregistration explicitly drain or reject outstanding handoffs.

Compatibility

The legacy constructor and kFlushOldest behavior remain unchanged unless a new policy is explicitly selected. There are no on-disk format changes.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15047

Reviewed By: xingbowang

Differential Revision: D112396106

fbshipit-source-id: 3049adc7034dd4e00d74d7d86e1d19004292d299
2026-10-06 16:26:51 -07:00
Peter Dillinger
1c490a4a4d Fix "ancester" spelling; deprecate TablePropertiesNames (#15335)
Summary:
Follow-up to https://github.com/facebook/rocksdb/issues/15304, which renamed `TableProperties::creation_time` to `oldest_ancestor_time` but left the long-standing "ancester" misspelling everywhere else, so the same concept was spelled two ways (e.g. `TableProperties::oldest_ancestor_time` vs. `FileMetaData::oldest_ancester_time` and `SstFileMetaData::oldest_ancester_time`). This change makes the spelling consistent and addresses remaining review feedback on https://github.com/facebook/rocksdb/issues/15304.

Change:
* All internal identifiers and comments now use "ancestor": `FileMetaData::oldest_ancestor_time`, `CompactionServiceOutputFile::oldest_ancestor_time`, `kUnknownOldestAncestorTime`, `TryGetOldestAncestorTime()`, `Compaction::MinInputFileOldestAncestorTime()`, the `kOldestAncestorTime` manifest tag enum, and related locals and test sync point names.
* Public API: `SstFileMetaData::oldest_ancestor_time` is the new name; `oldest_ancester_time` remains as a deprecated alias sharing storage via an anonymous union (same approach as `TableProperties::creation_time`, so size, layout, and copy semantics are unchanged).
* Public API: `TablePropertiesNames::kOldestAncestorTime` is the new name; `kCreationTime` remains as a deprecated `static const std::string&` alias. A static reference member has no per-object cost and binds at constant initialization, so it adds no static-init-order exposure.
* `TablePropertiesNames` as a whole is marked DEPRECATED for public use. An fbsource-wide BigGrep found no references outside RocksDB and its vendored copies, and it is only meaningful to table format internals; users should read the corresponding `TableProperties` fields.

Fix:
* The `table_file_creation` event log entry had the `oldest_key_time` and `newest_key_time` values swapped since https://github.com/facebook/rocksdb/issues/13083 (v10.0). Now each label is paired with its own value.

Also: tools/c_api_gen/auto_simple_bindings.py resets the pending anonymous union field list after use and skips unnamed members inside the union (review suggestion); generated C API output is unchanged.

Compatibility: names that are persisted or externally parsed intentionally keep the old spelling: the on-disk property name `rocksdb.creation.time`, the `"oldest_ancester_time"` key in remote compaction serialization, and the `oldest_ancester_time` / `OldestAncesterTime` labels in `VersionEdit` DebugString/DebugJSON (ldb manifest_dump output). The manifest tag value is unchanged. HISTORY.md is left as historical record.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15335

Test Plan: DBTablePropertiesTest.CreationTimeAliasesOldestAncestorTime now also verifies `TablePropertiesNames::kCreationTime` and `kOldestAncestorTime` refer to the same object, and new DBTablePropertiesTest.SstFileMetaDataOldestAncesterTimeAlias verifies the two `SstFileMetaData` names share an address and that writes through the old name are visible through the new one. Remote compaction serialization through the unchanged `"oldest_ancester_time"` key is covered by compaction_service_test; manifest encode/decode through the renamed field by version_edit_test. `make check-c-api-gen` confirms the generated C API is up to date and backward compatible.

Reviewed By: jaykorean

Differential Revision: D123559299

Pulled By: pdillinger

fbshipit-source-id: 480106e8d539e654f31db210ef6e1de02325d604
2026-10-06 11:59:54 -07:00
generatedunixname3846135475516776
60f090647d Rocksdb Crash Test failed: Callstack - SharedState::SafeTerminate -> NonBatchedOpsStres (#15338)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15338

Reviewed By: jaykorean

Differential Revision: D122865369

fbshipit-source-id: 65a67955dd79a68fe1e4f15116d40dc669e1519e
2026-10-06 11:32:30 -07:00
Josh Kang
5b77e34985 Add range deletion support to external tables (#15334)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15334

External table formats cannot currently persist range tombstones, so SST ingestion and full-mode DB flushes or compactions fail when they contain `DeleteRange` entries. This change adds an opt-in contract for storing RocksDB’s canonical range-deletion block. RocksDB retains responsibility for sequence-number handling, fragmentation, and visibility through its native range-tombstone iterators.

Existing factories remain unsupported by default, and existing files without range tombstones are unaffected. Missing or invalid advertised tombstone blocks are treated as corruption. RocksDB also supplies logical entry counts and sizes when a format does not persist the properties block, while preserving format-specific properties.

Reviewed By: xingbowang

Differential Revision: D123475827

fbshipit-source-id: de8fec401bdc311282f5d4a48b248875c682f7c9
2026-10-05 18:39:42 -07:00
Peter Dillinger
c83a84e98c Rename TableProperties::creation_time to oldest_ancestor_time (#15304)
Summary:
The `creation_time` table property has long stored the file's oldest ancestor time (the oldest key time among the file's ancestors, falling back to flush/compaction time), not the file's creation time, which is `file_creation_time`. A TODO in table_properties.h acknowledged the misleading name. This change starts the API migration: `TableProperties::oldest_ancestor_time` is the new name, and `creation_time` remains as a deprecated alias sharing storage via an anonymous union, so existing external reads and writes continue to compile and behave identically.

An anonymous union is used rather than a `uint64_t&` alias member. A reference member would delete TableProperties copy/move assignment, make the implicit copy constructor bind the copy's alias to the source object (dangling), permit writes through a const TableProperties, add 8 bytes, and break the uint64_t-range walk in TEST_SetRandomTableProperties. The union leaves size, layout, copying, constness, and offsetof unchanged. Reading the other union member is formally UB in ISO C++, but both members are uint64_t and this is supported in practice by GCC, Clang, and MSVC.

All in-repo uses of the old name now use `oldest_ancestor_time`. Names that are externally observable or persisted are intentionally unchanged:
* On-disk property name `rocksdb.creation.time` (`TablePropertiesNames::kCreationTime`)
* The `"creation_time"` key in TableProperties serialization (used by remote compaction)
* The event-log JSON key and `TableProperties::ToString()` label
* Java `TableProperties.getCreationTime()` and C `rocksdb_table_properties_creation_time()`

C API: the auto-binding generator (tools/c_api_gen/auto_simple_bindings.py) only considered direct FieldDecls and failed on the anonymous union's implicit unnamed field. It now treats anonymous union members as direct fields. Regenerating adds a new C getter
`rocksdb_table_properties_oldest_ancestor_time()` alongside the existing `rocksdb_table_properties_creation_time()`.

Follow-up: remove the deprecated `creation_time` alias in a future release.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15304

Test Plan: New unit test DBTablePropertiesTest.CreationTimeAliasesOldestAncestorTime verifies the two names share an address and that a write through the old name is visible through the new one. options_settable_test and compaction_service_test cover TableProperties serialization round trips through the unchanged "creation_time" key, now mapped to the renamed field.

Reviewed By: jaykorean

Differential Revision: D123079083

Pulled By: pdillinger

fbshipit-source-id: 18b07d91f303a8f6de58da0c730f80d8c371505c
2026-10-05 10:34:04 -07:00
Josh Kang
c0512c4c34 Remove deprecated write_global_seqno ingestion support (#15287)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15287

`IngestExternalFileOptions::write_global_seqno` has been deprecated since RocksDB 8.0 and will soon be removed from the API. It requires ingestion to mutate SST files, sync them, regenerate checksums, and expose file-offset plumbing solely for that mutation.

Remove the option and its C, Java, and `ldb` APIs. Assigned sequence numbers now live only in the MANIFEST, leaving newly ingested SST bytes and whole-file checksums unchanged.

## Backward Compatibility

New readers continue to support existing SSTs containing a nonzero global sequence number written by legacy ingestion. Because that old write did not update the properties-block checksum, checksum verification retries against a temporary copy with only the global-seqno value restored to its original zero placeholder. The parsed sequence number and file contents remain unchanged.

This fallback only accepts mismatches explained by the legacy rewrite. Other checksum failures continue through normal corruption handling, and `verify_checksums=false` continues to bypass verification.

Reviewed By: xingbowang

Differential Revision: D122405856

fbshipit-source-id: 934e9bc755d58ea8d84e1c085e9769015bd8dfd7
2026-10-05 09:53:52 -07:00
Xingbo Wang
31675ca6d0 Recover from poisoned WAL handles on remote file systems (#15193)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15193

Keep `DB::Write`'s existing immediate `IOError` contract while allowing RocksDB to recover safely when a remote file system reports that a WAL handle is permanently unusable. The public API documentation makes the persisted-state boundary, reopen behavior, and safe post-resumption retry point explicit. Normal operation remains unchanged; the additional protocol is entered only after a supported file-scoped WAL error.

### Motivation

A WAL write crosses an acknowledgement boundary: storage can accept the complete record and then fail before the caller receives confirmation. The resulting `IOError` therefore cannot tell RocksDB or the application whether the record is absent, partial, or complete. At the same time, the remote file handle can become permanently poisoned, so retrying that same handle can never succeed.

The previous recovery path retried the poisoned handle. Simply replacing it is not sufficient: RocksDB must also prevent reuse of an ambiguous sequence range, keep every column family on one durable boundary, and prevent WAL tailers from publishing a physically complete record that successful live resumption deliberately abandoned. The new protocol quarantines the affected WALs, reserves the ambiguous sequence range, creates a replacement WAL, atomically persists acknowledged state, retires the quarantined handles, and only then resumes writes.

### Behavior changes

#### 1. Recovering from a poisoned remote WAL handle

- **Why required:** A remote file system can permanently poison a WAL handle after an I/O failure. Retrying that handle cannot succeed and leaves the database write-stopped.
- **Previous behavior:** RocksDB retried the same WAL handle during recovery.
- **New behavior:** The remote file system marks the failure as file-scoped. RocksDB creates a replacement WAL, atomically persists all state that could depend on the failed WAL, and then removes the quarantined WAL handles from active use. Physical file deletion remains part of normal cleanup.

#### 2. Preserving an indeterminate WAL write outcome

- **Why required:** A WAL record can reach storage before the append returns `IOError`; neither RocksDB nor the application can know the outcome from that error alone.
- **Previous behavior:** Recovery could reuse a file-scoped failed write group's sequence numbers, allowing a later acknowledged write to overlap a record that might already be in the WAL. The public write API did not explain the acknowledgement ambiguity or distinguish live resumption from close/reopen recovery.
- **New behavior:** `DB::Write` still returns the original `IOError` immediately and does not retry the logical batch. Normal DB reads can continue, but the failed write is not visible in that DB session. The persisted DB remains indeterminate with respect to the failed write until supported file-scoped live resumption succeeds without closing or reopening the DB. On reopen, or for any other WAL error without the durable-abandonment guarantee, applications must resolve consistency within RocksDB's documented guarantees, including `WALRecoveryMode`, through idempotency, deduplication, reconciliation, or an equivalent mechanism. After successful supported file-scoped live resumption, RocksDB has durably abandoned the failed batch and reserved its sequence range; retrying the same logical write is then recommended to preserve one linear logical history.

#### 3. Keeping TransactionLogIterator consistent with live resumption

- **Why required:** A physically complete error-returned record can remain readable from its WAL even though supported file-scoped recovery excludes that batch from the database. Returning it from `GetUpdatesSince()` would make replication or change-data-capture consumers diverge from the recovered DB.
- **Previous behavior:** An iterator opened before recovery could return the abandoned batch. A new iterator could also return it while the physical WAL remained visible.
- **New behavior:** For file-scoped failures, RocksDB records the exact sequence range and WAL number of each indeterminate write. For the lifetime of that open DB, existing and new WAL iterators stop with `Status::NotFound` before the range, whether the physical record is complete or absent. A caller can begin a new iterator after the reserved range. Iterator creation snapshots the recovery-gap generation together with its WAL-file view so a failure on a later, unseen WAL returns `TryAgain` for the old iterator instead of skipping its valid prefix.

#### 4. Keeping all column families on one recovery boundary

- **Why required:** A write batch can update multiple column families. Retiring its WAL after independently persisting only some column families can expose a partially applied batch after reopen.
- **Previous behavior:** With `atomic_flush=false`, recovery could persist column families independently before abandoning the WAL.
- **New behavior:** File-scoped WAL recovery force-flushes every live column family atomically and verifies that each one advanced beyond the failed WAL before writers are retired or writes resume.

#### 5. Merging concurrent WAL failures

- **Why required:** Another WAL write can fail while an earlier recovery attempt temporarily releases the DB mutex.
- **Previous behavior:** A later failed WAL cutoff or ambiguous sequence could be overwritten or cleared by the earlier recovery attempt.
- **New behavior:** Each error episode monotonically merges the largest failed WAL cutoff and the sequence range of the latest ambiguous write group. Recovery clears the quarantine cutoff only if no concurrent failure extended it; otherwise the newer failure remains write-stopping for the next attempt. Iterator fencing tracks the actual ambiguous write range separately, so an older failed `SyncWAL()` and a newer ambiguous write are not incorrectly paired.

#### 6. Retaining file scope through `NoSpace` recovery

- **Why required:** `NoSpace` can still identify one permanently unusable WAL handle.
- **Previous behavior:** The `SstFileManager` recovery path entered generic recovery without carrying the failed WAL context.
- **New behavior:** File-scoped `NoSpace` carries the same cutoff and sequence evidence through the `SstFileManager` path as other file-scoped WAL errors.

#### 7. Scoping recovery context to one error episode

- **Why required:** A completed recovery must not affect a later unrelated background error.
- **Previous behavior:** WAL recovery context could remain latched after `ClearBGError` succeeded.
- **New behavior:** Successful background-error clearing resets the context. A failed recovery retains it so a later retry has the same safety information. The separate iterator gap record remains for the lifetime of the open DB because later WAL readers must continue to reject the abandoned physical record.

#### 8. Preserving successful `SyncWAL` metadata

- **Why required:** A healthy WAL can finish syncing while a separate recovery error already exists.
- **Previous behavior:** `SyncWAL` could return the existing background error before applying the successful WAL's `VersionEdit`, dropping its MANIFEST update.
- **New behavior:** `SyncWAL` skips quarantined writers, applies metadata for successfully synced healthy WALs, and only then returns any existing recovery error.

#### 9. Classifying only the intended remote-file-system failure

- **Why required:** File-scoped recovery is valid only for an actual WAL `IOError` from a handle whose ownership is still valid.
- **Previous behavior:** Poison state could be queried before ownership fencing was resolved, and file scope could be stamped onto a non-I/O status.
- **New behavior:** Ownership fencing is resolved first, poison state is queried afterward, and only an actual `IOError` can be classified as file-scoped. `IOFenced` remains fatal.

#### 10. Preserving `paranoid_checks=false` retry behavior

- **Why required:** Non-paranoid mode historically cleared a writer's sticky error so a later operation could retry after storage recovered.
- **Previous behavior:** Avoiding reclassification of a cached synthetic writer error also bypassed `WALIOStatusCheck`, so later `SyncWAL` calls kept returning the stale error.
- **New behavior:** A cached error still fails the current `SyncWAL` call, but the non-paranoid path resets the writer for a later retry without setting a background error or entering file-scoped recovery.

#### 11. Preserving async WAL precreation across recovery

- **Why required:** A precreated WAL is an unpublished replacement and is not the active WAL. Recovery can also consume the prepared slot while background work is stopped.
- **Previous behavior:** A file-scoped failure while creating or starting the unpublished replacement was attributed to the active WAL, unnecessarily quarantining it. After successful recovery consumed a prepared WAL, the next slot was not replenished, so the following rotation created its WAL synchronously.
- **New behavior:** Only an active WAL `WriteBuffer()` failure attaches the active-WAL cutoff. A failed unpublished candidate is discarded while the current WAL remains active, and successful recovery schedules the next async precreation after `ClearBGError()` re-enables background work.

### Implementation

- Carry `DBRecoverContext` through generic, `NoSpace`, and `SstFileManager` recovery, merging WAL cutoffs and failed write-group sequence ranges independently of error severity. Generic errors retain the range only so a later retry that establishes file scope can activate the stronger protocol; they do not persist a reservation or install an iterator gap on their own.
- Document the WAL acknowledgement ambiguity, persisted-state boundary, reopen behavior, application consistency requirements, and post-resumption retry guidance on the public `DB::Write` API.
- Publish a lock-free quarantine cutoff used by write and sync paths; replace even an empty failed current WAL; persist file-scoped ambiguous sequence reservations; force and verify an atomic all-column-family flush; and detach quarantined writers only after that durable boundary.
- Record each file-scoped ambiguous write group's exact WAL number and sequence range in a DB-lifetime tracker shared by `WalManager` and all `WalIteratorImpl` instances. Stop an iterator before a batch that contains or crosses the range, and use a generation snapshot to preserve the existing `TryAgain` behavior when an iterator's fixed WAL-file view predates the failure.
- Keep successful healthy-WAL tracking edits in `SyncWAL`, reset completed recovery context, and preserve legacy non-paranoid writer reset behavior.
- Distinguish failures on an unpublished replacement WAL from failures flushing the active WAL. Log the failed replacement identity, keep the active WAL out of quarantine, and replenish the async-precreation slot after successful recovery.
- Classify poisoned remote-file-system handles after ownership fencing and only for `IOError`.
- Add lifecycle logging and a state-machine comment covering quarantine, sequence reservation, iterator fencing, replacement, atomic persistence, retirement, and concurrent-error handoff.
- Add an opt-in `db_stress` mode that injects file-scoped faults only into WAL writes, disables WAL tracking in that mode, validates unsupported direct invocations, and sanitizes crash-test parameters to the supported recovery envelope. A small fraction of ordinary non-specialized crash-test runs select this mode automatically; existing stress behavior is unchanged when the flag is off.
- Make the concurrent-error regression test hold the initial background flush until `SyncWAL` records the intended older-WAL error, preventing scheduler-dependent retirement of the test WAL before fault injection.

### Safety boundaries and unsupported cases

- Automatic file-scoped WAL recovery fails closed with `NotSupported` for manual WAL flush, 2PC/transactions, two write queues, pipelined or unordered writes, WAL recycling, WAL TTL or size retention, either WAL-tracking mode, or blob direct writes.
- Unsupported recovery leaves the DB write-stopped. `DB::Resume()` returns `NotSupported`; after correcting the I/O condition, operational recovery requires close and reopen.
- The failed logical batch is never retried. Only supported file-scoped recovery durably reserves its sequence range and fences `GetUpdatesSince()`. Generic WAL errors retain the existing indeterminate outcome and can replay after close/reopen; applications must use idempotency or reconciliation unless successful file-scoped live resumption has provided the stronger guarantee.
- Failed recovery attempts retain their context. Only a successful `ClearBGError` resets it.
- Physical WAL deletion is not part of the recovery correctness boundary; normal obsolete-file cleanup removes files later.
- The TransactionLogIterator ambiguity fence is kept for the lifetime of the DB instance that observed the file-scoped error and is not persisted as new MANIFEST metadata. After a later close/reopen, normal WAL recovery and obsolete-WAL retention/deletion semantics apply.
- The in-memory iterator-gap tracker can grow with the number of recovered file-scoped failures during one DB lifetime. Safe pruning must account for retained WALs and existing iterator snapshots; it is left for follow-up.
- Async WAL precreation remains disabled with WAL recycling. A prepared WAL is unpublished until its start records succeed; failure before installation does not make the active WAL file-scoped.
- The new stress mode is test-only and defaults off, so normal `db_stress` and database operation without an error are unchanged.
- Automatic stress-profile selection is limited to fully parsed, supported configurations. Explicit targeted runs reject options files and other unsupported combinations.

### Tests and known limitations

- The affected recovery, WAL-manager, WAL-iterator, fault-injection, async-WAL-precreation, and secondary-close binaries build successfully.
- Checked-status suites pass for all 70 `error_handler_fs_test` cases, all 7 `wal_manager_test` cases, all 11 `db_log_iter_test` cases, all 8 `fault_injection_fs_test` cases, and all 5 `DBWALTest.AsyncWalPrecreate*` cases. `DBSecondaryTest.SecondaryCloseFiles` passes 20 consecutive repetitions.
- Focused regressions cover file-scoped gap fencing after an iterator snapshot, generic errors not installing a file-scoped sequence gap, and later recovery discovering that a generic error is file-scoped.
- Earlier validation on this diff also passed crash-test unit coverage, remote-file-system adapter tests, repeated concurrency tests, two bounded multi-column-family stress runs, and bounded black-box crash/reopen dry runs.
- The exact internal RocksDB format check and `arc f --take-formatters --enforce-lint-clean` report no formatting or lint issues.
- The full crash/stress matrix and TSAN were not run.

Reviewed By: pdillinger

Differential Revision: D119324616

fbshipit-source-id: 05779d2bff7018f0ae69c4cbbe5b6455a45675d6
2026-09-30 04:40:00 -07:00
Josh Kang
176d7c7ea1 Add full external table support and crash testing (#15271)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15271

## Summary

Extend the experimental external-table API with a full mode that preserves RocksDB internal keys, sequence numbers, and entry types. Unlike the existing ingestion-oriented mode, full external tables can represent normal flush and compaction output and participate in live databases with puts, merges, deletions, snapshots, `Get`, `MultiGet`, iteration, multiple LSM levels, and external-file ingestion.

The existing `ExternalTable*` interfaces remain aliases for the original sequence-zero, Put-only mode, preserving compatibility for current implementations.

## Implementation details

- Introduce `ExternalTableMode` and mode-specific reader and factory interfaces. Full-mode builders and iterators receive complete internal keys, while the original mode continues to receive user keys.
- Adapt full-mode point lookups to RocksDB’s `GetContext`, allowing readers to return multiple versions of a user key in internal-key order so RocksDB can resolve snapshots, merges, and deletions correctly.
- Add a batched `MultiGet` context that preserves per-request lookup state and maps external-reader results back to RocksDB’s original request range.
- Translate iterator seek boundaries and returned keys while ensuring file-wide global sequence numbers are applied exactly once.
- Track sequence-number bounds, deletion and merge counts, user-collected properties, file size, and unique IDs when building tables. Unsupported entries fail and abandon the builder.
- Forward checksum verification to the external reader while retaining compatibility with implementations that do not override it.
- Add a standalone test implementation shared by basic and full modes. It uses a simple length-prefixed file format with a RocksDB properties block and checksum, avoiding dependencies on native table implementations.

## Crash testing

Add `use_full_external_table` support to `db_stress` and a dedicated blackbox `db_crashtest.py` profile. The profile exercises randomized reads, writes, merges, ingestion, flushes, compactions, snapshots, checksum verification, and crash recovery.

The profile disables unsupported combinations, including range deletions, user timestamps, sequence-number time tracking, backward scans, multiple column families, backup reconstruction, remote compaction, dynamic table-factory replacement, randomized aggregate/all-table property probes, and global-sequence-number rewriting during ingestion.

Because the simple reader materializes each open table in memory, the profile limits open files and generated file sizes to bound total database memory usage.

Reviewed By: xingbowang

Differential Revision: D120637416

fbshipit-source-id: aa2cbd06999e4325a234210d9c45987addd37550
2026-09-29 17:57:17 -07:00
Jay Huh
4052fccde9 Enable SecondaryDB in CF-consistency crash tests (#15270)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15270

D121318547 added SecondaryDB batch-iterator coverage to CfConsistencyStressTest::ContinuouslyVerifyDb(), but the automated CF-consistency crash-test profile never selected test_secondary. Automated runs therefore exercised only the primary DB path while the targeted manual validation used test_secondary=true.

Randomize test_secondary between 0 and 1 in cf_consistency_params. Each generated run can now exercise either the existing primary-only path or the SecondaryDB path, preserving both configurations in the continuous matrix. The regression test deterministically verifies both random outcomes. Existing sanitization may still disable SecondaryDB when another selected option is incompatible, such as remote compaction or primary UDI mode.

Reviewed By: xingbowang

Differential Revision: D121813002

fbshipit-source-id: 4080e2131c1f063d590d24cc0bcee9b9096de63a
2026-09-25 16:27:49 -07:00
Xingbo Wang
ac30c4d738 Add stress scan witness diagnostics (#15199)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15199

## Why
PrefixScan and MultiScan verification failures are difficult to reconstruct from the terminal assertion alone. The iterator options, bounds, seeks, preparation state, and immediately preceding movements are often the evidence needed to distinguish a database defect from an invalid test assumption.

## Why the existing recovery trace is not enough
The expected-state trace exists to reconstruct the expected database state after recovery. It preserves ordered write batches and deliberately filters out reads and iterator seeks, then recovery consumes a sequence-number-selected prefix. Consequently it contains none of the scan-specific evidence needed here: read options, bounds, seek targets, iterator validity, lazy-value preparation, movements, or the exact mismatch state. Scan witnesses capture that bounded human-readable context only when a verification failure needs it; they are best effort, are never replayed, and do not alter the recovery trace or its failure semantics.

## Previous behavior
The scan paths emitted generic failure output but did not retain a focused bounded witness of iterator setup and state transitions. Adding instrumentation naively would also format diagnostic strings on the normal path even when disabled and could create one file per failing operation.

## New behavior
An opt-in bounded ScanWitnessLog records scan configuration and recent iterator events for PrefixScan and MultiScan variants. On a qualifying failure it writes the latest complete witness for that DB, PID, thread, and scan type. The operation ordinal remains in the file contents. Repeated failures atomically replace the fixed witness rather than creating unbounded files.

## Implementation
The witness captures sanitized read options, input ranges, bounds, seeks, movement, PrepareValue results, iterator status, keys, value size and hash, and wide-column state. MultiScan tracks whether the current primary iterator value was actually prepared so failure snapshots report accurate state. Disabled witnesses avoid scan-label ownership, range formatting, expected-count formatting in release builds, and numeric iterator-label conversion. Witness writes share the per-thread diagnostic I/O-disable latch with breadcrumbs, write to a fixed temporary path, close, and rename over the final file. Entry counts are capped at 65,536. A formatting-only follow-up normalized all touched C++ lines with the repository clang-format rules.

## Safety and unsupported cases
The feature is stress-tool-only and raw db_stress keeps it disabled by default. It observes public iterator state and does not change database contents, expected-state tracing, recovery replay, iterator decisions, or fault-injection outcomes. Diagnostic I/O remains best effort and cannot fail the workload. Files are bounded per retained PID, thread, and scan type. The witness covers the instrumented PrefixScan and MultiScan implementations only and is not a general RocksDB trace. Files are not fsynced and therefore are not a power-loss durability mechanism.

## Tests and limitations
Validated with 47 tools/db_crashtest_test.py tests, exact-candidate debug and release db_stress builds, arc lint over the complete stack, oversized-entry rejection, and clean independent correctness reviews of the final integrated stack. make check-sources is unavailable because it invokes Git in this Sapling checkout; equivalent added-line hygiene checks pass. Only the newest complete witness per DB, PID, thread, and scan type is retained, so later failures intentionally replace earlier witnesses.

## Rebase update
Rebased onto fbsource master dc59f986b964. The logical diff is unchanged; the rebased stack passes debug and release db_stress builds, all 47 db_crashtest Python tests, and arc lint.

Reviewed By: jaykorean

Differential Revision: D119160911

fbshipit-source-id: 0e9b040c3bbb482e0a3b1b11070a7c5cc519f060
2026-09-25 16:20:54 -07:00
Ranjan Banerjee
662cdbc9ec Update RocksDB main for 11.11 release (#15263)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15263

Why: keep internal RocksDB main ready for 11.12.0 development while the 11.11 release is cut.
Previous behavior: Conveyor opened a GitHub pull request for this update, which could be missed and was not stacked with the internal release diff.
New behavior: Conveyor owns an internal Phabricator diff that the release-import diff stacks on top of. The release manager exports this diff manually with ExportIt.

Implementation:
- Bump version.h from 11.11.0 to 11.12.0
- Add `11.11.fb` to `check_format_compatible.sh`
- Sync HISTORY.md `## 11.11.` section from `11.11.fb`
- Delete 19 consumed `unreleased_history/` note file(s) from main

Safety and unsupported cases:
- Only version.h, check_format_compatible.sh, HISTORY.md, and consumed release-note files are allowed.
- Open legacy GitHub update-main PRs block this flow; this automation never modifies, exports, or lands them.
- Concurrent direct CLI invocations are unsupported; Conveyor serializes the Release Cut node to one active run.
- The tool refuses unrelated commits, mismatched release metadata, and non-canonical GitHub repositories.

Known limitations:
- ExportIt remains manual. Bot create permission for internal_repo_rocksdb/repo is a rollout prerequisite.

Reviewed By: xingbowang

Differential Revision: D121434642

fbshipit-source-id: 3c6c90f425e043ff9c13318ecc671aedbdef0347
2026-09-23 21:51:29 -07:00
Jay Huh
ffd4ac31af Make SecondaryDB multi-CF iterator reads non-blocking (#15259)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15259

Publish immutable SecondaryDB read views after open and successful catch-up so `NewIterators()` can acquire one cross-CF sequence and SuperVersion set without waiting for `DBImpl::mutex_`. Keep failed catch-ups on the previous view, reject refresh for batch iterators, and exercise concurrent catch-up plus batch scans in the CF-consistency stress profile.

Reviewed By: xingbowang

Differential Revision: D121318547

fbshipit-source-id: 3198f4766c90ab0bb39400963df3c329b4ecf6bf
2026-09-23 13:09:24 -07:00
Andrei Preda
507206e46b Fix stale readahead trim prefix on SeekToFirst (#15216)
Summary:
`BlockBasedTableIterator::SeekImpl()` sets
`seek_key_prefix_for_readahead_trimming_` only when given a seek key,
and `ResetBlockCacheLookupVar()` does not clear it. After a `Seek()`, a
`SeekToFirst()` on the same iterator trims readahead against the
previous seek key's prefix. `DBIter::SeekToFirst()` under
`prefix_same_as_start` takes its prefix from the first key found, so the
old prefix does not describe the keys being scanned.

With the stale prefix, `IsNextBlockOutOfReadaheadBound()` is true at
every block instead of only at the prefix boundary, and each prefetch is
clamped to the current block. The same predicate sets
`is_index_out_of_bound_`, which `FindBlockForward()` converts to
`is_out_of_bound_` and ends the scan. Under `ReadOptions::async_io` this
drops keys.

The bug needs `auto_readahead_size` and a preceding `Seek()` that reads
no data block. A `Seek()` that lands on a key leaves the prefetch buffer
anchored above the scan, and the trimming callback is not called. The
bug does not occur under `allow_mmap_reads`, where `FilePrefetchBuffer`
is constructed disabled.

The fix clears the prefix in `SeekToFirst()`, so prefix-based trimming
is disabled for that positioning. The `auto_readahead_size`
documentation in `include/rocksdb/options.h` already says prefix-based
trimming requires `Seek(key)`.

On buffered reads with `prefix_same_as_start`, `auto_readahead_size` and
`readahead_size=8192`, a cold-cache `SeekToFirst()` scan of 2M keys in a
single 222 MB file, preceded on the same iterator by a `Seek()` past the
last key, took 391 ms before and 308 ms after. The same scan with no
preceding `Seek()` takes 307 ms. Under direct IO on a local disk it went
from 33.1 s to 11.8 s. Data blocks read are unchanged; file reads drop
by 3x.

Also fixes a pre-existing test bug: `SeekParallelizationTestWithPosix`
asserts on `number_async_seek` without resetting that cumulative
thread-local, unlike its three siblings. The new tests move
`prefetch_test` from 11 shards to 13, so different tests share a
process, and the assertion fails at the parent commit under that
grouping.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15216

Test Plan:
New regression test in `PrefetchTrimReadaheadTestParam`, in sync and
`async_io` variants over the fixture's three `index_shortening` modes.
It runs two scans of the same keys from an equally cold block cache,
differing only in a preceding `Seek()`. Reverting the `clear()` fails 3
of 6 sync parameterizations on `READAHEAD_TRIMMED` (6 vs 40) and 3 of 6
`async_io` parameterizations on key count (40 vs 1).

`make check` and `ASSERT_STATUS_CHECKED=1 make check` show no new
failures. `prefetch_test` 122/122, `db_iterator_test` 249/249,
`table_test` 7629/7629, `block_based_table_reader_test` 5347/5347,
`db_basic_test` 300/300. New cases ran 1200 times under
`COERCE_CONTEXT_SWITCH=1` without failure.

Added db_stress coverage for `SeekToFirst()` after `Seek()` under
`prefix_same_as_start`. With the fix reverted and io_uring available,
the configuration in the second commit fails 8 of 8 seeds with
"Iterator diverged from control iterator"; with the fix, the same seeds
pass.

Reviewed By: rban1

Differential Revision: D120228466

Pulled By: xingbowang

fbshipit-source-id: a4935f03a0080cc8dd4c7a8c1438404e9b8f5c3a
2026-09-21 11:28:32 -07:00
Omkar Gawde
7530287bde Stop over-counting limited SstFileDumper reads (#15228)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15228

**What**: Correct the limited-read count and add regression coverage for recompression source-read errors.

**How**:
- `ReadSequential()` checks `read_num_limit` before consuming and counting the next entry.
- `RecompressReturnsSourceReadError` injects data-block read failures and verifies `ShowAllCompressionSizes()` returns the error.

**Why**: `sst_dump --read_num` reported one extra key, while the recompression input-materialization error path lacked regression coverage.

Reviewed By: joshkang97

Differential Revision: D120182510

fbshipit-source-id: d3b07794c9f7b74c29577242ad64c9c1f4a081cf
2026-09-17 15:31:21 -07:00
Xingbo Wang
8108b49e0c Add db_stress operation breadcrumbs (#15194)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15194

## Why
Intermittent db_stress and db_crashtest failures often report only the terminal assertion, after the operations that established the failing state have scrolled out of logs. The diagnostics must retain the latest operations even when the stress process is killed before it can run a failure handler.

## Why the existing recovery trace is not enough
The existing expected-state trace is a correctness oracle for recovery: after an expected-state checkpoint it records ordered write batches, and recovery replays the prefix implied by the database sequence number. Reads and iterator seeks are deliberately filtered out, trace-write failures are fatal, and the trace is machine-consumed rather than intended as a concise failure narrative. It therefore cannot show which read, scan, maintenance operation, or operation phase immediately preceded an assertion. Breadcrumbs fill that observability gap with a bounded, human-readable, per-thread history. They are best effort, never replayed, and remain independent of the recovery-validation contract.

## Previous behavior
db_stress did not retain a bounded per-thread operation history. db_crashtest did not automatically provision a local directory for these artifacts. The first version of this diff kept the ring in process-local memory and periodically wrote a complete temp-file snapshot, which could lose every operation since the last snapshot when the process crashed.

## New behavior
An opt-in per-thread memory-mapped breadcrumb ring records recent operation begin, context, and end events directly in a local file. Committed records therefore remain in the kernel page cache when only the db_stress process exits or is killed; no periodic flush is required. db_crashtest enables breadcrumbs by default, assigns a local diagnostics directory, retains the newest eight process groups after failed child runs, and removes managed artifacts after success.

## Implementation
Each ThreadState lazily creates a uniquely named, preallocated file and maps it through Env::NewMemoryMappedFileBuffer(). The file contains a fixed header and 512-byte text records. A record slot is marked I before its body is replaced and V only after the complete body is published; E denotes an unused slot. Each valid record carries a monotonically increasing sequence number so wrapped records can be ordered. Oversized context is bounded and ends with details_truncated=1. Lazy initialization avoids empty files for auxiliary ThreadState objects, and a process-wide file ID prevents collisions between ThreadState objects that share a logical thread ID.

The periodic-flush flag, temp-file/rename snapshot path, and verification-failure fallback flush are removed. Enabled operation-context formatting continues to reuse a per-thread string and uses std::to_chars for integers; disabled call sites avoid formatting. Diagnostic directory setup and mapping failures are reported but do not change the workload result. Auto-directory pruning and cleanup accept only generated filename schemas, reject symlink roots, preserve unrelated files and explicit user directories, and retain at most eight PID groups.

## Safety and unsupported cases
This is stress-tool-only diagnostic behavior. Raw db_stress keeps breadcrumbs disabled by default. The expected-state recovery trace and scan-witness contracts are unchanged. The mapping deliberately does not use msync or fsync: it is intended to survive a process crash while the kernel remains running, not power loss, host reset, or kernel failure. Filesystems or Env implementations without shared writable mappings disable breadcrumbs non-fatally. Replacing or truncating a live mapped file is unsupported. Records are fixed width, so unusually large operation context is explicitly truncated. Concurrent live readers are not promised a consistent snapshot; post-termination readers must ignore I and E records and order V records by sequence number. Nested operation scopes remain unsupported.

## Tests and limitations
Validated with all 47 tools/db_crashtest_test.py tests, exact integrated debug and release db_stress builds, complete-stack arc lint, lower/upper/combined clang-format checks, normal ring wrap, distinct files for four worker threads, an oversized 256-column-family context, and process-crash tests. A release process killed with SIGKILL retained all 16 committed ring records without an explicit flush. A debugger-forced SIGKILL after the I store but before V publication left the interrupted slot marked I. In five CPU-pinned alternating release pairs of 50,000 operations, enabled and disabled medians were both 2.15 seconds. This remains a bounded latest history rather than a complete log and provides no host-crash or power-loss durability.

## Rebase update
Rebased onto fbsource master dc59f986b964. The logical diff is unchanged; the rebased stack passes debug and release db_stress builds, all 47 db_crashtest Python tests, and arc lint.

## Review follow-up
The mapped-slot invalidation now uses an acquire-release atomic exchange so later body stores cannot become visible before the I marker on weakly ordered architectures.

Reviewed By: anand1976

Differential Revision: D119160910

fbshipit-source-id: ffe172af9ebc75c617196592806dc9641a539a25
2026-09-17 13:01:13 -07:00
Peter Dillinger
34d5074bfa Cross-key coalescing for lazy wide-column blob reads (#15178)
Summary:
Follow-up (Phase 3) to the experimental lazy wide-column read API (DB::GetEntityLazy / DB::MultiGetEntityLazy). A MultiGetEntityLazy batch now resolves blob references across keys with coalesced I/O instead of key-by-key.

- MultiGetEntityLazy acquires one SuperVersion and one consistent (implicit) sequence number for the whole batch via MultiCFSnapshot (as multi-CF iterators do) and transfers a single shared per-column-family pin into the LazyWideColumnsBatch, replacing the former per-key SuperVersion + explicit-snapshot loop. Single column family for now; a cross-CF ColumnFamily** overload remains a follow-up (the batch already models a per-CF pin map).
- LazyWideColumnsBatch::MultiResolve groups the batch's reads by (Version, blob file) for separate-file references and by SST for embedded references, and issues one coalesced read per group -- for whole-column and byte-range reads alike -- caching whole values in each entity's resolver and pinning partial reads directly.
- Adds the coalesced blob-read primitives this needs (none existed before): range-aware BlobFileReader::MultiGetBlobRange / BlobSource::MultiGetBlobRange / Version::MultiGetBlobLazy for separate-file references, and an embedded (SimpleGen2) batch read path (blob_gen2_format batch readers + BlobSource::MultiGetSimpleGen2Blob[Range] + BlockBasedTable::MultiGetSameFileBlob). Whole-value separate-file coalescing reuses the existing MultiGetBlob. The batch readers sort their requests by file offset before issuing the underlying MultiRead (which requires ascending offsets), and gen2 checksum verification is factored into one helper shared by the scalar and batch readers.
- The hot non-lazy Get / MultiGet / iterator / compaction paths are untouched: the new multis are lazy-only, and the GetImpl change is gated on a new opt-in GetImplOptions field (an injected shared SuperVersion + sequence number) used only by the batched lazy read.
- Blob read failures on these paths (short reads, invalid offsets, out-of-range requests, compression mismatches) now carry file/offset/size and expected-vs-actual context, mirroring the block-checksum diagnostics.
- db_bench gains a multireadrandomentitylazy benchmark for measuring the coalesced batch path.

No public API change, so this is a performance follow-up to the still-recent lazy API rather than a new feature. EXCEPT that `LazyColumnReadRequest::status == nullptr is now explicitly disallowed`.

Left for later follow-up:
- When a whole fetch is planned for an (entity, column), bind ordinary range requests for that column to the same fetch and slice them after adoption instead of issuing separate range I/O.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15178

Test Plan:
New unit tests in db/wide/db_lazy_entity_test.cc assert cross-key coalescing via a blob-file / SST MultiRead counter added to the test FileSystem wrapper:
- N whole-column separate-file reads across keys in one blob file collapse to a single coalesced MultiRead (and the fetched values are cached, so a repeat read does no further blob I/O);
- N byte-range separate-file reads collapse to one MultiRead, save the un-read bytes (rocksdb.blobdb.lazy.partial.bytes.saved), and never fill the blob cache;
- embedded (same-file) reads across keys in one SST collapse to one MultiRead over that SST;
- an out-of-order embedded batch (reads issued in descending record offset) still resolves correctly in one coalesced read, covering the batch readers' internal offset sort;
- a mixed batch (inline + whole + byte-range across keys) returns the correct bytes for each read;
- a batched MultiGetEntityLazy + one MultiResolve matches resolving each key via a separate GetEntityLazy. The existing db_stress lazy-vs-eager differential (MaybeTestMultiGetEntityLazy) exercises the new batched read and cross-key MultiResolve unchanged (same public API).

Reviewed By: xingbowang

Differential Revision: D118660682

Pulled By: pdillinger

fbshipit-source-id: 6a832175e730917eddeedab8e0b9cb2b8d6ae7b4
2026-09-16 18:10:21 -07:00
Omkar Gawde
613a642b5c Fix null dereferences in SstFileDumper error paths (#15215)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15215

**What**: Stop `SstFileDumper` from dereferencing a null file handle on two error paths, both of which crash `sst_dump` today.

**How**: Both sites create a file and then use the handle without checking whether creation succeeded. `Env::NewWritableFile()` and `FileSystem::NewRandomAccessFile()` each reset the caller's handle before they can fail, so on failure the handle is null.
- `DumpTable()` fell through to a shared error path that closed the handle. It now returns as soon as `NewWritableFile()` fails.
- `GetTableReader()` re-opens plain and cuckoo tables with mmap enabled and discarded the returned status entirely, then wrapped the null handle in a `RandomAccessFileReader` that the next property read dereferenced. It now propagates the status.

**Why**: Both are reachable from the `sst_dump` tool. The output file cannot be created on an unwritable directory, a missing parent, or a full filesystem; the mmap re-open fails when mmap is unavailable or the file changed between the two opens. In each case the tool crashed instead of reporting the error. The plain and cuckoo branch had no test coverage at all, which is why its unchecked status never tripped an `ASSERT_STATUS_CHECKED` build.

Reviewed By: xingbowang

Differential Revision: D120182509

fbshipit-source-id: dbe9b7f84140ed11dea395f4192b895e0a7bc70d
2026-09-15 15:54:16 -07:00
Peter Dillinger
e231c1826c Revamp sst_dump recompress for serious benchmarking (#15195)
Summary:
Extends `sst_dump --command=recompress` from a basic evaluation tool to a serious compression benchmarking tool, also able to evaluate interactions with table format options affecting uncompressed sizes and compressibility.

For each input file it now emits a benchmark "baseline" entry for the file as stored, followed by one entry per compression type / strategy / level, reporting compressed and uncompressed data-block sizes, index sizes, compression ratio, and both wall-clock and CPU time for the write and read tests. All data/index sizes are block payload excluding block trailers, so compressed vs uncompressed figures are on a consistent basis. The input's uncompressed data size is inferred from decompression statistics, so it is available even for files that don't persist it. That inference is exact when the input's index blocks are stored uncompressed (the default); with enable_index_compression it can be slightly overstated because index-block decompression is also counted (see the CAVEAT in GetInputBenchmarkMeasurement).

New capabilities:
* `--json` for machine-readable output. It is self-contained: each measurement carries the compression manager id and the full CompressionOptions, and every baseline/measurement entry is tagged with its input file path so multiple input files produce one valid document.
* `--compression_strategy=` to sweep a list of CompressionOptions strategy values (multiplied with levels), useful for side-channel configuration of custom compressors.
* `--show_properties` additionally dumps the input and last recompressed output TableProperties.

To keep measurements meaningful, a quiet throw-away priming build runs whenever the compression type changes (warming up the compressor), CPU time is measured process-wide so parallel compression worker threads (compression_parallel_threads > 1) are included rather than undercounted, and the timed windows are kept tight: the write build is fed from an in-memory copy of the input so its timing excludes reading and decompressing the input (and is independent of block-cache state), and the read timing covers only the block reads. The timings for compression and decompression do include BlockBasedTable overheads but this is considered reasonable--if not preferable--to the intended application of finding the overall most efficient settings related to compression.

Also fixes a latent bug exposed by measuring several compression types in one run: the recompressed table was read back through the shared block cache under a common (unknown) cache-key identity, so a later type could get a cache hit on a stale block from an earlier, differently-compressed table and fail with "Corruption: bad entry in block". The read-back now bypasses the block cache.

Also fixes a typo in CLAUDE.md.

Intended follow-up: persist TableProperties::uncompressed_data_size. It is already computed in memory at build time but is never written to the properties block (PropertyBlockBuilder::AddTableProperty has no entry for it), so it reads back as 0 for on-disk files. Persisting it (backward compatible; old files read 0) would fix `data uncompressed size: 0` for all property readers and let this tool read the value directly, removing the decompression-ticker inference above and its index-compression caveat for all but pre-existing files.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15195

Test Plan:
Added sst_dump_test cases exercising recompress with --show_properties and with --json.

Ad hoc manual testing against real SST files: verified the text benchmark table, and piped `--json` output through `json_pp` to confirm well-formed JSON, for a single file, a directory of multiple SST files, and combined with --show_properties, --output_hex, --compression_strategy, and --block_based_table_options. Confirmed process CPU time exceeds wall-clock write time with --compression_parallel_threads=4 (and matches at =1).

Reviewed By: joshkang97

Differential Revision: D119536444

Pulled By: pdillinger

fbshipit-source-id: 30dbabc14872b9c6f7d8ca11227e762674dcef02
2026-09-15 12:40:17 -07:00
Xingbo Wang
4c2a977aa5 Track newer snapshot versions in RocksDB metadata reads (#15086)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15086

Why: An application built on top of RocksDB could use cache population reads through explicit snapshots and needs to know when the returned value has already been superseded, without a second point read.

Previous behavior: Get and MultiGet returned the snapshot-visible value and optional timestamp but could not report a newer point update or covering range deletion.

New behavior: experimental C++, C, and Java metadata-read APIs report newer committed puts, deletes, merges, and covering range tombstones; ordinary primary-DB writes completed before the read are guaranteed to be reported, racing writes remain best-effort, and true has no false positives.

Implementation: metadata reads widen lookup behind read-callback hooks while keeping metadata-only state off passive common object layouts; per-key state stays in KeyContext/GetContext, ordinary MultiGet row-cache replay semantics are preserved, BlobDB/TTL use shared output helpers, dead single-CF plumbing and an invalid cross-key stress assertion are removed, and the range-tombstone ordering invariant is documented without changing behavior.

Safety and unsupported cases: tracking is opt-in and requires an explicit snapshot; persisted-tier reads, custom transactional visibility callbacks, secondary DBs, and unsupported subclasses return NotSupported, while read-only/compacted DBs return false because their in-process views are static.

Tests and limitations: 1,265 Buck tests plus focused metadata, 100x coerced-context-switch row-cache, write-committed/write-prepared crash tests, builds, and lint passed. Current-parent release db_bench found no material passive regression: readrandom +0.65%, MultiGet batch 4 -1.40%, and batch 32 +0.48%.

Reviewed By: pdillinger

Differential Revision: D104182397

fbshipit-source-id: 36f7be1cd50fe2383689adaae31a6bb7d7e462dc
2026-09-11 04:44:59 -07:00
Xingbo Wang
7d3150ffce Report RocksDB benchmark interval latency and perf metrics (#15101)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15101

This diff contains all changes under fbcode/internal_repo_rocksdb/repo for the RocksDB regression benchmark observability work.

It adds opt-in per-interval latency percentile reporting to db_bench. When --report_interval_percentiles is enabled with histogram and a positive stats interval, each worker records into pre-created per-operation interval histograms and counters. A background reporter periodically merges and resets those thread-owned stats, skips excluded background-writer stats, performs a final partial-interval flush, and emits IntervalPercentiles lines with sample count, operation count, and P50/P75/P99/P99.9/P99.99 latency. Worker updates and reporter snapshots use a double-buffer handoff, so the reporter never reads or clears a histogram still being updated. The legacy histogram timing path remains unchanged when interval reporting is disabled.

The interval implementation uses RocksDB UnorderedMap aliases for the hot lookup maps and UnownedPtr for non-owning reporter references.

It also aggregates each worker thread PerfContext and IOStatsContext into the final merged benchmark Stats and emits final PERF_CONTEXT and IOSTATS_CONTEXT blocks, so multi-thread benchmark metrics represent all workers rather than one thread.

For options-file runs, explicit benchmark-critical CLI overrides are applied after loading the file. This includes --disable_auto_compactions, fixing an existing precedence bug where disable_auto_compactions=false in the options file silently overrode an explicit CLI true value. The regression harness relies on that CLI setting, so the bug allowed automatic compactions during origin construction and write-heavy benchmarks. The regression test persists false in an options file, passes --disable_auto_compactions=true, reloads the opened DB options, and verifies the effective value is true.

One shared WriteBufferManager is created after option loading and reused by all DBs, enforcing the intended aggregate memory cap for multi-DB benchmarks.

The compactall benchmark now accepts --compact_all_parallelism. It compacts independent DBs through a bounded worker set, applies --subcompactions to each DB, emits per-DB progress and elapsed time, and propagates the first CompactRange failure after joining all workers. Its default parallelism remains 1 for backward compatibility.

The dependent harness diff enables these features, parses their output, and publishes the resulting interval and final metrics.

Reviewed By: pdillinger

Differential Revision: D116025321

fbshipit-source-id: d44185ba2110aacbe9fadc65a59799bf8c1c54f0
2026-09-10 08:08:38 -07:00
Jay Huh
91b96f41cf Retry the OpenAndCompact secondary open when the primary replaces CURRENT (#15179)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15179

A remote compaction worker (`DB::OpenAndCompact()`) opens the *live* primary's DB directory read-only, and that open reads `CURRENT`. The primary publishes each MANIFEST rotation by writing a new `CURRENT` under a temp name and renaming it over the old `CURRENT`. POSIX keeps an already-opened `CURRENT` readable after that replacement, but file systems without read-after-unlink semantics (remote or object stores) do not: the replaced object is reported as gone, surfacing in RocksDB as `IO error: No such file or directory` (`Status::PathNotFound`). The secondary open was not retried, so this transient race failed the whole compaction. There is an equivalent, already-recognized race one step later for the MANIFEST file (`ReactiveVersionSet::MaybeSwitchManifest()` converts it into `Status::TryAgain`), which was fatal here for the same reason.

`DB::OpenAndCompact()` now retries the secondary open (up to 3 attempts) when it fails with `TryAgain` / `PathNotFound` / `NotFound` -- exactly the statuses a concurrent `CURRENT`/MANIFEST replacement produces. The retry limit is configurable via `OpenAndCompactOptions::max_secondary_open_retries`. Genuine failures are still returned after the bounded retries, and no safety check is weakened or skipped.

Found by a `db_stress` crash test failure. Two non-default options in that run make this reachable and fatal:
* `remote_compaction_worker_threads=8` with `remote_compaction_failure_fall_back_to_local=0` -- the latter was only recently randomized to 0; before that a remote compaction failure was always silently retried locally. Now `DbStressCompactionService::Wait()` maps the failure to `kFailure`, which becomes a sticky background error on the primary, so every subsequent `Flush()` returns it and `StressTest::ProcessStatus()` aborts with `assert(false)`.
* `max_manifest_file_size=8192` -- the primary rotates MANIFEST (and therefore replaces `CURRENT`) constantly, which is what makes the race window hit in practice.

Full chain: primary renames a new `CURRENT` over the old one -> the file system reports the replaced object as gone for the reader that already resolved it -> `GetCurrentManifestPath()` inside the worker's secondary open fails with `IO error: No such file or directory` -> `DB::OpenAndCompact()` returns that error with no retry -> `kFailure` (no local fallback) -> primary background error -> `Flush failed: IO error: No such file or directory: ... /CURRENT ...` -> assertion failure in `db_stress`.

Reviewed By: joshkang97

Differential Revision: D118480192

fbshipit-source-id: a2618ee24e582e2e713af04464ee1e4038925ccb
2026-09-09 19:32:00 -07:00
Josh Kang
37234200b5 Handle benign io_uring stderr in crash tests (#15181)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15181

A crash test failed when `use_async_db_api=1` created read-executor workers on a host unable to allocate io_uring rings. Folly logged the setup failure and threw; RocksDB caught it and successfully fell back to synchronous reads, but the harness failed because stderr was non-empty.

Representative stderr:

```
WARNING: Logging before InitGoogleLogging() is written to STDERR
I0903 14:05:39.470695 286445 IoUringBackend.cpp:527] io_uring_queue_init_params(512,1024) failed errno = 0:"Success" 0x7d2e06295280 retrying with capacity = 512
E0903 14:05:39.470759 286445 IoUringBackend.cpp:537] io_uring_queue_init_params(512,512) failed ret = -12:"Unknown error -12" 0x7d2e06295280
```

Initialize glog in the Folly-enabled OSS `db_stress` entry point and centralize known stderr sanitization. Only `IoUringBackend` ring-initialization failures are moved to stdout when the async DB API is enabled; existing expected post-SIGTERM errors use the same sanitizer. All unrelated stderr remains fatal.

___

Differential Revision: D118836521

fbshipit-source-id: c9ce05896f417c7723c5cd73d293fbb1cd14ee1e
2026-09-04 12:22:30 -07:00
Josh Kang
ac0f4ae69f Isolate coroutine statistics across async reads (#15142)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15142

Coroutine statistics use TLS, but coroutine reads can interleave on the same executor thread. Previously, when a stats-enabled request suspended, it saved its counters without disabling the executor's TLS configuration. A stats-disabled request running next could inherit those enabled settings and collect statistics unexpectedly. Stats setup also lived in individual DB implementations, so wrapper early returns and future stackable DB implementations could bypass it.

Move statistics ownership to the public `CoroDB` and callback-based async read boundaries, before virtual dispatch. Each operation captures its caller's configuration, enabled requests preserve their counters across suspensions, and every suspension or completion leaves executor TLS disabled. Because the scope wraps stackable DB dispatch, current and future `CoroStackableDB` implementations automatically inherit the correct behavior without adding their own stats reset or scope.

For consistency between coroutine and callback-based reads, the experimental callback API now uses TLS for both configuring and consuming statistics. `AsyncCallback::OnComplete()` no longer receives context arguments; RocksDB publishes the completed per-operation counters to TLS before invoking it.

```
Before

time -------------------------------------------------------------->

stats-on A:   onSet(enable A) -- work -- onUnset(save A) ...... onSet(A)
executor TLS: [A enabled] ------------> [still enabled] ------> [A enabled]
stats-off B:                                  run
                                               ^ inherits A's enabled TLS

After

time -------------------------------------------------------------->

stats-on A:   onSet(enable A) -- work -- onUnset(save A, disable) ... onSet(A)
executor TLS: [A enabled] ------------> [disabled] ----------------> [A enabled]
stats-off B:                                  run
                                               ^ remains stats-disabled
```

Reviewed By: xingbowang

Differential Revision: D117017089

fbshipit-source-id: 8a85f7ba43e3b73225995f5d0b79da3892baafb9
2026-09-04 11:19:16 -07:00
Jay Huh
bec9dcec7c Make db_stress actually tolerate transient remote IO errors (#15170)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15170

`warm_storage_tsan_liveness_crash_test` fails with

```
error : Get() returns IO error: Failed to read 1032 bytes from file
ws://ws.zdb.ldc0zdbtest1/.../000093.sst from offset 5461, message
[0 code=DEADLINE_EXCEEDED isRetryable=1 ... error=WSE_BLOCK_READ_TIMEOUT
classification=TRANSIENT_ERROR ...] for key: 0000...78 (30280).
Verification failed :(
```

The Warm Storage error is explicitly `classification=TRANSIENT_ERROR isRetryable=1`, and the DB artifact confirms there is no RocksDB problem: `000093.sst` is intact (37760 bytes, downloaded successfully after the run) and the DB `LOG` reaches `Shutdown complete` with zero IO-error or corruption entries. This is exactly the class of failure `--tolerate_non_injected_io_errors_for_remote_dbs` was added for (D112136438), but that mechanism never actually took effect. Two independent defects:

**1. The triggering non-default option: `tolerate_non_injected_io_errors_for_remote_dbs=0` on a remote DB.**
`db_crashtest.py` hard-coded the parameter to `0` in `default_params` and only ever *force-disabled* it (for local DBs), leaving each remote job to opt in on its own command line. `rocks/sandcastle/test-driver.sh`'s `warm_storage_*crash*` branch never passed it, so every Warm Storage crash-test job ran with the flag off and a single transient read timeout failed the run. Fix: the parameter now defaults to `1` whenever `--env_uri` / `--fs_uri` names a remote backend, so no remote job has to remember to opt in. A caller can still pass `0` explicitly, and `finalize_and_sanitize()` still force-disables it for local DBs so genuine local IO errors (e.g. io_uring failures) are never masked.

**2. `injected_error_count == 0` made the flag a no-op in `no_batched_ops_stress.cc`.**
All five read-path guards were
`injected_error_count == 0 || !IsErrorInjectedAndRetryable(s)`.
`IsErrorInjectedAndRetryable()` covers *non-injected* remote IO errors, which by definition have no injected fault to correlate with — and the `liveness` profile forces every `*_fault_one_in` to `0`, pinning `injected_error_count` to `0` for the whole run. So the left operand short-circuited to true and the error was reported as a verification failure regardless of the flag. Fix: extract `StressTest::IsTolerableNonInjectedRemoteIOError()` and add `StressTest::IsRetryableOperationError(injected_error_count, s)`, which gates the injected-error branch on `injected_error_count > 0` but lets a tolerated non-injected remote IO error through unconditionally. The five call sites now use the helper.

With the tolerate flag off (all local runs), the new guard reduces algebraically to the old expression, so local behaviour is unchanged. The genuine consistency-check gate at `no_batched_ops_stress.cc:1146` (`do_consistency_check && injected_error_count == 0`) is intentionally left alone.

Fixes T286575055.

[Session trajectory link](https://www.internalfb.com/intern/devai/devmate/inspector/?id=9646c050-6791-4ac3-9abb-3ee110dcb54e)

Reviewed By: pdillinger

Differential Revision: D117886823

fbshipit-source-id: d7591472db8ee6e03c9d9f018480409b6d802838
2026-09-03 10:43:54 -07:00
Jay Huh
897162a6bc Add session-scoped temporary directory for remote compaction (#15169)
Summary:
Remote compaction writes output SSTs into caller-selected job directories. If the primary crashes before installation or falls back to local compaction, those directories can outlive the DB session and accumulate under the DB directory.

This change defines `kSessionTmpDirName` as `session_tmp`, the canonical name for a DB-local temporary directory whose contents have the lifetime of a DB session. The immutable boolean option `use_session_tmp_dir_for_remote_compaction` opts remote compaction into this directory. When enabled, an `OpenAndCompact()` directory name is resolved as `<dbname>/session_tmp/<job>`.

During DB open, RocksDB creates the session temporary directory, completes recovery, and then synchronously deletes contents left by the previous DB session before scheduling compactions. `DestroyDB()` removes it as part of database deletion, and normal completion, failure, and cancellation paths clean the resolved per-job directory.

The option defaults to false. Disabled callers retain the existing `<dbname>/<job>` layout and no session-directory cleanup. For rollout compatibility, legacy callers that already pass a DB-rooted output path are accepted unchanged only while the option is false. Directory creation and invalid-path errors are delegated to the configured filesystem rather than prevalidated by RocksDB.

After D118301531 landed, per-job directory creation and resumption cleanup are kept together in `InitializeCompactionWorkspace()`. `OpenAndCompact()` resolves the client-provided name and expects DB open to have initialized the session temporary directory when the feature is enabled.

The boolean is wired through C++ and generated C APIs, option serialization and reconstruction, `OpenAndCompact()`, ldb, db_bench, and db_stress. Tests cover path resolution, cross-session deletion, ownership boundaries, disabled behavior, `DestroyDB()`, configured compaction output, resumption fallback, and option round trips.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15169

Reviewed By: pdillinger

Differential Revision: D118227112

fbshipit-source-id: 01aa834b0b3f75638102130a86d95d537a2797bd
2026-09-02 21:46:24 -07:00
rocksdb-release-bot
1ca38e1442 Update version to 11.11.0 for 11.10 release (#15157)
Summary:
- Bump version.h from 11.10.0 to 11.11.0
- Add `11.10.fb` to `check_format_compatible.sh`
- Sync HISTORY.md `## 11.10.` section from `11.10.fb`
- Delete 13 consumed `unreleased_history/` note file(s) from main

Part of 11.10 release workflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15157

Reviewed By: jaykorean

Differential Revision: D118302742

Pulled By: xingbowang

fbshipit-source-id: 751fb106cc5a4f5dd502c5838c5653278b5a8529
2026-09-02 09:07:42 -07:00
Josh Kang
da3a0961fa Add custom read context to ReadOptions (#15173)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15173

Add an opaque, non-owning `ReadOptions::custom_context` pointer that is passed to table implementations so external table readers can consume application-defined read options. Document its lifetime and synchronization contract, and keep the unsupported pointer out of generated C bindings.

Reviewed By: xingbowang

Differential Revision: D118341098

fbshipit-source-id: e51185f58b5ce80d8fd07efab02a05dbc8c7bd12
2026-09-01 18:51:16 -07:00
Josh Kang
96ed085402 Blog post for coroutines (#15143)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15143

## Summary

Adds a RocksDB blog post describing the native async/coroutine read APIs introduced in RocksDB 11.10.0. It covers the callback and `folly::coro::Task` interfaces, differences from `ReadOptions::async_io`, executor and I/O-completion handoffs, filesystem integration, request-local statistics across suspension, and API lifetime constraints.

The post includes three explanatory diagrams and benchmark results comparing synchronous and coroutine point reads across task counts and `MultiGet` batch sizes.

https://www.internalfb.com/intern/px/p/cDGqv/

Reviewed By: xingbowang

Differential Revision: D117159178

fbshipit-source-id: 5452e6b07e29abb5594505021995e9b5bac9d198
2026-09-01 15:55:37 -07:00
Peter Dillinger
2c1c7ea2f1 Surface remote cx crash test failures and fix resumption output-dir cleanup (#15168)
Summary:
Follow-up to the "UPDATE" note on D116738823 (PR https://github.com/facebook/rocksdb/issues/15133, "Faster and safer MANIFEST recovery for remote compaction (OpenAndCompact)"). That change observed that the crash test does not really cover remote compaction because `remote_compaction_failure_fall_back_to_local` is pinned to 1 in db_crashtest.py, so any remote compaction failure is silently retried locally instead of failing the test -- masking existing failures. Addressing that was called out as a high-priority follow-up (tracked under T232588967, "Rocksdb Crash Test failed: Remote Compaction").

This does the cited enablement and fixes the first masked failure it surfaces:

* Crash test: randomize `remote_compaction_failure_fall_back_to_local` between 0 and 1 (the TODO(jaykorean)), so remote compaction failures actually surface instead of always being hidden by a local fallback.

* Fix a real OpenAndCompact bug the enablement immediately exposed. When a compaction is scheduled with `allow_resumption=true` but resumption is internally disabled because it is incompatible with output-hash verification (`paranoid_file_checks=true`, or `verify_output_flags` requesting iteration verification), the worker started a fresh compaction but did NOT clean the output directory. The caller, having requested resumption, does not empty the directory either, so a compaction that was canceled after writing some output and then retried reused the same output file numbers and collided with the leftover files -- surfacing under the crash test's fault-injection FS as `NewWritableFile violates no-reopen-for-write contract`, which became a background error and failed subsequent writes. The fix honors the documented `OpenAndCompactOptions::allow_resumption=true` fallback contract: when resumption is requested but cannot be fulfilled, clean any leftover progress and output files before starting fresh (reusing the existing Scan/Cleanup helpers), so the fresh compaction always begins from a clean directory. When `allow_resumption=false`, the caller still owns emptying the directory (unchanged).

No known production exposure: this bug can only bite a caller that requests `allow_resumption=true` on a column family where output-hash verification is enabled (`paranoid_file_checks=true`, or `verify_output_flags` including `kVerifyIteration`), because only that combination disables resumption while the (uncleaned) output directory is left to the caller. Auditing how the option is used across internal deployments found nothing in that state: the deployments that enable resumption for remote compaction do not enable those iteration/paranoid verification modes, and where remote-compaction output verification is enabled at all it is block-checksum-only, which keeps resumption active and never hits the buggy path. So this is effectively a latent-bug fix -- it prevents a failure that would appear if iteration/paranoid output verification were later turned on for a resumption-enabled remote-compaction fleet.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15168

Test Plan:
Added compaction_service_test.cc unit test
`CleansOutputDirWhenResumptionDisabled`, which drives OpenAndCompact with `allow_resumption=true` and `paranoid_file_checks=true` after seeding a leftover output SST, and asserts the leftover is cleaned and the compaction succeeds. It was confirmed to FAIL without the fix (leftover retained) and pass with it.

Local crash test runs (blackbox, `remote_compaction_worker_threads=8`, `remote_compaction_failure_fall_back_to_local=0`):
- Without the fix, the collision-trigger config (`allow_resumption_one_in=1 --paranoid_file_checks=1`) fails within the first ~60s iteration on the no-reopen-for-write contract violation.
- With the fix, that same config ran ~10 min clean, and a full-random config (everything else randomized) ran ~15 min clean, both with 10-15 kill/reopen cycles plus a final crash-recovery verification. More local crash test runs are planned to shake out any remaining, rarer masked failures now that the fallback no longer hides them.

Reviewed By: jaykorean

Differential Revision: D118301531

Pulled By: pdillinger

fbshipit-source-id: ceb9683ae06ce930ea78bafa3585ad616012243f
2026-09-01 12:05:27 -07:00
Omkar Gawde
d699ffbd5c Support partial atomic range replacement (#15154)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15154

**What**: Let `IngestExternalFileArg::atomic_replace_range` replace a half-open `[start, limit)` range that only partially overlaps existing SSTs, by committing a generated range tombstone alongside the replacement files.

**How**:
- Tombstone: `NumFilesToPrepare` reserves one extra file number and `PrepareAtomicReplaceRangeTombstone` writes the SST only when the pinned `Version` holds a straddling file (`HasPartialOverlap`), so the no-straddle case pays no extra write; run rescans and returns `TryAgain` if a straddler appeared since. On activation it is rotated to the front of `files_to_ingest_` so one `VersionEdit` lands `old < tombstone < data` seqnos, straddlers are retained, contained files metadata-deleted, and `generated_for_ingestion` keeps `move_files` from unlinking it.
- Half-open plumbing: `RangesOverlapWithMemtables`, `RangeOverlapWithCompaction`, `OverlapProximalLevelOutputRange`, and `IsRangeOverlapped` take a required limit-exclusivity argument, and `largest_internal_key` now comes from `RangeTombstone::SerializeEndKey()`. `OverlapWithLevelIterator` stays inclusive: over-counting can only push the tombstone one level shallower, which is safe.
- Eligibility: `SupportsAtomicReplaceRangeTombstone` requires universal compaction, a delete-range-capable table factory, `allow_global_seqno`, `!allow_db_generated_files`, `!snapshot_consistency`, and no UDT. `fail_if_not_bottommost_level=true` rejects partial overlap because the retained file forces the tombstone and replacements above Lmax. The `atomic_replace_range` doc comment and a new `unreleased_history/public_api_changes/` note state the half-open contract.
- `ResetTableReader` passes `kNoFileChecksumFuncName` in `FileOptions` for external files, making "no checksum metadata" explicit rather than empty.
- Stress: new `ingest_external_file_atomic_replace_one_in` gflag. `PickAtomicReplaceRange` picks a run of overwriteable keys strictly inside an existing SST so every replacement straddles, writes even-indexed keys and marks the interleaved odd-indexed keys deleted so the tombstone must clear the holes, and rolls back expected values on `TryAgain`. `db_stress_tool.cc` and `db_crashtest.py` gate the flag on the same eligibility set and force `acquire_snapshot_one_in=0`.

**Why**: Atomic replacement previously required range bounds to align with SST bounds, and the public doc admitted the upper bound "may be interpreted as inclusive or exclusive". The tombstone overlay removes the alignment restriction and pins the contract to half-open without slowing the Nimble path or the no-straddle case.

Reviewed By: pdillinger

Differential Revision: D117155307

fbshipit-source-id: 65f45463e8082a7db7a9d44c8dc50debe95e36e7
2026-08-26 21:52:40 -07:00
Peter Dillinger
e4a7136d3e Preferred-phase scheduling for periodic (time-based) compaction
Summary:
`periodic_compaction_seconds` softly guarantees every SST is rewritten at least
once per interval. Across a fleet of similarly-configured DBs, many events line
those intervals up in lockstep -- a shared enable, a coordinated restart (e.g.
DC power-outage recovery), or just similar write history -- so the periodic
compactions all come due together, a recurring "thundering herd" that is
especially painful with remote compaction (SEV S698635).

The obvious fixes trade one problem for another. Spreading the herd out means
some files get compacted off their natural schedule, which is wasted work; and a
clumsy spread just relocates the herd or spawns a new one. So the real goal is
narrow: **de-herd while keeping "badness" -- extra and/or late compaction --
small, and without inducing fresh herding at any step.**

This change adds an EXPERIMENTAL, opt-in mechanism that gives each `(DB, column
family)` a stable, well-spread *preferred phase* within the interval and steers
periodic compactions toward it. It is randomized and *uncoordinated* (DBs never
talk to each other), which both keeps it simple and, as it turns out, de-herds
far more cheaply than adding jitter to the interval. It is controlled by a single
mutable DBOption and is **off by default**:

- `periodic_compaction_phase_recovery_percent` (default `0` = disabled / kill
  switch; recommended `33`; clamped to `[0, 100]`) -- how aggressively each
  trigger converges toward the phase, as a percent of the remaining gap, only
  ever pulling the trigger *earlier*. `0` reproduces the classic scheduler
  exactly; `100` jumps to the phase in one cycle; lower values approach gradually
  with much less extra work.

The preferred phase itself needs no tuning: it is derived from the DB ID (stable
across re-open and physical cloning/migration, but distinct across DBs) and each
column family is offset from the DB's base phase, so the CFs of one DB don't fire
together. (An earlier draft exposed a `compaction_schedule_seed` string option
for choosing the phase source; it was over-engineered for the one behavior anyone
needed, and as a mutable string it also broke OPTIONS-file round-trip
verification in crash testing. It has been removed in favor of always using the
DB ID.)

## The two kinds of badness

"Badness" here is the fraction of compaction work that is ideally unnecessary:
over-compaction (rewriting a file earlier than the interval required) plus a
penalty for lateness (going overdue past the interval). Steady state is 0; both
"never compact" and "compact twice as often as needed" approach 1. De-herding
inherently spends a little badness to spread load out; every design choice below
exists to bound one of those two costs *without* re-clustering the fleet.

The scenario names cited in the design bullets below (`upgrade_dynamic_r33`,
`naive_upgrade_r25`, `burst_r*`, `turndown_r33`, `turnup_r33`, ...) all come from
the out-of-tree simulator; see its results page (the "Simulator and
visualization" section below, with an interactive plot per scenario) for the
evidence behind each claim.

## Design

- **A stable per-(DB, CF) preferred phase = one fixed attractor per DB.** Each DB
  gets a base phase from its DB ID; each CF is offset from it. Because the target
  is fixed (not re-rolled each cycle) and unique per DB, the fleet converges to a
  uniform spread and then compacts *on time* -- badness decays toward ~0. This is
  the crucial contrast with naive per-cycle jitter, which de-herds but never stops
  jittering and so pays a permanent over-compaction cost (see
  `naive_upgrade_r25`: flat ~12-14%, only diffusive spreading) vs.
  `upgrade_dynamic_r33`, whose badness decays to ~0 for the same anti-herding.

- **Converge gradually, and only ever earlier.** Each trigger closes a
  `recovery_percent` *fraction* of the gap to the phase, reaching it over a few
  cycles rather than in one burst, and never later than
  `periodic_compaction_seconds` in steady state. A lower rate does less extra
  work, which matters when the phase keeps moving (write-driven / bursty
  workloads), where a high rate wastefully "chases" it -- the burst sweep shows
  `burst_r25 < burst_r33 < burst_r50 < burst_r100` in sustained badness. `33` is
  the default balance: quick spreading, low extra work.

- **Fractional recovery (not "snap") is latency-tolerant and doesn't re-herd.**
  Closing a fraction of the gap gives a single stable convergence point, so
  real-world scheduling/queueing latency -- the delay between a file becoming due
  and its compaction actually running -- merely shifts the whole fleet uniformly
  instead of re-clustering it. A tried "snap onto the phase when in range" variant
  converged a bit faster with no latency but re-herded around the snap
  threshold under latency; it was rejected. Graceful degradation beat peak speed.

- **Spread a suddenly-due cohort instead of bursting it (bounded lateness).**
  Enabling the feature, turning `periodic_compaction_seconds` down, or catching up
  after downtime can leave a whole cohort past due at the same instant. Firing it
  all at once would just be the herd again, so such a cohort is spread over the
  first quarter-interval (~N/4) after the phasing anchor on an *absolute*,
  epoch-aligned per-(DB, CF) grid. This trades a bounded lateness (<= ~N/4 past
  the deadline, so the interval is a *soft* upper bound during these transients)
  for anti-herding, and the absolute grid keeps a rapidly restarting DB from being
  perpetually re-deferred and starved. `turndown_r33` shows the past-due cohort
  smeared over N/4 rather than bursting; `turnup_r33` confirms a turn-up creates
  no past-due cohort (deadlines only grow) and so no burst. For immediate
  enforcement at such a transition, trigger a manual compaction.

- **Off by default, with a kill switch.** Inert unless
  `periodic_compaction_phase_recovery_percent > 0`; at `0` behavior is identical
  to the classic scheduler.

## Implementation notes

- Phase math is integer-only (`Hash64` + `FastRange64`); floating point is avoided
  for its pitfalls. The per-CF offset is a golden-ratio (Weyl/Kronecker) additive
  sequence -- low-discrepancy for any number of CFs.
- Logic lives in `db/periodic_compaction_phaser.{h,cc}`
  (`PeriodicCompactionPhaser`), which owns the recovery percent and the phasing
  anchor and exposes the stateless trigger-time math; `VersionSet` holds one and
  caches the per-CF result on each `Version`. The anchor is (re)set to now on DB
  open, on a recovery-percent change, and on any `periodic_compaction_seconds`
  change -- the three events that can create a past-due cohort.
- Plumbed through the C API, `db_bench`, `db_stress`, and `db_crashtest`.

## Simulator and visualization (out-of-tree, not part of this change)

A standalone simulator drives 40 real RocksDB instances through a real
periodic-task scheduler, records every compaction (periodic vs write-driven), and
scores the badness metric above across ~17 scenarios: dynamic/restart enable,
interval turn-down/turn-up via `SetOptions`, on-cadence writes, random write
bursts with a recovery-rate sweep, and the naive-jitter baseline. It is the basis
for the numbers cited above. Interactive HTML (**NOTE** fb-internal URL sanitization might require you to copy-paste the URL yourself):
5759c6a62d/pc_phase_sim.html

Reviewed By: jaykorean

Differential Revision: D117487278

fbshipit-source-id: a62439548b7dd6fe7dfdbbe21b69531d8b57a1e8
2026-08-26 20:14:26 -07:00
Peter Dillinger
f7b791b213 Fix bottommost-files infinite compaction loop with preserve/preclude (#15146)
Summary:
Leveled compaction combined with preserve_internal_time_seconds or preclude_last_level_data_seconds is a known-broken combination, but it is easy to enable by accident -- for example a fleet-wide config rollout that turns on the preserve time for CFs that happen to be leveled. Rather than degrading gracefully, it triggers an infinite kBottommostFiles compaction loop: a bottommost file gets marked, rewritten with no progress, and immediately re-marked, continuously rewriting the file and growing the MANIFEST so the file set never quiesces. This change makes such a misconfiguration a harmless no-op instead.

Before this change, the marking logic
(ComputeBottommostFilesMarkedForCompaction) marked a bottommost file whenever its largest seqno was below the oldest snapshot, without checking whether a compaction could actually zero that seqno. Recently written seqnos stay within the seqno->time "preserve" window and cannot be zeroed, so the rewrite never made progress. The same futile marking also occurred with user-defined timestamps when full_history_ts_low is unset. The underlying issue is that the "can this bottommost seqno be zeroed?" question was answered independently by the marking logic and by the compaction output path (CompactionIterator::PrepareOutput), and the two had drifted apart.

With this change, both paths answer that question through one shared predicate (BottommostSeqnoCanBeZeroed), and the preserve-window boundary that marking needs is plumbed into VersionStorageInfo from DBImpl, mirroring the existing oldest-snapshot plumbing. Snapshot visibility stays a per-site check. On CFs without preserve/preclude the predicate is a no-op. Confirmed that caller invariants ensure that relocating the `ikey_.sequence <= preserve_seqno_after_` check in CompactionIterator::PrepareOutput() will not trip the ROCKSDB_LOG_FATAL.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15146

Test Plan:
- compaction_picker_test: new CompactionPickerTest BottommostFileNotMarkedWithinPreserveWindow / BottommostFileMarkedBelowPreserveWindow cover the preserve-window boundary; new CompactionPickerU64TsTest BottommostNotMarkedWithEmptyFullHistoryTsLowAndUnknownMaxTs covers the UDT edge case.
- tiered_compaction_test: new PrecludeBottommostLoopTest LeveledPreserveTimeNoBottommostLoop is an end-to-end regression for the loop and the DBImpl plumbing: a bottommost file with a nonzero largest seqno inside the preserve window, made a marking candidate via a snapshot release, must not be marked. It reproduces the loop (hangs) when the plumbing is neutralized and passes with the fix.

Reviewed By: xingbowang

Differential Revision: D117299979

Pulled By: pdillinger

fbshipit-source-id: 51e8e2d4e2116984d16d3c7ef54a9635d5aded74
2026-08-26 19:44:47 -07:00
Peter Dillinger
8f492b2f47 Store common user-key prefix once per block (format_version 8) (#15117)
Summary:
Building on D116105558, this change finishes and publishes `format_version = 8` as
production-ready.

The headline feature is a tweaked block encoding that stores the common user-key
prefix shared by all keys in a block **once**, in the block's leading bytes
`[0, restarts[0])`, instead of repeating it at every restart point. This shrinks
prefix-heavy blocks (and their block-cache footprint) and, for bytewise-ordered
keys, speeds up `Seek` by comparing prefix-stripped suffixes. It is applied to
**data blocks** and **every index tier** (leaf/partition index, the
partitioned-index top-level, and the partitioned-filter top-level).

Motivation: MyRocks/ZippyDB/timeseries-style keys carry a long shared prefix
(table/index id, partition, etc.). Repeating it at each restart wastes space and
cache, and every Seek re-compares those bytes. Profiles show index-block seek CPU
is comparable to data-block seek CPU (partitioned index does two index seeks per
data seek), so index blocks are a first-class target, not just data blocks.

### Format (self-describing, no new footer bit)

- The common prefix occupies the block's leading bytes; a non-zero `restarts[0]`
  (the offset of the first entry, i.e. the prefix length) self-signals its
  presence. `restarts[0]` is 0 in every block that does not use the feature (all
  versions, all block types), so a single fv8 reader transparently handles any
  mix of stripped and unstripped blocks -- no footer bit is consumed.
- Previous versions would misinterpret this encoding by quietly ignoring the
  common prefix, which (along with the lack of extensibility in data-block
  metadata) is why it is gated behind format_version 8.
- Only restart-point entries change on disk; non-restart entries are byte
  identical to fv7, so the writer produces the exact same bytes regardless of
  implementation.

### Public option

New `BlockBasedTableOptions::optimize_key_common_prefix`
(`enum class OptimizeKeyCommonPrefix`), default `kIfFastSeek`:

- `kDisabled` -- never use it.
- `kIfFastSeek` (default) -- use it only where it also enables the Seek speedup:
  format_version >= 8 with a built-in (reverse-)bytewise comparator.
- `kEnabled` -- use it for all comparators at fv8; (reverse-)bytewise get space +
  fast seek, other comparators get the space savings only (the reader
  reconstructs full keys and compares with the real comparator).

Expressed as a criteria so it need not be toggled per CF based on the comparator
(which can vary by CF). It additionally requires `use_delta_encoding` (the
feature builds on delta encoding) and `ts_sz == 0`; when `use_delta_encoding` is
false -- the setting that also preserves zero-copy key pinning -- it is a no-op.
Default format_version is unchanged (still 7); the feature is opt-in via fv8.

### Scope: every index tier

The reader handles all index-like blocks uniformly (a non-zero `restarts[0]`
self-signals a stripped block regardless of block type), so the writer strips the
common prefix from every tier:

- **Data blocks** and **leaf / partition index blocks**: enabled.
- **Partitioned-index top-level block**: enabled. Under the default
  `index_shortening = kShortenSeparatorsAndSuccessor` the file's last separator
  would otherwise be a short successor sharing no leading bytes, collapsing the
  single-block top-level's common prefix to 0. When common-prefix is on the
  builder suppresses that one successor (keeps the full last key -- i.e.
  `kShortenSeparators` behavior for that one entry), so the top-level strips
  uniformly under every shortening mode; the full last key is also a tighter file
  upper bound, so it is not worse.
- **Partitioned-filter top-level index**: enabled (same uniform treatment). Its
  space/CPU effect is small at default partitioning (the top-level filter index
  is tiny) but grows with many small partitions -- a filter miss short-circuits
  at, and is dominated by, the top-level filter-index seek (see Performance).
  Included for a uniform on-disk surface across tiers.

Bloom filter *contents* still hash full user keys, so prefix/whole-key filtering
is unaffected.

### Implementation

- Writer (`block_builder.{h,cc}`): keys are encoded incrementally in the fv7
  layout while the running common prefix is tracked cheaply (reusing the delta
  `shared`); at `Finish()` only the restart-point entries are rewritten in place
  with the prefix stripped, and the prefix is written once at the block start.
  The rewrite is generalized to the value-delta (fv4) index layout used by index
  blocks (no `value_size` header; bulk-copy the inline BlockHandle and
  non-restart entries verbatim).
- Reader (`block.{h,cc}`, `dbformat.h`): `Block` decodes the prefix from
  `restarts[0]`; `BlockIter::ParseNextKey` prepends it on a restart point
  (`shared == 0`). The suffix-seek helpers (strip target once, compare shortened
  suffixes) live in the templated `BlockIter` base and are shared by data and
  index iterators (binary search, templated on `DecodeKey`/`DecodeKeyV4`); for
  non-bytewise comparators (data) and the hash `PrefixSeek` refine step, the
  reader reconstructs the probed restart key and compares full keys.
- Gating (`block_based_table_builder.cc`, `index_builder.{h,cc}`,
  `partitioned_filter_block.{h,cc}`): the criteria is threaded to the data-block
  builder and to every index tier -- leaf/partition index, the partitioned-index
  top-level (`use_common_prefix_top`), and the partitioned-filter top-level --
  together with the last-separator successor suppression
  (`ShortenedIndexBuilder::GetSeparatorWithSeq`) that lets the top-level strip
  under the default shortening mode. The index base gate also requires
  `super_block_alignment_size == 0` (block alignment can trigger
  `skip_delta_encoding`, producing non-restart `shared == 0` entries the reader
  could not distinguish from stripped restart keys).
- Memory: the per-block/per-iterator prefix is stored as a `uint32_t` length
  (the prefix is always at offset 0) rather than a full `Slice`, and `Block` /
  `BlockBuilder` members were repacked so the feature adds little or no struct
  size.
- Java: a new `OptimizeKeyCommonPrefix` enum and a `BlockBasedTableConfig`
  getter/setter, plumbed through JNI in both directions (the
  `newTableFactoryHandle` writer and the options-reconstruction constructor).

### Whole-file (table-level) prefix: investigated and rejected

A natural extension is to store the whole-file common prefix once per *file*
(when an `SstPartitioner` guarantees every key in the file shares a fixed prefix)
instead of once per *block* -- e.g. the MyRocks index/table id, or a future
128-bit global id. It was prototyped end-to-end (partitioner-provided prefix
captured at file open, stored in a `user_key_common_prefix` table property, and
stripped from every data/index block; the reader re-prepends it) and then
dropped. The `user_key_common_prefix` table property reserved in the
previous diff is dropped in this one. The rationale, in case it is revisited:

- Marginal benefit over per-block. Per-block already strips the full shared
  prefix from every key; the only thing whole-file removes on top is the single
  per-block copy of that prefix, i.e. `~= prefix_len / block_size` of the
  uncompressed (block-cache) footprint, with no meaningful read-CPU change
  (per-block already delivers strip-once + suffix compares). Measured at
  prefix_len=12, 4 KB blocks this was ~-0.3% incremental space; at the 8-16 KB
  data blocks typical of the space-constrained target (MyRocks/UDB) it is
  ~0.1-0.2% even with a 16-byte global id -- a fraction of a percent, seen only
  by partitioner users. Small block-size workloads are becoming more
  niche, and even where they exist, they are also CPU-sensitive (see next
  paragraph).

- Real, if small, CPU tax on the *per-block* path. Reconstructing a key from two
  non-contiguous parts (file prefix + block residual) cost ~0.5-1.0% additional
  retired instructions on the per-block read path (deterministic across gcc and
  clang), wall-clock-neutral in cache-resident tests but present even when no
  whole-file prefix is configured. Attempts to remove it did not pan out: a
  compile-time specialization of the reconstruction *increased* instructions
  (+2.9%) by defeating inlining; a per-block combined-prefix buffer trades the
  CPU cost for a memory cost on every (default) block; and a per-iterator buffer
  was the original larger regression. Only invasive iterator-level
  monomorphization would truly zero it, which is not justified on this hot path.

- Decision. A fraction-of-a-percent, partitioner-only space win does not justify
  a permanent per-block-read-path CPU tax plus additional on-disk format surface
  (a table property that changes whole-file interpretation). Dropped. The generic
  `SstPartitioner::ShouldPartitionByPrefix` hook is kept (cheap, and useful
  independently), and the reserved `user_key_common_prefix` table property is
  removed since no writer will emit it.

### Performance (db_bench, release/native, cache-warm, best-of-3, pinned)

Three arms: (A) fv7 on the base/main branch, (B) fv7 with this change (should be
neutral / byte-identical writing), (C) fv8 with this change and
`optimize_key_common_prefix=enabled` (the optimization is active). Partitioned
index + filters; `compression_type=none`, so SST bytes are the uncompressed
block-cache footprint. `int` = db_bench default keys (variation-early, near worst
case); `pfxN` = an N-byte shared prefix + varying suffix (MyRocks-like).
Setup: `num=3,000,000`, `reads=1,500,000`, single thread,
`metadata_block_size=4096`, cache holds the working set, pinned to one core.

| shape (key/val) | arm | SST bytes | readrandom us/op | seekrandom us/op |
|---|---|---:|---:|---:|
| int (32/8)    | A fv7 main | 138,983,658 | 3.484 | 4.995 |
|               | B fv7 ours | 138,983,146 | 3.518 | 5.024 |
|               | C fv8 ours | 137,681,420 | 3.490 | 4.986 |
| pfx24 (32/8)  | A fv7 main |  71,697,531 | 3.417 | 5.561 |
|               | B fv7 ours |  71,698,043 | 3.448 | 5.579 |
|               | C fv8 ours |  65,578,236 | 3.284 | 5.386 |
| pfx56 (64/16) | A fv7 main | 102,942,266 | 3.624 | 5.830 |
|               | B fv7 ours | 102,942,266 | 3.678 | 5.835 |
|               | C fv8 ours |  90,554,642 | 3.444 | 5.626 |

fv8 vs fv7-main (C vs A) -- space / readrandom / seekrandom:

- int (32/8):    **-0.9%**  / ~neutral  / ~neutral
- pfx24 (32/8):  **-8.5%**  / **-3.9%** / **-3.1%**
- pfx56 (64/16): **-12.0%** / **-5.0%** / **-3.5%**

fv7-ours vs fv7-main (B vs A): SST bytes byte-identical (within 512 B; the fv8
paths are gated off), read/seek us/op within run-to-run noise (~1%, best-of-3 on
a shared host). Net: fv8 is a space **and** read/seek-CPU win on prefix-heavy
data and neutral on variation-early (`int`) data, so `kIfFastSeek` is safe on by
default at fv8.

### Space by tier (compression=none, num=3M, mdb=4096; fv7 -> fv8)

Where the win comes from -- the index tiers strip the most:

| shape | data | leaf/partition index | top-level index | filter |
|---|---:|---:|---:|---:|
| int   | -0.8%  | -29% | -45% | -0.2% |
| pfx24 | -8.3%  | -69% | -89% | -0.8% |
| pfx56 | -11.1% | -83% | -96% | -1.6% |

Data bytes dominate the absolute total, but every index tier is stripped:
leaf/partition index -29% to -83% and the partitioned-index top-level -45% to
-96%. Filter *blocks* barely move (-0.2% to -1.6%) because filter bits hash full
user keys; only the small top-level filter index is stripped.

### Point-miss lookups (readmissing)

A partitioned-filter miss short-circuits at the filter (no data-block read), so
it is dominated by the top-level filter-index seek plus the partition bloom. At a
typical `metadata_block_size=4096` (few partitions, tiny top-level filter index)
fv8 is neutral on misses (int +0.9%, pfx24 -1.0%, pfx56 -0.7% us/op -- all
noise). The top-level-filter stripping pays only with many small partitions: at
`metadata_block_size=256` it is pfx24 1.166->1.138 (**-2.4%**) and pfx56
1.273->1.188 (**-6.7%**) us/op.

### Backward / forward compatibility

- Default `format_version` remains 7; the feature is inert unless a CF opts into
  fv8. Older RocksDB rejects fv8 files at footer decode (fail-safe).
- Within fv8, the stripped-block set a writer can emit is fixed and every fv8
  reader can read it (the `restarts[0]` signal plus uniform reader handling), so
  the on-disk surface is self-consistent across builds/downgrade.

### Follow-ups (not in this change)

- Suffix-aware interpolation / hash index seek (currently correct via the
  reconstruction fallback, not perf-tuned).

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15117

Test Plan:
- New focused unit tests (`table_test`), run at the full `format_version` matrix
  including the now-supported fv8:
  `GeneralTableTest.OptimizeKeyCommonPrefix` (data-block space+read+Seek
  correctness: existing and missing keys straddling the prefix, before/after-all,
  SeekForPrev), `OptimizeKeyCommonPrefixNonBytewise` (reconstruction path), and
  `IndexBlockCommonPrefixEquivalence` across `{kBinarySearch,
  kBinarySearchWithFirstKey, kHashSearch, kTwoLevelIndexSearch}` x
  `{partitioned filters}` x `{bytewise, reverse-bytewise}` (so both top-level
  tiers -- partitioned-index and partitioned-filter -- are exercised), asserting
  identical reads and a shrunken fv8 index.
- The randomized `ParameterizedHarnessTest` now runs at fv8 (reverse-bytewise,
  restart intervals `{1,16,1024}`, special/empty keys fuzzed against the feature).
- `db_crashtest.py` randomizes `format_version` including 8 and
  `optimize_key_common_prefix` in {0,1,2}.
- Removed the reserved-property carve-out: dropped the `ReservedUserKeyCommonPrefix`
  reader test and the C API getter for the property; C API regenerated via
  `tools/c_api_gen/` (`make regen-c-api`), and the intentional public-C-API
  removal is recorded in `tools/c_api_gen/api_compatibility_allowlist.json`.
- Java: `BlockBasedTableConfigTest.optimizeKeyCommonPrefix` (default
  `kIfFastSeek`, plus set/get of every value).
- `table_test` `FooterTests` updated for the now-published fv8 footer, whose
  metaindex-gap encoding stores the metaindex block size in 16 bits (the test
  caps that size for fv >= 8).
- Manual verification: the db_bench runs in Performance above (three-arm
  space + read/seek CPU, per-tier byte breakdown via `sst_dump`, and
  `readmissing`); and `readrandom`/`seekrandom` with
  `--block_protection_bytes_per_key=8` find 100% of keys at both fv7 and fv8
  (reconstructed keys pass per-key checksum).

Reviewed By: xingbowang

Differential Revision: D116365016

Pulled By: pdillinger

fbshipit-source-id: 66f4b5c60ea4fe0ff48d34c98d191ba8ebdf0665
2026-08-20 17:32:41 -07:00
rocksdb-release-bot
330ded5129 Update version to 11.10.0 for 11.9 release (#15135)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15135

- Bump version.h from 11.9.0 to 11.10.0
- Add `11.9.fb` to `check_format_compatible.sh`
- Sync HISTORY.md `## 11.9.` section from `11.9.fb`
- Delete 17 consumed `unreleased_history/` note file(s) from main

Part of 11.9 release workflow.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15096

Reviewed By: anand1976

Differential Revision: D116833037

Pulled By: joshkang97

fbshipit-source-id: 04ebda8db5b2c4fdae71a2cf107784b6abdc0f3c
2026-08-20 15:13:31 -07:00
Xingbo Wang
42fca1bb39 Enable core dumps for sanitizer crash tests (#15131)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15131

ASAN and other sanitizer runtimes can disable coredumps or terminate without a core after the test driver sets its shell core limit. This leaves sanitizer stress-test failures with an LLDB stack but no core for the DB artifact.

Configure ASAN_OPTIONS, TSAN_OPTIONS, and UBSAN_OPTIONS during sanitizer crash-test steps so reports terminate with a core-generating abort and core dumping remains enabled. Preserve caller-provided options and the existing Make fallback semantics. Keep the behavior scoped to test steps and crash targets.

Enabling sanitizer cores raises a concern that the generated core-dump artifacts can be large because they include the process working set. Reduce RocksDB's cache and memtable contribution to the core size without replacing the normal randomized test configuration. Add a db_crashtest cache/write-buffer size multiplier that is applied after each iteration chooses its values, and pass 0.5 from ASAN, TSAN, and UBSAN crash lanes. For the simple profile, cache_size remains randomized with 8/32 MiB becoming 4/16 MiB, while the 32 MiB write buffer becomes 16 MiB. Preserve RocksDB's 64 KiB minimum write-buffer size and leave non-sanitizer lanes unchanged.

Reviewed By: rban1

Differential Revision: D116666171

fbshipit-source-id: aa157cfa255bb637d4a68e54ea2039db779fdc53
2026-08-20 04:59:40 -07:00
Peter Dillinger
d3a82f28ce Optimize SstPartitioner overhead in compaction via ShouldPartitionByPrefix() (#15118)
Summary:
When a column family configures an SstPartitioner, compaction paid a per-key cost for every key-value written to an output SST: a full user-key copy (into `last_key_for_partitioner_`) plus a virtual `SstPartitioner::ShouldPartition()` call (which also builds a PartitionerRequest and truncates/compares two Slices).

This is motivated by an internal user whose custom SstPartitioner parses a variable-length prefix (a prefix code) out of each key to decide partition boundaries; today that prefix parsing runs on every key written during compaction, on top of the copy and virtual call above.

This wires up the `SstPartitioner::ShouldPartitionByPrefix()` API (added, but unused, alongside the format_version=8 groundwork) so compaction can skip that per-key work. When a partitioner can express its next boundary as a key prefix, `CompactionOutputs` caches that prefix once per output file (re-derived from the file's first key) and replaces the per-key copy + virtual call with an inlined `Slice::starts_with()` check. That also lets a partitioner like the one above do its prefix parsing once per output file instead of once per key. When the partitioner returns no prefix (ambiguous, e.g. `SstPartitionerFixedPrefix` for keys shorter than its length), compaction falls back to the previous per-key `ShouldPartition()` path. Output file boundaries are byte-for-byte identical to before. `SstPartitionerFixedPrefix` implements `ShouldPartitionByPrefix()`; the base-class default returns "no prefix", so existing custom partitioners are unaffected until they opt in.

Also:
- db_bench: new `--sst_partitioner_fixed_prefix_len` to configure a fixed-prefix partitioner (enables benchmarking; no default behavior change).
- `ShouldPartitionByPrefix()` API doc updated now that it is used and recommended for compaction CPU efficiency (any prefix code works, not just fixed-size).

No new option and no behavior or format change.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15118

Test Plan:
Added db_compaction_test cases covering the new fast path and its fallbacks: many keys sharing a prefix (one output file per prefix, no spurious cuts), keys shorter than the fixed prefix length (fallback to ShouldPartition), the zero-length-prefix "never partition" case, and short<->long key transitions across output-file boundaries. Existing SstPartitioner tests and the range tombstone + partitioner tests (db_range_del_test) provide regression coverage that file boundaries are unchanged, including the fast path with range tombstones. The crash test already exercises the partitioner via `enable_sst_partitioner_factory`.

Benchmark (db_bench `compact` of a pre-built 50M-key DB; 16B keys / 8B values, 8-byte shared key prefixes with FixedPrefix length 8, no compression, single subcompaction). A/B compares this change against the same binary with only the compaction hot-path change reverted. Means of 6 trials, optimized faster on every trial and every metric; both variants produced identical output (50 files, i.e. identical partition boundaries):

| Metric                          | Baseline | Optimized | Delta  |
|---------------------------------|----------|-----------|--------|
| Instructions (whole process)    | 92.46 B  | 90.09 B   | -2.6%  |
| CPU cycles                      | 46.20 B  | 45.28 B   | -2.0%  |
| CPU time (task-clock)           | 18.74 s  | 18.39 s   | -1.9%  |
| `compact` wall time             | 20.58 s  | 20.08 s   | -2.4%  |

DB-internal compaction stats corroborate: CompMergeCPU 15.84s -> 15.42s (-2.6%), compaction write throughput 38.9 -> 39.9 MB/s. The relative gain shrinks with larger values or compression enabled, where the partitioner is a smaller fraction of total compaction work; it is a pure win whenever a prefix partitioner is configured.

Reviewed By: rban1

Differential Revision: D116372363

Pulled By: pdillinger

fbshipit-source-id: 63de190329112aa2ce5b48da9fc77ef386494558
2026-08-19 11:43:10 -07:00
Xingbo Wang
d8536d3274 Enable continuous verification for secondary stress tests (#15114)
Summary:
PR https://github.com/facebook/rocksdb/issues/15066 fixed the bug that has caused this combination fails in the past. Re-enable this combination in the stress test.

db_crashtest.py currently disables continuous_verification_interval whenever test_secondary=1. This change removes that quarantine and enables continuous verification for secondary DB stress tests.

The disable predates auto_refresh_iterator_with_snapshot. When auto-refresh was later added, its stress-test integration mechanically added snapshot setup to several iterator paths, including the secondary-
only NonBatchedOpsStressTest::ContinuouslyVerifyDb() path:

snapshot = std::make_unique<ManagedSnapshot>(db_);
read_opts.snapshot = snapshot->snapshot();

This creates a snapshot from the primary db_ and passes it to an iterator created from secondary_db_. That was never supported: a ReadOptions snapshot must belong to the DB being read, and secondary DB
iterators explicitly reject non-null snapshots.

The incompatibility went unnoticed because the existing sanitizer made this combination unreachable in recurring crash tests. It was therefore a stress-test integration bug, not evidence that snapshot-
preserving auto-refresh was supported by secondary DBs.

This change:

- Stops forcing continuous_verification_interval=0 for secondary tests.
- Avoids attaching a primary-owned snapshot to secondary iterators.
- Continues exercising auto_refresh_iterator_with_snapshot through primary iterator paths.

Secondary iterators still support ordinary Iterator::Refresh(), which advances them to the latest caught-up secondary state. Snapshot-preserving automatic refresh remains unsupported.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/15114

Test Plan:
- python3 tools/db_crashtest_test.py
- make check-sources
- AUTO_CLEAN=1 make -j192 db_stress
- Targeted blackbox crash test with:
- test_secondary=1
- continuous_verification_interval=1000
- auto_refresh_iterator_with_snapshot=1

Reviewed By: jaykorean

Differential Revision: D116381630

Pulled By: xingbowang

fbshipit-source-id: a162ab357a537c2e5047c1eda85d0c2138b56ed1
2026-08-19 11:34:01 -07:00
zaidoon
136d0d1fa1 Add UDI index mode API vocabulary (#14959)
Summary:
Part 2 of 13 in the UDI split.

Stack order:
1. https://github.com/facebook/rocksdb/issues/14954 Add IndexFactory compatibility names
2. https://github.com/facebook/rocksdb/issues/14959 Add UDI index mode API vocabulary
3. https://github.com/facebook/rocksdb/issues/14960 Add optional UDI builder protocols
4. https://github.com/facebook/rocksdb/issues/14961 Add built-in index factory wrappers
5. https://github.com/facebook/rocksdb/issues/14969 Add built-in index factory tests
6. https://github.com/facebook/rocksdb/issues/14962 Promote IndexFactory as UDI SPI
7. https://github.com/facebook/rocksdb/issues/14963 Wire IndexFactory through block-based tables
8. https://github.com/facebook/rocksdb/issues/14964 Add IndexFactory table routing tests
9. https://github.com/facebook/rocksdb/issues/14970 Add IndexFactory parallel routing tests
10. https://github.com/facebook/rocksdb/issues/14965 Account UDI blocks as index blocks
11. https://github.com/facebook/rocksdb/issues/14966 Support parallel AddIndexEntry in trie index
12. https://github.com/facebook/rocksdb/issues/14967 Cover trie index modes
13. https://github.com/facebook/rocksdb/issues/14968 Add IndexFactory stress and benchmark flags

Previous: https://github.com/facebook/rocksdb/issues/14954.
Next: https://github.com/facebook/rocksdb/issues/14960.

Depends on https://github.com/facebook/rocksdb/issues/14954. Until https://github.com/facebook/rocksdb/issues/14954 lands, GitHub shows the cumulative diff against main. After that lands, the intended review diff is:

`zaidoon/udi-01-index-factory-compat-shim..zaidoon/udi-02-index-mode-api`

That final review delta is 4 files with 52 insertions.

What changed:
- Add inert IndexMode and ReadIndex vocabulary to the public options headers.
- Block those experimental enum fields from automatic C API generation until the explicit C API mapping lands later in the stack.
- Keep behavior unchanged. Runtime wiring comes in later PRs.

Validation:
- `git diff --check`
- `make check-sources`
- `python3 tools/c_api_gen/verify_generated_up_to_date.py`
- `AUTO_CLEAN=1 make check-c-api-gen`
- `./c_test`

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14959

Reviewed By: jaykorean

Differential Revision: D116211178

Pulled By: xingbowang

fbshipit-source-id: 42edd3f0991d90b0b5c90232e5023de6e3740dac
2026-08-19 10:31:27 -07:00