Compare commits

...
80 Commits
Author SHA1 Message Date
Xingbo Wang 21a7975dfd 11.13.2 patch release 2026-10-28 12:58:03 -07:00
Xingbo Wang bf1a9a1a20 Fix async secondary catch-up on deleted SSTs (#15438)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15438

Fix a RocksDB secondary with `open_files_async = true` latching on `PathNotFound` during `TryCatchUpWithPrimary()`.

**Why:** With `open_files_async = true` and `max_open_files = -1`, a secondary's catch-up could fail with `PathNotFound` on a table that was live at open time (most reachably in a non-default column family) but which the primary compacted away before the background async opener pinned it. The error latched in the tailer's status, so the secondary never advanced again and had to be closed and reopened. With `open_files_async = false` the same sequence caught up fine. This blocks enabling async secondary open where it would otherwise cut cold-open time substantially.

**Previous behavior:** In `VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit`, a negative-edge `LoadTableHandlers()` failure skipped the unrecoverable point-in-time only when the status was `Corruption`; a `PathNotFound` was returned as-is and latched. During a secondary's catch-up, `open_files_async` leaves recovery-time tables unpinned until the background opener reaches them, so `LoadTableHandlers()` could try to open a file the primary had already deleted.

**New behavior:** That negative-edge failure path now also treats `PathNotFound` as a missing file (skip this point-in-time and continue) when `no_error_if_files_missing_` is set, mirroring the tolerance `VersionEditHandler::LoadTables()` already applies. Catch-up continues past the transient state and installs the current version, so the secondary advances instead of latching.

**Implementation:**
- `db/version_edit_handler.cc`: broaden the negative-edge skip condition in `VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit` from `s.IsCorruption()` to also include `s.IsPathNotFound() && no_error_if_files_missing_`.
- `db/db_secondary_test.cc`: add `DBSecondaryTest.OpenFilesAsyncNonDefaultCfCatchUp`, a regression test that opens an async secondary with a non-default column family, holds the background opener back so the open-time table stays unpinned, has the primary flush and `CompactRange(kForce)` to replace and delete that table, then asserts `TryCatchUpWithPrimary()` succeeds and reads the new value.

**Safety boundaries:** The change only affects point-in-time handlers that opted into missing-file tolerance (`no_error_if_files_missing_`, set by secondary/follower tailing and best-efforts recovery); it does not relax error handling for strict recovery. It does not change the `trust_manifest_recovery_` / `defer_sst_file_opening_` paths, which already skip opening.

**Validation and limitations:** The regression test covers async secondary catch-up after deletion of an open-time SST in a non-default column family. Validation covers async opening, catch-up, memtable retention, and repeated scheduling changes. Strict recovery, API and file formats are unchanged. The full supported platform matrix has not been run locally.

Reviewed By: jaykorean

Differential Revision: D125378257

fbshipit-source-id: 3dd23c2f763e4e744c9f38fb77dfdfb3f554ee7f
(cherry picked from commit 81369fc170)
2026-10-28 12:57:16 -07:00
cm-wang 163db5ee28 Match blob direct write generations to the memtables a flush picked (#15481)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15481

With blob direct write, each memtable switch moves the column family's open blob files into a new generation, queued in `BlobFilePartitionManager::pending_generations_`. A flush then seals and publishes the generations of the memtables it flushes.

`PrepareFlushAdditions(n)` always used the first `n` queued generations, and `CommitPreparedGenerations(n)` always popped the first `n`. That is only right if flushes pick, and finish, the oldest memtables in order. With more than one flush running for a column family (`max_background_flushes` > 1, or `max_background_jobs` >= 8), a second flush skips the memtable the first one is still flushing and picks a newer one, but prepares the oldest generation, which belongs to the first flush. Both flush edits then add the same blob file, and installing them fails with `Corruption: VersionBuilder: Blob file #N already added`, which stops the database. The newer memtable's own blob files are never published, and committing the second flush pops a generation it did not own. Atomic flush prepares before it waits to install in order, so it has the same problem.

Removing generations by the flush's own memtables is not enough either. A newer flush that finishes first only marks its memtable completed: the older flush installs both, or rolls the newer one back too if it fails. A generation removed by the newer flush is then gone when the retried flush looks for it (`Corruption: Missing blob direct write generation metadata for flush`).

Fix:
- Tag each generation with the ID of the memtable whose switch created it (`RotateCurrentGeneration(memtable_id)`). `PrepareFlushAdditions()` takes the IDs of the memtables the flush picked and seals exactly those generations, in both flush paths.
- Remove generations only once installed: after a successful flush, `CommitFlushedGenerations()` drops the generations of the memtables older than the earliest one still in the immutable list (memtables leave it only by installation, in order), and stops tracking their sealed files as manager-owned.

Reviewed By: xingbowang, archang19

Differential Revision: D126852567

fbshipit-source-id: 910bf466674cf8bc89bc3ccbbcf08c684968ad72
(cherry picked from commit 6d03b3df45)
2026-10-28 12:56:50 -07:00
Josh Kang 533ba40a62 [rocksdb] Update history for 11.13.1 patch 2026-10-26 12:03:08 -07:00
Josh Kang 834b2c21cb [rocksdb] Patch 11.13.fb with tombstone conversion fixes
Backport D126796014 (PR #15451) and D126875720 (PR #15461) onto
11.13.fb to prevent permanent data loss when read-path range tombstone
conversion scans a partial tier or an iterator view predating ingestion.
Retain both fixes' regression tests and release notes.
2026-10-26 11:25:29 -07:00
meta-rocksdb-release-automation[bot] c7c3047b90 Update HISTORY.md for 11.13.0 release 2026-10-22 10:31:26 +00:00
Josh Kang babce11705 Add public internal key encoding utilities (#15396)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15396

## Summary

`ParseEntry()` exposes the components of an internal key, but constructing one with a chosen sequence number and entry type currently requires private RocksDB utilities or duplicating the encoding. Add public `EncodeEntry()` to construct a stored internal key from `ParsedEntryInfo`, including its user key, timestamp, sequence number, and entry type. Also expose `PackSequenceAndType()` and `UnPackSequenceAndType()` to work with the sequence/type footer as a `uint64_t`.

The helpers map all nine representable public entry types to their internal types and use the existing footer encoding; `EncodeEntry()` reuses the public packer. Packing `kEntryOther` or a sequence number above the 56-bit limit returns `InvalidArgument` without changing the output. Unpacking reports internal value types without a public equivalent as `kEntryOther`. Timestamp size remains the caller's responsibility, and input slices must not alias the output string. These are additive C++ utilities using the existing internal-key format.

Reviewed By: anand1976, xingbowang

Differential Revision: D125082298

fbshipit-source-id: 6c09a31695f1d377b9cc208fa5b6edd3ad6638e2
2026-10-21 13:08:03 -07:00
Andrew Chang ee305ac1c3 Avoid a TLS init call on every perf counter update (#15428)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15428

RocksDB counts per-operation statistics (blocks read, bytes read, mutex waits,
timings) in `perf_context`. It is a `thread_local` variable: each thread has its
own copy, so threads never contend on it. This per-thread copy is "thread-local
storage" (TLS).

**Problem**: `PerfContext` has a non-trivial destructor. For such a variable, C++
must make sure the calling thread's copy is constructed before each use from
another source file, so the compiler emits a call to a small "TLS init"
function. That call goes through the PLT (procedure linkage table, the jump
table used to call functions that may live in another shared library). Every
`PERF_COUNTER_ADD` therefore paid a real function call before incrementing a
counter, and these macros run in hot loops such as key comparisons during block
iteration. `perf_level`, the on/off switch, has a smaller cost: it is
`extern thread_local` without `constinit`, so each read also tests whether it
has been initialized.

**Fix**:
- The perf macros read a `constinit thread_local PerfContext*` that each
  thread sets on first use. After that, a counter update is a pointer load,
  a null check and the increment.
- `perf_level` is declared and defined `constinit`, so reading it is a plain
  thread-local load.
- No public API change. `perf_context`, `get_perf_context()` and macro
  behavior are unchanged, and so is thread-exit behavior.

**Results**:
- Microbenchmark (`PERF_COUNTER_ADD` in a loop): 2.10 -> 0.84 ns per update
  with counting enabled; unchanged at 0.42 ns with counting disabled.
- Calls to the TLS init functions drop from 40 and 96 to 0 in block.cc.o
  and memtable.cc.o.
- In CPU profiles of large production RocksDB deployments, these accessors
  cost roughly 0.3-0.6% of total CPU. This change removes most of the
  `perf_context` part.

Reviewed By: xingbowang

Differential Revision: D125982209

fbshipit-source-id: f38b4fffda09611d28322e7534b135fb35631389
2026-10-21 11:47:50 -07:00
Anand Ananthabhotla 074134d98f Stop writes on retryable background IO errors when auto resume is disabled (#15444)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15444

**Why the change is required**

`max_bgerror_resume_count <= 0` turns off automatic `DB::Resume()`, so the application is responsible for recovering from background errors. Before automatic recovery was introduced, a background error under `paranoid_checks` latched `bg_error_` and made the DB read-only. Since then, two classes of retryable IO error are either never latched or latched as soft errors that leave the DB accepting writes. When auto resume is disabled, no one recovers from them and the application gets no clear signal.

**Previous behavior** (with `max_bgerror_resume_count <= 0`)

- A retryable or file-scoped IO error during compaction was never set in `bg_error_`. Compaction rescheduled itself and retried indefinitely. A persistent failure was visible only through `EventListener::OnBackgroundError` and the LOG, while compaction debt grew until writes slowed or stopped on L0/pending-compaction triggers.
- A retryable or file-scoped IO error while writing a flush SST or the MANIFEST after a successful WAL sync (`kFlushNoWAL` / `kManifestWriteNoWAL`, which covers almost every retryable flush failure, not only `disableWAL` writes) was a soft error with `soft_error_no_bg_work_` set. Background work stopped, but writes kept succeeding into the memtable. Nothing retried the flush, so writers eventually blocked indefinitely on the memtable limit instead of failing.

**New behavior**

With `max_bgerror_resume_count <= 0`, every retryable or file-scoped background IO error that reaches the retryable path in `ErrorHandler::SetBGErrorImpl` is latched as `kHardError`, whatever the reason (compaction, flush, MANIFEST, WAL). Writes fail immediately and reads continue to work. `DB::Resume()` clears the error, flushes and reschedules compaction as before.

**Implementation**

In `SetBGErrorImpl`, the compaction special case (notify the listener and return without setting `bg_error_`) now applies only when auto resume is enabled, and the severity selection maps everything to `kHardError` when auto resume is disabled. Because `kErrorRecoveryRetryFlush` is no longer selected in that mode, `soft_error_no_bg_work_` is not set. The option documentation in `include/rocksdb/options.h` and the comment above `SetBGError` are updated.

**Safety, compatibility, and limitations**

- This is an intentional behavior change, limited to DBs that set `max_bgerror_resume_count <= 0`. With the default (`INT_MAX`) or any positive value, nothing changes.
- fbcode callers that set the option to 0, and their apparent expectations:
  - `unicorn/live_updates/dispatcher/MessageStore.cpp`: the comment says "we do not allow any [background errors] as they should be retried by the underlying FS". Stopping on retryable compaction errors fits that intent better than silent retries.
  - `reliable_volumes/client/index_manager/RocksDBIndexStorage.cpp`: a JustKnob (`disableRocksDbBackgroundErrorAutoResume`) disables auto resume alongside other corruption-detection options, which suggests it wants background errors surfaced rather than masked.
  - `zippydb/server/store` (`RocksDBStoreResumeWritesTest`): uses 0 with `disableWAL` and drives recovery through its own `ResumeWrites` task calling `DB::Resume()`, so it already expects writes to stop until resume. I read the test and expect it to pass, but did not run it. No production zippydb configuration setting 0 was found.
- Out-of-space errors (`kNoSpace`/`kSpaceLimit`) are untouched: they skip this path and are still recovered through `SstFileManager`, which does not consult `max_bgerror_resume_count`. Errors ignored under `paranoid_checks=false` are also unchanged.
- No API, ABI or on-disk format change.
- Found during testing, pre-existing and not fixed here: `SetBGErrorImpl` releases the DB mutex in `NotifyOnBackgroundError` before calling `StartRecoverFromRetryableBGIOError`. If the last failed auto-resume attempt's flush error is reported in that window, the recovery thread can give up and clear `recovery_in_prog_` first, and the late report then starts a new recovery with a fresh retry budget, so `max_bgerror_resume_count` can be exceeded. This only applies with auto resume enabled. Worth a separate fix.

Reviewed By: joshkang97

Differential Revision: D126453053

fbshipit-source-id: 3f4be1d12a8d44552313b022db5afe200bc1c6ce
2026-10-20 20:21:11 -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
Jay Huh a7f6f33ccc Keep OPTIONS file alive while GetLiveFilesStorageInfo reads its size (#15430)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15430

A db_stress blackbox crash test failed in verification with:
`GetLiveFilesStorageInfo failed: IO error: No such file or directory: while stat a file for size: .../OPTIONS-857089`

Since D123104845, `GetLiveFilesStorageInfoImpl` picks the OPTIONS file number while holding the DB mutex, releases the mutex, and only then calls `GetFileSize` on that file. If a `SetOptions()` runs in that window, it writes a new OPTIONS file, and `MaybeDeleteObsoleteOptionsFiles()` keeps only the newest two plus the MANIFEST-committed one. So the file that was just picked can be deleted before it is stat'ed. Checkpoint calls `DisableFileDeletions()` first and is not affected, but a plain `GetLiveFilesStorageInfo()` call, as db_stress makes, gets a spurious IO error.

The fix increments `disable_delete_obsolete_files_` (`DisableFileDeletionsWithLock()`) before releasing the mutex, and decrements it again under the mutex right after the size read. `MaybeDeleteObsoleteOptionsFiles()` already skips cleanup while the counter is non-zero. Any OPTIONS cleanup skipped during that window happens on the next OPTIONS write.

The decrement is done directly rather than through `EnableFileDeletions()`. That method is virtual, and `DBImplReadOnly` and `CompactedDBImpl` override it to return `NotSupported` without decrementing. Read-only DBs do reach this code (their OPTIONS file number is set at open), so going through `EnableFileDeletions()` left the counter stuck at 1 after every call. Calling the non-virtual `DBImpl::EnableFileDeletions()` instead is not safe either: when the counter reaches 0 it runs a forced `FindObsoleteFiles` scan and `PurgeObsoleteFiles`, which a read-only DB must never do. Secondary DBs never take this path, because `ReactiveVersionSet` does not record an OPTIONS file number, so `options_number` is 0 there.

Cost: no extra I/O or scan. Checkpoint already disables deletions before calling, so for it this only bumps the counter. For a caller that has not, such as db_stress, obsolete files that become deletable during the short window are not purged at the decrement. They stay queued and are removed by the next `FindObsoleteFiles` (end of the next flush or compaction, or the periodic full scan). Reading the cached `VersionSet::options_file_size_` under the mutex, as before D123104845, would avoid the stat entirely. I kept the stat because D123104845 chose it deliberately. The cached size is the better option if it is always in sync with `options_file_number_`.

Reviewed By: xingbowang

Differential Revision: D126001916

fbshipit-source-id: 60f4f31c73e48b374f4015ed2847d45e706b85de
2026-10-19 18:44:50 -07:00
generatedunixname3846135475516776 61831d47be Rocksdb Crash Test failed: stderr: Failure: latest seqno changed from <N> to <N> with W (#15433)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15433

Reviewed By: jaykorean, xingbowang

Differential Revision: D125887282

fbshipit-source-id: 611491c1ece2c6e3bc74f22545bd5d93467cf9b9
2026-10-19 12:56:16 -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
Anand Ananthabhotla a3b0f187ff Add FileSystem::IsPreallocationSupported() to skip WAL truncation on open (#15395)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15395

**Why the change is required:**
During `DB::Open`, RocksDB reopens the last WAL file and truncates it to its logical size. This is done even when every column family's log number has already advanced past that WAL, so the file is obsolete and will be deleted later in the same open. The only purpose of the truncation is to release space preallocated beyond the logical end of file (e.g. by `fallocate` with `FALLOC_FL_KEEP_SIZE`); `GetFileSize()` reports the apparent size, so truncating to it can only free preallocated blocks. On a FileSystem that never preallocates, this work is pure overhead: a `GetFileSize`, a `ReopenWritableFile`, a no-op `Truncate` (the default `FSWritableFile::Truncate` returns OK without doing anything), and a `Close` on every open. On remote or append-oriented FileSystems the reopen can be expensive.

**Previous behavior:**
- If recovery flushed all data, or the WALs contained no data (including when all WALs are older than the min log number to keep), `MaybeFlushFinalMemtableOrRestoreActiveLogFiles()` always called `GetLogSizeAndMaybeTruncate()` on the last WAL. In read-only mode it still issued a `GetFileSize()` whose result was discarded.
- With `avoid_flush_during_recovery`, `RestoreAliveLogFiles()` always reopened and truncated the last live WAL.

**New behavior:**
A FileSystem can report that it never preallocates space by returning false from the new `FileSystem::IsPreallocationSupported()`. When it does, DB open skips reopening and truncating the WAL. In the flushed / no-data path it also skips the unused `GetFileSize()`, so no I/O is issued against the old WAL. The read-only no-data path no longer issues the discarded `GetFileSize()` either.

**Implementation:**
- `include/rocksdb/file_system.h`: new `virtual bool IsPreallocationSupported()`, defaulting to `true`. `FileSystemWrapper` forwards it to the target.
- The policy lives in the two callers in `db_impl_open.cc`; `GetLogSizeAndMaybeTruncate()` is unchanged. `RestoreAliveLogFiles()` still calls `GetFileSize()` because the size is needed for `alive_wal_files_` and `wals_total_size_`.

**Safety, compatibility, and limitations:**
- The default is `true`, so every existing FileSystem, including ones that override `SupportedOps()` without calling the base class, keeps the current behavior. This is why it is a separate virtual function instead of an opt-in `FSSupportedOps` bit, which would silently disable truncation for those FileSystems.
- No built-in FileSystem overrides the default: Posix, Windows, `MockFileSystem`, and `LegacyFileSystemWrapper` all report `true`, so their WAL truncation behavior is unchanged. Posix deliberately reports `true` even in builds without `ROCKSDB_FALLOCATE_PRESENT`: WALs may have been preallocated by a different build running on the same machine, and a build-dependent answer would leave that space unreclaimed.
- The function describes what the FileSystem can do, not per-file options such as `allow_fallocate` or build configuration. A WAL may have been preallocated by an earlier process with different options, so gating on the current options could leak space.
- A FileSystem that wrongly returns false can only leak preallocated space, in archived WALs (`WAL_ttl_seconds` / `WAL_size_limit_MB`) or across a crash loop before obsolete WALs are deleted. Data is unaffected.

Reviewed By: joshkang97

Differential Revision: D125049741

fbshipit-source-id: ac9795f2045c46911cd86b8faacef5aa49043496
2026-10-16 12:15:57 -07:00
Ranjan Banerjee a215c38a8e Fix libaio download after Folly update (#15404)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15404

The RocksDB Folly checkout rewrites a dead libaio source URL to Debian's mirror. The pinned Folly revision now uses releases.pagure.org, so the rewrite still matched the older pagure.io archive URL and became a no-op. Public Folly builds then downloaded an empty file and failed SHA-256 verification.

Update the rewrite to match Folly's current libaio URL. The Debian archive has the same libaio 0.3.113 contents and SHA-256 already recorded in the Folly manifest, so the checksum and extraction directory remain unchanged. This only affects the public Folly checkout path; RocksDB runtime behavior and system-package users are unaffected.

___

landed-with-radar-review

Differential Revision: D125347462

fbshipit-source-id: 4203b6a0b6037efb23aa1f44306b0ca0cd73a43c
2026-10-14 18:25:57 -07:00
Anand Ananthabhotla df90749560 Wire compaction to retry TableBuilder file cuts (#15344)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15344

Add the internal TableBuilder::TryAdd() contract so RocksDB-owned table builders can distinguish accepted rows, real builder errors, and clean requests to cut the current output table and retry the same row. Add diagnostic reason codes for retryable table boundaries; existing internal table builders inherit a default TryAdd() implementation that forwards to Add().

Translate the public ExternalTableBuilderBase::TryAdd() result in ExternalTableBuilderAdapter so external table implementations can request a file cut while RocksDB compaction uses the internal TableBuilder contract.

Teach compaction output writing to honor kRequiresNewTable by closing the current output file, opening a new one through the existing callbacks, and retrying the same key without advancing the input iterator. Split OutputValidator validation into CanAdd() and AddValidated() so validator state is not updated until the key is actually accepted by a table builder.

Add a DB compaction test with a custom TableFactory that wraps block-based tables and requests one mid-compaction file cut. The test verifies the cut produces two readable output files and preserves all key/value results.

Reviewed By: jaykorean, xingbowang

Differential Revision: D123932406

fbshipit-source-id: 9ed82c3782787de192bca15e40bfbf014ef28c15
2026-10-14 15:59:16 -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
Jay Huh 956c7042da Check async I/O support on the MultiGet thread and fall back to sync (#15337)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15337

## Why

RocksDB crash tests repeatedly fail with `MultiGetEntity error: Not implemented: ReadAsync: failed to init io_uring` when synchronous `MultiGet` requests async I/O on a thread that cannot create an io_uring. The same `NotSupported` status can reach users if capability detection succeeds but an individual asynchronous submission later fails.

## Previous behavior

`Version` cached `FSSupportedOps::kAsyncIO` when it was constructed. Posix io_uring instances are thread-local, so that result described the construction thread rather than the thread later executing MultiGet. A cached positive result could route a different thread into `ReadAsync`, where io_uring creation failed and the error reached the caller. A cached negative result could also unnecessarily disable async I/O on a capable read thread.

## New behavior and implementation

Synchronous `Version::MultiGet` now checks async-I/O support on the thread executing the read. Both the cross-level `optimize_multiget_for_io` path and the within-level overlap path enter their coroutine-based I/O only when that thread supports it. Posix `SupportedOps` eagerly creates and retains that thread-local io_uring, so subsequent submissions reuse the verified ring.

The capability remains advisory: if `ReadAsync` nevertheless returns `NotSupported`, `AsyncFileReader` completes the `FSReadRequest` synchronously, including requests that use filesystem-owned buffers. Poll errors are applied only to requests that were actually submitted asynchronously, so synchronous fallbacks in a mixed batch retain their successful status.

Regression coverage forces io_uring capability failure only on a dedicated read thread and exercises both synchronous MultiGet branches. Separate tests cover all-request fallback and a batch mixing submitted and synchronously fallen-back requests under an injected Poll error. A one-line bug-fix release note records the user-visible correction.

## Safety and compatibility

This changes no API, ABI, or persisted format. The support check is only performed when synchronous `MultiGet` receives `ReadOptions::async_io`; the Posix steady-state cost is a virtual call and thread-local ring lookup per such MultiGet. Direct I/O continues to use the existing synchronous `MultiRead` route before `AsyncFileReader`, and native coroutine APIs are unchanged.

Reviewed By: pdillinger

Differential Revision: D117802488

fbshipit-source-id: 0f2991a523607d894436cd53123e7a1b2fe93167
2026-10-12 19:22:21 -07:00
Anand Ananthabhotla 9198b11b46 Make InjectedErrorLog::Entry 256 bytes on 32-bit targets (#15388)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15388

`InjectedErrorLog::Entry` in `utilities/fault_injection_fs.h` is written to the injected error log in its native layout, and a `static_assert` requires `sizeof(Entry) == 256` so the record size stays stable. That assert fails on i386 builds.

Root cause: the fields add up to only 252 bytes. On targets where `alignof(uint64_t)` is 8 (x86_64, aarch64, armv7), the compiler adds 4 bytes of implicit tail padding, so the size is 256. On i386, `uint64_t` members are 4-byte aligned inside a struct, so there is no tail padding and the size is 252.

Fix: add an explicit trailing `uint8_t padding[4]` field so the size no longer depends on `uint64_t` alignment.

Compatibility: on 64-bit targets the new field takes the place of the old implicit tail padding, so every field offset and the 256-byte record layout are unchanged. `Record()` value-initializes the entry (`Entry entry{}`), so the padding bytes are always written as zero. No decoder in the repo depends on the padding bytes.

___

landed-with-radar-review

Differential Revision: D124873369

fbshipit-source-id: f5c6f43ca85013b0e769853ad13b8d4f3f89b958
2026-10-12 16:50:46 -07:00
Yoshinori Matsunobu 77eef9fa2e Make RocksDB reverse iteration honor query aborts (#15376)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15376

RocksDB `DBIter` polled `ROCKSDB_THREAD_YIELD_CHECK_ABORT()` while scanning ordinary forward entries, but reverse `Prev()` / `SeekForPrev()` traversal and `MergeValuesNewToOld()` could scan many internal keys without polling. Long tombstone, version, or merge-operand runs could therefore keep a MyRocks query running after `KILL QUERY` until the scan finished.

Poll the existing hook before each potentially expensive internal iterator movement in the forward scan, forward merge-operand scan, and four reverse traversal loops. Mark the abort branch `UNLIKELY`; on abort, invalidate the iterator and return `Status::Aborted("Query abort.")`.

MyRocks already defines the hook and maps `Status::Aborted` for a killed THD to `HA_ERR_QUERY_INTERRUPTED`, so no MyRocks change is required. There are no API, ABI, or persisted-format changes. The hook remains Linux-only, and cancellation remains cooperative: this cannot preempt an individual underlying iterator operation that never returns.

The change is rebased onto current master, including D124377137's fix for the unrelated `WriteBufferManagerTest.FlushPolicyRetiresAndRestartsHandoffWorkers` failures seen on V1.

Reviewed By: pdillinger

Differential Revision: D124441004

fbshipit-source-id: 53bf83a00121b42566dbd93de232e973cbe78688
2026-10-12 16:36:04 -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
Josh Kang ddac37a780 Serialize subset-checkpoint OPTIONS persistence with SetOptions (#15386)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15386

Fixes crash-test failure T292562874:
`Checkpoint failed: Corruption: [RocksDBOptionsParser]:failed the verification on BlockBasedTable::: filter_policy.id`

Root cause:
- D123104845 ("Commit authoritative OPTIONS file numbers in MANIFEST", 77d94c989243) made subset checkpoints (`CreateCheckpoint` with `include_cf_ids` that exclude some CFs) write a new OPTIONS file. `DBImpl::GetLiveFilesStorageInfoImpl` captures the snapshot under the DB mutex with `cfd->GetLatestCFOptions()`. That is a shallow copy: `table_factory`, and through it `filter_policy`, are `shared_ptr`s to the live objects. `DBImpl::CreateOptionsFileForSubsetCheckpoint` then calls `PersistRocksDBOptions` without holding `options_mutex_`. `PersistRocksDBOptions` serializes the file, then re-parses it and checks it against the same in-memory options.
- `RibbonFilterPolicy::bloom_before_level_` is a `kMutable` atomic that `SetOptions(table_factory.filter_policy.bloom_before_level=...)` changes in place, and `RibbonFilterPolicy::GetId()` includes it (`ribbonfilter:<bits>:<bloom_before_level>`). A concurrent `SetOptions` between the write and the verify makes the in-memory `filter_policy.id` differ from the file, so verification returns Corruption and the checkpoint fails.
- Every other OPTIONS writer (`WriteOptionsFile`, CF create/drop) holds `options_mutex_`. `SetOptions` holds it for its whole mutate-and-persist sequence. The new subset-checkpoint writer was the only one that skipped it.

Trigger in the crash test: db_stress randomly sets `table_factory.filter_policy.bloom_before_level` through `SetOptions` (when `keepRibbonFilterPolicyOnly`, i.e. `bloom_before_level` != INT_MAX with ribbon filters; see the OPTIONS dump with `ribbonfilter:18:1`, `ribbonfilter:8.9:2147483647`, `ribbonfilter:5.67:-1` across CFs). It also takes subset checkpoints. The two run concurrently on different threads.

Fix: hold `options_mutex_` in `CreateOptionsFileForSubsetCheckpoint` while persisting and verifying, the same as `WriteOptionsFile`. The only caller is `CheckpointImpl::CreateCheckpoint`, which holds no DB locks at that point, so lock ordering (options_mutex_ before mutex_) is kept.

Note: the snapshot's scalar fields are still captured at live-file time. Live pointer sub-options such as the filter policy reflect their state at persist time. This was already true of the shallow snapshot. The change only makes the persisted file self-consistent with what is verified.

Reviewed By: xingbowang

Differential Revision: D124728996

fbshipit-source-id: adb5511c2ddd73ab138fd7fd21109199ff4f5b1e
2026-10-12 12:01:42 -07:00
Peter Dillinger 76d7c29389 Update folly hash (#15375)
Summary:
has been neglected for a few months, finally taking care of it.

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

Test Plan: local folly build (from internal wiki instructions) to verify the fixes from claude, CI

Reviewed By: archang19

Differential Revision: D124436669

Pulled By: pdillinger

fbshipit-source-id: 0a0e3f6857cc8ba20941281b9dfd618a6997b86f
2026-10-12 08:49:01 -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
Xingbo Wang 9fb698e218 Synchronize IteratorCloseWALFile1 flushes (#15382)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15382

Why the change is required:
IteratorCloseWALFile1 is flaky under suite load. Its WAL-count assertions assume the iterator is the final owner of an obsolete SuperVersion, but the test did not prove that the preceding background flush had released its temporary references or that the next flush was unable to start.

Previous behavior:
The test occupied one HIGH-priority Env worker with a sleeping task. The shared Env can have multiple HIGH-priority workers, so another worker could run the flush concurrently. Also, Flush() can return before BackgroundCallFlush() has fully exited. Either race can leave two WAL writers open after iterator deletion. A fatal assertion could then destroy the stack-local SpecialEnv before fixture teardown closed the DB, causing a secondary use-after-return or segmentation fault.

New behavior:
The test waits for DBImpl::BGWorkFlush:done after the iterator-establishing flush, then replaces that dependency with a barrier at DBImpl::BackgroundCallFlush:start for the flush triggered by the final write. The iterator is therefore the final old-SuperVersion owner when it is destroyed, and the test releases the blocked flush only after checking synchronous WAL closure.

Implementation:
Replace the sleeping background task with existing SyncPoint dependencies. Own the iterator with unique_ptr, disable SyncPoint processing on every exit, and close the DB with a scope guard before the local SpecialEnv is destroyed.

Safety, compatibility, and limitations:
This changes only test synchronization and failure cleanup. It does not change production code, RocksDB APIs, persisted formats, locking, or runtime behavior. The test continues to cover foreground iterator cleanup with background_close_inactive_wals enabled; background purge behavior remains covered separately by IteratorCloseWALFile2.

___

landed-with-radar-review

Differential Revision: D124653963

fbshipit-source-id: 65d381128b5013b4d62ad60e42f0e4566928f000
2026-10-11 12:02:10 -07:00
Xingbo Wang baa4d373a6 Commit authoritative OPTIONS file numbers in MANIFEST (#15305)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15305

Why the change is required:

Column-family topology is durable in MANIFEST, while its operational
configuration snapshot is stored in a separate OPTIONS file.
CreateColumnFamily and DropColumnFamily previously committed MANIFEST first
and wrote OPTIONS afterward. An OPTIONS I/O failure could therefore make the
API report failure after the topology change was already durable.
Persisted-options readers also selected the highest file number and could
consume an orphan left by a failed publication.

Previous behavior:

RocksDB had no durable correspondence between a MANIFEST state and the
OPTIONS snapshot describing it. LoadLatestOptions and
GetLatestOptionsFileName treated the highest-numbered OPTIONS file as
authoritative. Column-family creation and deletion returned the later OPTIONS
persistence error even when their MANIFEST edit had already succeeded.
Multi-column-family operations wrote a complete OPTIONS snapshot after every
successful member, producing quadratic OPTIONS bytes.

Ordinary DB::Open consumes caller-supplied DB and column-family options rather
than loading OPTIONS. This change preserves that contract.

New behavior:

Add the default-disabled experimental DB option
track_options_file_number_in_manifest. Its value is serialized into each
OPTIONS file so the selection rule is self-describing and participates in the
normal DB-options compatibility checks.

When enabled, RocksDB first durably creates and renames OPTIONS-N and then
commits N with one safely ignorable MANIFEST field. A normal OPTIONS rotation
uses one MANIFEST commit. Column-family creation and deletion prewrite the
proposed snapshot and commit its number together with a topology edit in the
same VersionEdit. A failure before the OPTIONS file is durable changes no
topology. A failure after the file is durable but before MANIFEST commit leaves
an orphan that binding-aware readers ignore, while the previous committed
snapshot remains authoritative.

Each multi-CF create or drop writes one proposed snapshot, independent of the
batch size. Create commits the new number with the first successful add; drop
commits it with the last successful drop. Existing partial-success semantics
can therefore leave a committed snapshot with extra CFs, but never one missing
a live MANIFEST CF. Persisted-options APIs filter this safe superset against
recovered MANIFEST membership. A snapshot missing a live CF is corruption.
DB::Open with create_missing_column_families uses the same batched path.

If the newest OPTIONS file has tracking disabled, it retains the legacy
highest-numbered-file behavior, including after an old or disabled writer has
run. If it has tracking enabled, readers use the recovered MANIFEST commit and
reject newer tracked orphans. A missing usable commit falls back to the highest
tracked snapshot for RepairDB and legacy-rewrite recovery. DB open logs that
fallback through its configured logger; logger-less utility APIs remain
silent. Writable open removes rejected tracked orphans when local obsolete-file
deletion is enabled. Read-only open never mutates the DB directory.

A DB with no recovered commit and tracking disabled takes the legacy fast path
without parsing the newest OPTIONS file. When inspection is required, RocksDB
reads only the tracking marker. Marker-read failures used for selection do not
fail DB open; a recovered commit remains authoritative and ambiguous newer
candidates are conservatively retained.

The first topology change after enabling tracking first binds the current
topology, so failure of the proposed change cannot make its orphan
authoritative. MANIFEST failure during an ordinary tracked OPTIONS rotation
also enters the existing manifest-write background-error path.

Implementation:

- Add the safely ignorable CommittedOptionsFileNumber VersionEdit field and
  carry it through recovery, MANIFEST snapshots, rollover, and subset
  checkpoints.
- Persist the tracking marker through C++, C, and Java options, generated
  round-trip coverage, compatibility checking, and db_stress configuration.
- Centralize binding-aware selection for DB open and public persisted-options
  APIs. Reuse RecoveryContext's directory listing, use a narrow marker scan,
  and recover live CF names while replaying MANIFEST.
- Filter manifest-selected safe-superset snapshots to live MANIFEST CFs in
  LoadLatestOptions and compatibility verification without discarding their
  option maps.
- Prevalidate and sanitize CF batches outside the DB mutex, write one complete
  snapshot while options_mutex_ serializes topology changes, and hold the DB
  mutex only for the existing individual MANIFEST commits. Batch missing-CF
  creation during DB open follows the same locking boundary.
- Keep the committed snapshot live in cleanup and live-file APIs, and make
  subset checkpoints write an OPTIONS snapshot and filtered MANIFEST with
  identical membership in tracked and legacy modes.
- Preserve the first partial multi-CF drop error while consuming legacy
  OPTIONS persistence Status, and sync the checkpoint MANIFEST directory after
  replacement.
- Pass checkpoint helper callbacks by reference and group VersionEdit storage
  by alignment so the new paths add neither callback copies nor avoidable
  object padding.

Safety boundaries and unsupported cases:

- Tracking defaults to false. Disabled writers and never-tracked databases
  retain legacy write ordering and highest-numbered selection.
- Older binaries safely ignore the new MANIFEST tag and continue choosing the
  highest OPTIONS number. Downgrade is readable but not crash-atomic: an old
  binary can consume a tracked orphan. That is the same class of
  OPTIONS/MANIFEST inconsistency as the previous behavior.
- A rollback binary must know the serialized option name before the feature is
  enabled. Exact compatibility checks intentionally report a mismatch when the
  caller's tracking setting differs from the persisted snapshot.
- A newer untracked OPTIONS file deliberately supersedes an older committed
  number, allowing an old or disabled writer to become authoritative without
  rewriting MANIFEST.
- Reactive secondary and follower instances do not enforce or repair the
  binding. Ordinary DB::Open still uses caller-supplied options.
- Writable open does not delete rejected tracked or ambiguous files when a
  compaction service owns OPTIONS-file retention. Read-only open performs no
  repair writes.
- Direct consumers that scan OPTIONS filenames themselves are unchanged; the
  binding is enforced by RocksDB's persisted-options APIs.
- If the committed snapshot is missing and only tracked candidates remain,
  selection intentionally falls back to the highest candidate so RepairDB
  output remains usable.

Validation coverage and known limitations:

Focused coverage exercises encoding compatibility, tracked and legacy
selection, the never-tracked open fast path, marker-read failures, exact
tracking-marker compatibility, activation and disable/re-enable transitions,
batched create/drop and create-missing behavior, partial-batch safe supersets,
OPTIONS/MANIFEST failure injection, background-error propagation, orphan
cleanup and retention, read-only non-mutation, subset-checkpoint behavior,
stale OPTIONS recovery, RepairDB option sanitization, and transient
read-corruption reconstruction. The assertion-enabled full executable suite
passes. The repository check-format and check-sources wrappers cannot run
because they invoke Git inside this Sapling checkout; fbsource lint is used
instead. Cross-platform CI is not part of local validation.

Reviewed By: pdillinger

Differential Revision: D123104845

fbshipit-source-id: 77d94c989243c3f3f9fedfa81fa364c166250303
2026-10-11 10:39:30 -07:00
Ranjan Banerjee 4b44c2b4ba Fix WBM worker retirement and flush test accounting (#15373)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15373

Fix worker retirement when changing `WriteBufferManager` from `kFlushLargestAcrossDBs` to `kFlushOldest`. The coordinator could pass its active-policy check, consume the policy-change notification during refresh, and enter a one-second idle wait. This delayed retirement beyond the 300 ms deadline in `WriteBufferManagerTest.FlushPolicyRetiresAndRestartsHandoffWorkers`. Recheck the policy under `sorter_wait_mu` before waiting so the coordinator promptly enters the retirement path. The setter remains non-blocking, and retirement still waits for executor callbacks to finish.

Also fix startup pressure-wait accounting in `BackgroundFlushCycleTest.FastRejectedCandidateDoesNotAddPressureDelay`. Registering the healthy candidate can invalidate the coordinator's initial ranking snapshot and cause a legitimate pressure wait before either handoff runs. The old assertion counted waits from startup and incorrectly attributed that wait to the rejected candidate. Record the counts at rejected and healthy dispatch and require them to match, preserving the check that rejection adds no pressure interval.

Include both fixes in this diff so its `write_buffer_manager_test` target covers both corrected behaviors. Replace worker retirement and restart polling with sync-point synchronization. The rejection test deterministically invalidates the first snapshot during healthy-candidate registration, verifies that a startup wait occurred, and checks that advancing to the healthy candidate adds no further pressure wait. Retain the clang-format 21 line wrapping required by CI.

Reviewed By: pdillinger

Differential Revision: D124377137

fbshipit-source-id: 1420196e47ccba0268d0427c4ed623925aa3ad5b
2026-10-09 16:50:24 -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
Andrew Chang 9b4b980c6a Check background error Status in DirSyncFailureAfterTrashFileDeleted (#15371)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15371

`DeleteSchedulerTest.DirSyncFailureAfterTrashFileDeleted` aborts with "Failed to check Status" in builds with `ASSERT_STATUS_CHECKED=1`.

**Why it matters:** this breaks the `ASSERT_STATUS_CHECKED=1` `make check` CI job. Only the test is wrong; `DeleteScheduler` is unchanged.

- **Problem:** the test calls `GetBackgroundErrors().size()`. The returned `std::map<std::string, Status>` holds unchecked `Status` copies, so destroying it aborts.
- **Fix:** keep the map, assert its size, and check each error with `ASSERT_NOK`, as the other tests in this file do.

___

landed-with-radar-review

Differential Revision: D124369694

fbshipit-source-id: 85429b0bc886f6c87af3836bfe34b5da6eb47459
2026-10-09 09:31:32 -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
Andrew Chang f9b62a0567 Release trash accounting on immediate retry and after dir sync failure (#15352)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15352

DeleteScheduler now releases a trash file's accounting in two more cases where the file is gone but its size stayed counted until restart.

**Why it matters:** Leaked bytes stay in the total trash size. Once they pass `max_trash_db_ratio` of the tracked DB size, every later deletion skips rate limiting and is deleted immediately, which can cause I/O stalls.

- **Immediate retry:** A failed accounted trash deletion is retried through `DeleteExistingTrashFileImpl`. When the retry deletes immediately, for example with rate limiting disabled, and the file is already gone, `DeleteFileImmediately` skipped `OnDeleteFile` and the retry entry was put back. It now untracks the file and subtracts the entry's size. The NotFound status is still returned.
- **Dir sync failure:** If the unlink worked but `NewDirectory` or the dir fsync then failed, `DeleteTrashFile` reported 0 deleted bytes and skipped `OnDeleteFile`, so the accounting stayed. It now records the deleted bytes and untracks the file right after the unlink, and reports `file_gone` so `BackgroundEmptyTrash` releases the rest without parking a retry entry. The sync error is still reported as a background error.
- **Details:** Adds an `IsFileGone` helper for NotFound/PathNotFound and a `DeleteScheduler::DeleteTrashFile:BeforeSyncDir` sync point for tests. Both new tests keep one more tracked file, so they check that only the gone file's bytes are released.

Reviewed By: anand1976

Differential Revision: D124000703

fbshipit-source-id: 54ed8be66d5670709054a46a258ceaf72b10d899
2026-10-08 15:52:29 -07:00
Peter Dillinger 2e689e3c00 Fix hang in FlushLargestAcrossDBsPolicy test (#15365)
Summary:
DBWriteBufferManagerTest.FlushLargestAcrossDBsPolicy (added in https://github.com/facebook/rocksdb/issues/15047) can hang forever under load, as seen in nightly
build-linux-arm-test-full (db_write_buffer_manager_test shard 4).

The test's first phase waits for other_db to flush via the OnFlushCompleted listener, then immediately starts a second phase that writes 500KB to other_db and calls TEST_ScheduleFlushOnLargestDB(), expecting other_db to be picked. But OnFlushCompleted fires before the flush thread finishes its post-flush work, including refreshing other_db's WriteBufferManager flushable-memory accounting. If the second phase runs in that window, other_db's accounting is stale and the refresh (which only TryLocks the DB mutex) can leave other_db out of the ranking, so db_ is picked and flushed instead. The test then waits in the unbounded FlushedDBRecorder::WaitForFlush(other_db) forever. The same race occasionally fails ASSERT_TRUE(TEST_ScheduleFlushOnLargestDB()) instead, when other_db still has WBM flush work outstanding.

Fix the test by waiting for other_db's background work to finish (including any WBM flush job) after the first-phase flush, before starting the second phase. No production code changes.

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

Test Plan: Reproduced by running db_write_buffer_manager_test shard 4 of 6 (as generated for a 4-core CI machine) many times concurrently on an oversubscribed machine. In an interleaved A/B of 1500 runs each, the unmodified test hung 71 times (all at the second-phase WaitForFlush, confirmed via stack dumps) and failed the line-995 assertion 16 times; with this change there were no hangs or failures of this test. DB LOG thread IDs from hung runs confirmed the test thread had flushed db_ in the second phase. The hang rate was similar before https://github.com/facebook/rocksdb/issues/15331, so the race dates to https://github.com/facebook/rocksdb/issues/15047.

Reviewed By: rban1

Differential Revision: D124196572

Pulled By: pdillinger

fbshipit-source-id: d621c5a0b441634fc339283caf9ad7a0d6eea401
2026-10-08 15:26:53 -07:00
Andrew Chang 78a9d3c98c Release trash accounting when a queued trash file vanishes (#15349)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15349

DeleteScheduler now releases the accounting for a queued trash file that disappears before it is deleted. Before this change, that size leaked until the process restarted.

**Why it matters:** The leaked bytes stay in the total trash size. Once they exceed `max_trash_db_ratio` of the tracked DB size, every later deletion bypasses rate limiting and is deleted immediately, which can cause I/O stalls.

- **Problem:** If a trash file's directory is renamed or removed while the file waits in the delete queue, `DeleteTrashFile` fails with PathNotFound. `BackgroundEmptyTrash` then keeps the file's remaining accounted bytes in `total_trash_size_` and records it in `failed_accounted_trash_deletions_` for a retry. The retry never happens, because nothing schedules that path again. The file also stays tracked in `SstFileManager`, so the total size is inflated too.
- **Fix:** On NotFound/PathNotFound, `DeleteTrashFile` untracks the file via `OnDeleteFile`, and `BackgroundEmptyTrash` subtracts the file's full remaining accounted size and skips the retry map. The error is still reported through `GetBackgroundErrors()`, so that behavior is unchanged.

Reviewed By: anand1976

Differential Revision: D123986893

fbshipit-source-id: 4c084241a8100ae9d28fb92edf02a4362d6f60cb
2026-10-08 15:06:39 -07:00
Anand Ananthabhotla 39d56d5ad7 add block read accounting to IODispatcher (#15363)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15363

as title. we were not accurately reporting block read sizes and counts to the thread local perf context

given that IODispatcher can coalesce requests, we also count any sub coalesce threshold added bytes to these totals

Reviewed By: chaitanyadatye, xingbowang

Differential Revision: D123941253

fbshipit-source-id: eddc33d144afa49a8137fb174008030d22f3bddd
2026-10-08 14:28:01 -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 9b4c5422f0 Avoid WBM pressure delay after fast rejection (#15343)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15343

Problem

The cross-DB WriteBufferManager coordinator dispatches ranked flush candidates asynchronously. A candidate can reject its handoff immediately—for example because its DB mutex is busy, writes are stopped, or equivalent WBM work is already scheduled—and finish before the sorter reaches its pressure wait.

Before this change, completion called RequestRefresh(), but the sorter’s active-pressure branch did not consume that already-recorded notification. Condition-variable notifications are not retained, so if completion won the race, the sorter still slept for the full 10 ms pressure interval before trying the next ranked DB. With many quickly rejected candidates, that delay accumulated once per candidate; 10,000 rejected DBs ahead of a healthy DB could theoretically delay useful relief by roughly 100 seconds while shared memory remained above its limit.

Solution

Snapshot the existing refresh-request generation immediately before ProcessFlushHandoffRequest(). After processing, if the handoff is already idle, the current ranked candidate cycle remains active, and the generation advanced, the sorter knows that asynchronous work completed during the dispatch window. It consumes that persistent notification and immediately continues to the next candidate instead of entering the pressure backoff.

The generation check is deliberately narrower than checking the refresh Boolean alone: it reacts only to notifications that occurred during this handoff attempt and avoids treating an older unrelated refresh as completion of the current candidate. The existing 10 ms backoff remains after a complete no-progress candidate cycle, and queued or executing handoffs retain their existing lease/wait behavior.

Testing

A deterministic SyncPoint test pauses the sorter only after a handoff has actually been submitted, lets a rejecting executor callback finish first, and verifies that the healthy next candidate is reached without entering the pressure-wait point. The synchronization wait is bounded so a broken ordering fails the test rather than hanging CI.

Reviewed By: xingbowang

Differential Revision: D123926506

fbshipit-source-id: b057e5b9a41ee0abd415f72ef2067b0d3f718384
2026-10-07 16:03:20 -07:00
Davis Rollman 7292adcc9a AutoHeaders: migrate to explicit headers (batch 116)
Summary:
Finish OpenZL, the internal RocksDB source tree, and the released RocksDB copy, removing all three parent AutoHeaders opt-ins. Declare inferred headers while preserving aliases, globs, selectors, and visibility. RocksDB's generator lists library headers and passes test source headers through its template and shared wrapper. Header lists account for the source files compiled by each release, so the older released tree does not gain headers from newer internal test helpers.

Add `release_to_fbcode.py internal_repo_rocksdb --buck-only -y` to refresh the four Buck generator files and regenerate BUCK against the existing release's source lists. The command restores its five outputs if copying or generation fails, applies the existing generated-file warning, and rejects release-commit options. It does not copy runtime sources, change the release version, clone, commit, or deploy. Both RocksDB BUCK files are produced by the maintained generators.

Graft the OpenZL dev declarations into prod. Align the predictor's Folly include paths and dependencies with prod so promotion is reproducible. Add the IPNext controller transition header explicitly.

Authored with Codex.

Reviewed By: IanChilds

Differential Revision: D123482073

fbshipit-source-id: 05ab47e592d710834da56bbea57f5f93a0aea565
2026-10-07 15:43:34 -07:00
Peter Dillinger 6b15f1643e Give nightly-candidate workflow a distinct name (#15345)
Summary:
The workflow_dispatch-only nightly-candidate.yml shared the exact `name: facebook/rocksdb/nightly` with the scheduled nightly.yml. The name collision confuses GitHub's "list workflow runs by file name" REST query, so release tooling querying actions/workflows/nightly.yml/runs got a stale/partial set missing recent scheduled runs. Rename it to facebook/rocksdb/nightly-candidate, matching the pr-jobs-candidate convention, so every workflow name is unique.

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

Test Plan: rety the integration after fix

Reviewed By: xingbowang

Differential Revision: D123973936

Pulled By: pdillinger

fbshipit-source-id: cde1c238e6a91444f7112848081e161f54f5bf24
2026-10-07 15:35:08 -07:00
Ranjan Banerjee a38cd3a439 Bypass stalled cross-DB WBM flush candidates (#15331)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15331

## Motivation

The parent diff already implements cross-DB flushable-memory accounting and a background sorter that ranks DBs outside the write path. This change does not alter how the best candidate is selected.

The remaining liveness problem occurs after the sorter selects a DB and hands it off for execution. Previously, the coordinator tracked one remote handoff as pending and waited for that DB to report completion before advancing. A selected DB could therefore monopolize coordinator progress if its job waited in Env::LOW, blocked on the DB mutex, waited behind an existing write leader or write stall, or otherwise failed to complete promptly. Other ranked DBs could be ready to reclaim memory but would not be attempted while the first handoff remained pending.

## Solution

Make cross-DB flush handoff execution bounded and bypassable.

Cross-DB attempts now run on a small four-worker executor owned by the WriteBufferManager instead of consuming Env::LOW compaction capacity. The executor is created only for kFlushLargestAcrossDBs and its workers are retired when that policy is disabled.

The coordinator distinguishes queued and executing handoffs. A candidate lease begins when an executor worker starts the callback, rather than while the callback is merely waiting in the executor queue. If an executing handoff exceeds its lease, the coordinator releases ownership of that attempt and advances to the next ranked candidate. The original callback is not cancelled and may still complete, so the executor and outstanding-handoff count provide a strict bound on concurrent stuck work.

Each registration has a per-DB outstanding-job reservation, preventing the sorter from dispatching the same DB again while an earlier callback remains alive. The coordinator also records the identity of the active candidate, so late completion from an expired attempt cannot clear or corrupt a newer candidate lease.

DB mutex acquisition, write-thread joining, memtable switching, and flush dispatch continue to happen outside registry and coordinator locks. The executor invokes the existing BackgroundCallWBMFlush path directly; actual immutable-memtable flushing still uses RocksDB normal flush infrastructure.

## Compatibility

This behavior is limited to kFlushLargestAcrossDBs. Existing policies retain their previous local flush behavior, and no WBM executor threads are started for them.

## Validation

Unit coverage exercises lease expiry, advancement to the next candidate, late completion, duplicate suppression, bounded worker capacity, policy changes with blocked callbacks, shutdown, and reranking after progress.

The end-to-end stack was also compared against the original local policy in one matched memory-pressure run with 1,000 DBs and 16 writers. The complete largest-across-DBs policy delivered 61.0% higher throughput, 55.5% fewer WBM flushes, 37.9% lower mean latency, 47.9% lower P99 latency, 5.17x larger average flushes, and 62.8% lower peak RSS. These measurements validate the full policy stack rather than isolating the performance contribution of this liveness follow-up.

Reviewed By: xingbowang

Differential Revision: D123461329

fbshipit-source-id: 4ad86102c2a43beb469f7672d722674dfbda9871
2026-10-07 14:47:58 -07:00
Anand Ananthabhotla 45203a867f Add retryable ExternalTableBuilder contract (#15342)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15342

Add an ExternalTableBuilderBase::TryAdd() contract so external table builders can distinguish accepted rows, real builder errors, and clean requests to cut the current output table and retry the same row.

Add external-table-facing result, reason, and context types. Existing external table builders inherit a default TryAdd() implementation that forwards to Add(), so current implementations keep their historical behavior unless they explicitly opt into retryable file cuts.

Reviewed By: xingbowang

Differential Revision: D123930815

fbshipit-source-id: b5701c704938759db68b7c0bf0ebeba19d176945
2026-10-07 13:54:38 -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
Andrew Chang 3ea1ec56df Fix MultiGet reading from disk with kBlockCacheTier (#15321)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15321

`MultiGet` with `read_tier = kBlockCacheTier` now returns `Incomplete` for data blocks that are not in the block cache. Before this fix, it read them from disk.

**Why it matters:** Callers use `kBlockCacheTier` to prevent disk I/O. `Get` obeyed it, but `MultiGet` did not.

**Problem**
- Without mmap reads, `MultiGet` sends cache misses to `RetrieveMultipleBlocks`.
- That path called `MultiRead` with no `kBlockCacheTier` check.
- `RetrieveBlock` (used by `Get`) has the check.

**Fix**
- `RetrieveMultipleBlocks`: if `read_tier == kBlockCacheTier`, set `Incomplete("no blocking io")` for each block and do no I/O.
- `MultiGet`: when a block is `Incomplete`, return that status for the key, the same as `Get`. Before, the key status stayed `OK` with no value.
- Release note in `unreleased_history/bug_fixes`.

**Tests**
- New `DBTest2.MultiGetBlockCacheTierSkipsColdSstData`: `Get` and `MultiGet` both return `Incomplete` for a key whose data block is not in the cache.
- `DBBlobBasicTest.MultiGetBlobsFromCache` relied on the bug. Its inline key passed only because `MultiGet` read the data block from disk. The test now loads the block cache first, so it still checks the blob cache.

Reviewed By: joshkang97

Differential Revision: D122431823

fbshipit-source-id: 262913930cb4b570ea0a4d8f4f92364de08a27f5
2026-10-05 15:13:28 -07:00
Jay Huh f16157cfc5 Fix ClipColumnFamily dropping the file that ends at begin_key (#15320)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15320

Why the change is required:
`DB::ClipColumnFamily(cf, begin_key, end_key)` keeps `[begin_key, end_key)`. It first drops whole non-L0 files outside the range with `DeleteFilesInRanges` on `(-inf, begin_key)` and `[end_key, +inf)`, but called it with the default `include_end = true`. The first range was therefore treated as `(-inf, begin_key]`, so a non-L0 file whose largest key is `begin_key` was deleted even though that key is inside the kept range. Clipping `[b, d)` on L1 files `[a, b]` and `[c, d]` lost `b`.

This showed up while smoke-testing `SplitDB` (D120757697): single-key files trivially moved to the bottom level made the split destination miss its first key.

New behavior:
- Pass `include_end = false`, so `DeleteFilesInRanges` skips files whose largest key equals `begin_key`. The second range has no end, so it is unaffected. The remaining keys below `begin_key` are still removed by the existing `DeleteRange` and compaction steps.

Reviewed By: xingbowang

Differential Revision: D123306376

fbshipit-source-id: 65cb36ae730a55750ea558a6cdcd48d8fc17f0e9
2026-10-05 12:48:29 -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 5f1132b884 Require DB mutex checks in AI code reviews (#15324)
Summary:
CONTEXT: The AI review workflow had generic concurrency and performance checks, but it did not require reviewers to determine whether changed code or transitive callees execute while DBImpl::mutex_ is held. Reviews could therefore miss added contention, blocking work, lifetime errors, or snapshot and lock-ordering violations.

PREVIOUS: Review agents could examine DB-mutex changes through general correctness and performance lenses, but there was no explicit call-path audit, before/after mutex-held work comparison, or evidence requirement for regressions.

NEW: Every non-trivial review must state whether the DB mutex is affected. When it is affected, reviewers must inspect lock boundaries and correctness invariants, compare CPU and I/O before and after the change, keep unavoidable locked work highly optimized, and require measurements for material regressions.

IMPLEMENTATION: Add a DB Mutex Review Perspective covering locking contracts, object lifetime, snapshot boundaries, lock ordering, hidden callee costs, blocking work, validation, and performance evidence. Assign its checks to the correctness and performance reviewers and add required context and review checklist entries.

SAFETY: This changes only the AI code-review instructions. It does not change RocksDB runtime behavior, APIs, file formats, or compatibility. Changes that cannot execute under the DB mutex only need to state that the mutex is not affected.

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

Test Plan:
- `git show --check HEAD`
- `git diff --check upstream/main...2026_10_05_main`
- Documentation-only change; no runtime tests were run.

Reviewed By: joshkang97

Differential Revision: D123415306

Pulled By: xingbowang

fbshipit-source-id: 0f78e0b1d677edf003cd5dc6ed5ff8123aff4f98
2026-10-05 09:23:49 -07:00
Josh Kang c2b86f3ec0 Lazily allocate MultiGet coroutine scratch buffers (#15298)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15298

## Summary

BlockBasedTable::MultiGetCoroutine() embedded the synchronous 8 KiB scratch buffer in every coroutine frame, including cache-hit requests. The change uses zero coroutine stack capacity and allocates the required heap size only when I/O is needed, while preserving synchronous behavior.

## Benchmarks

| Variant | Coroutine frame allocation |
| Before | 21,048 bytes |
| After | 12,864 bytes |
| Change | -8,184 bytes (-38.9%) |

No API, format, or read-result changes.

Reviewed By: xingbowang

Differential Revision: D122810207

fbshipit-source-id: 003a8349b8da72de2eca2e62fb9f7b3d8802baed
2026-10-02 20:17:30 -07:00
Yoshinori Matsunobu 8585299552 Rate-limit DestroyDB cleanup of existing trash (#15285)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15285

Why the change is required:
MyRocks bulk-load v2 creates a temporary RocksDB and calls DestroyDB when the load is committed, rolled back, abandoned, or recovered. Successfully ingested SSTs are moved into the main DB, so leftover SST cleanup is primarily an error, abort, or recovery case; WALs, obsolete files, and compaction-created trash can still remain. All physical deletion performed by DestroyDB should honor the configured SstFileManager rate, including rocksdb_sst_mgr_rate_bytes_per_sec in MyRocks.

Previous behavior:
DestroyDB sent recognized table, blob, and WAL files through SstFileManager, but ignored filenames ending in .trash because ParseFileName rejects that suffix. The final best-effort DeleteDir then silently left the directory behind. DB::Open handled the same files through CleanupDirectory, forcing MyRocks cleanup code to discover and register trash itself. A global WaitForEmptyTrash workaround would also drain unrelated compaction, DROP TABLE, and DROP INDEX deletion work from every DB sharing the manager.

New behavior:
DestroyDB waits only for pending deletion work associated with each DB, data, WAL, and archive directory. It then schedules any remaining .trash file through the existing rate-limited scheduler and waits only for the private bucket created for this DestroyDB call. Directory keys are normalized for bookkeeping, so equivalent path spellings identify the same pending work while filesystem operations retain the original path.

Accounting and failure handling:
A failed accounted trash deletion retains its original file path and remaining trash-byte charge. If DB open or DestroyDB later discovers the file, the retry preserves the accounted classification, avoids charging the trash bytes twice, and clears SstFileManager tracked-size and trash-size accounting after successful deletion.

Scope and safety:
There is no new sysvar, public RocksDB API, or persisted-format change. Existing rate settings, hard-link handling, partial deletion, and separate-WAL foreground deletion remain unchanged. The bookkeeping is confined to file enqueue/completion and DestroyDB cleanup. A shared FIFO can still delay this DB behind unrelated work already ahead of its files, but unrelated work queued behind this DB no longer gates DestroyDB.

Reviewed By: xingbowang

Differential Revision: D122221503

fbshipit-source-id: ec106f57efa70e24827234b4173366e5dbcbda5c
2026-10-02 16:21:19 -07:00
Xingbo Wang 866e8e9f06 Make full error-recovery flush atomic (#15293)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15293

Why the change is required:
Full background-error recovery flushes all column families because WAL durability can be uncertain. A later recovery-flush failure or process crash must not persist different WAL replay boundaries for different column families. Otherwise reopen can recover only part of a write batch spanning those column families.

The risk includes non-file-scoped WAL write or sync errors. A failed write can have reached the WAL without reaching the memtables. An earlier unsynced write can instead remain in the memtables while its WAL bytes are not durable; retrying the fsync after resetting the writer error does not by itself prove those bytes survived. Sequential recovery can make either case visible in only some column families after reopen.

Previous behavior:
File-scoped WAL-error recovery already set FlushOptions::force_atomic_flush. Other full kErrorRecovery paths flushed column families sequentially when DBOptions::atomic_flush was false. Each successful per-column-family flush independently advanced its MANIFEST log number, so a later flush failure or process crash could leave mismatched replay boundaries.

New behavior:
Every full kErrorRecovery atomically commits the flush results and WAL replay boundaries for all participating column families. A successful recovery commits every column family; a failed recovery commits none. If an atomic recovery attempt fails, all of its flush output is discarded and recreated by a later attempt instead of retaining output from column families that finished first.

Implementation:
Set FlushOptions::force_atomic_flush for every full error-recovery FlushAllColumnFamilies call. Remove the now-unreachable recovery-only behavior from FlushMemTable and assert that recovery does not enter the non-atomic path. Keep the two-write-queue sequence fence at the atomic memtable-switch point.

Add an end-to-end fault-injection test for a retryable, non-file-scoped WAL sync error after a multi-column-family record is appended. It covers successful and failed recovery flushes, including a column family with no other memtable data, and verifies all-or-none behavior after reopen. Update the manual_wal_flush comment and add a release note.

Safety, compatibility, and limitations:
Only full kErrorRecovery changes. Normal, manual, shutdown, and kErrorRecoveryRetryFlush behavior is unchanged. File-scoped WAL-error recovery was already atomic. There are no API, ABI, option-default, or file-format changes.

The additional work is limited to the cold recovery path. A failed atomic recovery can redo more flush work than the previous sequential path, but this avoids partial multi-column-family recovery. Automatic recovery with manual_wal_flush remains disabled as fatal pending separate analysis and testing; this change does not remove that workaround. Validation covers local Linux builds and focused recovery tests; platform CI for this version is pending.

Reviewed By: pdillinger, rban1

Differential Revision: D122469648

fbshipit-source-id: f72845903d9b6bff70f5d881e02a4cb2e920ed72
2026-10-02 14:43:36 -07:00
Xingbo Wang 19b6f85266 Support async file open for SecondaryDB (#15294)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15294

Why the change is required:
`open_files_async` reduces startup latency by moving SST validation and table-reader opening to background work, but `DB::OpenAsSecondary()` still performed that work synchronously.

Previous behavior:
A secondary using `open_files_async=true` replayed its MANIFEST through `ReactiveVersionSet`, synchronously verified and opened every current SST, and only then returned. The option therefore provided no startup benefit for secondary instances.

New behavior:
`DB::OpenAsSecondary()` can now return after installing its initial read view and start initial SST validation and table-reader pinning through the existing asynchronous opener. With `max_open_files=-1`, the opener processes all current SSTs in the background; a finite table cache can stop the eager pass at capacity and validate remaining files on demand. Later `TryCatchUpWithPrimary()` rounds remain synchronous. Async-open failures notify listeners and are latched according to the existing severity and `paranoid_checks` policy. If an error is not latched, a later read retries the file access and reports a persistent failure. A latched error requires fixing the underlying problem and reopening the secondary. Read-only instances do not start write-oriented recovery.

Implementation:
Initial reactive MANIFEST replay can defer SST verification and handler loading. The deferral is immutable constructor state on `ReactiveVersionSet`, is cleared before catch-up mode, and is restricted to normal secondary recovery. `VersionBuilder::GetAndClearIntermediateFiles()` now moves out and clears its own historical add-then-delete bookkeeping so callers cannot accidentally retain stale state. Secondary open schedules the background opener after publishing the read view. Its context pins the captured Versions and column families, including retained secondary handles whose column family was subsequently dropped by the primary. Existing table-reader opening performs SST size and unique-ID validation, avoiding a separate serial metadata pass. Closing the DB or canceling background work safely cancels a queued opener or waits for a running opener. Read-only async-open errors use an ErrorHandler path that notifies listeners without automatic recovery. Secondary async open explicitly rejects FIFO compaction.

Safety, compatibility, and limitations:
The default `open_files_async=false` path and existing primary async-open behavior are unchanged. WAL and blob recovery, as well as later secondary catch-up, remain synchronous. Follower DB and `OpenAndCompact()` do not use this deferral. There are no file-format, ABI, or public API signature changes. Secondary async open requires `skip_stats_update_on_db_open=true` and does not support FIFO compaction. Applications must keep files accessible by coordinating deletion with the primary or by using an Env/FileSystem that delays physical deletion until every instance releases a file. With asynchronous opening, `max_open_files=-1` cannot guarantee every SST is pinned before `OpenAsSecondary()` returns; the table-files-only `max_open_files=-1` workaround applies only when asynchronous opening is disabled. No completion API is added.

Validation covers initial deferral and pinning, synchronous catch-up, table-reader detection of physical-size mismatch, FIFO rejection, listener-adjusted errors and synchronous read retry, retained handles after primary column-family deletion, close and explicit cancellation with no HIGH-priority worker, stopping new table opens during shutdown, primary async open, follower isolation, and remote compaction. Linux Buck builds and focused tests passed. Cross-platform CI is pending, and `make check-sources` cannot run successfully in this Sapling checkout because its script requires Git.

Reviewed By: pdillinger

Differential Revision: D122417547

fbshipit-source-id: 91d408469c35c4ea21165c5e7db75068e65c2389
2026-10-02 12:21:46 -07:00
xingbowang 4cd0450f31 Fix big-endian builds for the fault-injection filesystem (#15297)
Summary:
The persistent injected-error log writes `InjectedErrorLog::Entry` objects using their native in-memory layout, while the log format's consumer expects little-endian multi-byte fields. The existing `static_assert(port::kLittleEndian)` protected that format by rejecting every big-endian build that included the fault-injection filesystem, even though the log is an optional diagnostic.

This change keeps RocksDB and the fault-injection filesystem available on supported big-endian platforms while disabling only the incompatible persistent log. Log-file creation now returns an inactive descriptor when `port::kLittleEndian` is false, which makes `Record()` and `Flush()` remain safe no-ops. The fixed 256-byte entry-size assertion remains in place, and format-dependent tests are bypassed on big-endian targets.

The change also adds a release note and documents big-endian support plus the preferred capability-check pattern for optional architecture-specific features.

## Compatibility and limitations

- Little-endian behavior and the on-disk log layout are unchanged.
- On big-endian platforms, configuring an injected-error log path does not create or populate a log file; the rest of fault injection remains available.
- Windows already leaves this POSIX-backed diagnostic disabled.
- This does not introduce a portable log encoding. Supporting persistent injected-error logs on big-endian platforms would require a format change.

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

Test Plan:
- `build_tools/rockstest.sh fault_injection_fs_test '--gtest_filter=InjectedErrorLogTest.*'` (little-endian macOS): all 5 tests passed.
- `make check-sources`
- `git diff --check upstream/main...2026_10_01_endian_fix`

Native big-endian build and test execution were not available locally; that path relies on the compile-time `port::kLittleEndian` capability and should be covered by big-endian CI.

Reviewed By: jaykorean

Differential Revision: D122880760

Pulled By: xingbowang

fbshipit-source-id: 59dae9661a47608aa12f41078ad1b3ef0fa7231e
2026-10-01 15:15:13 -07:00
Xingbo Wang b58cbcf13c Apply transaction visibility to entity reads (#15269)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15269

Why the change is required:
Transaction entity reads must enforce the same commit visibility and read-your-own-write rules as value-only reads. Without the transaction-specific read callback, GetEntity-family APIs can return a value that is not visible to the caller.

Previous behavior:
TransactionBaseImpl handled GetEntity and MultiGetEntity through non-virtual helpers that called WriteBatchWithIndex without a ReadCallback. WritePrepared transactions could expose a prepared but uncommitted update. WriteUnprepared transactions could miss their own write after it had been flushed as an unprepared batch, although that write remained hidden from other transactions.

New behavior:
GetEntity, MultiGetEntity, and GetEntityForUpdate now follow the same snapshot visibility and read-your-own-write semantics as Get and MultiGet for WritePrepared and WriteUnprepared transactions.

Implementation:
Make the internal entity-read helper hooks virtual. Override them in WritePreparedTxn and WriteUnpreparedTxn to construct the existing policy-specific ReadCallback and pass it through WriteBatchWithIndex. The WritePrepared batched path validates the callback refreshed read sequence so reads without an explicit snapshot do not bypass eviction validation. Invalid callback or snapshot state returns TryAgain. Add shared transaction entity-read assertions and parameterized regression coverage for prepared visibility and unprepared read-your-own-write.

Safety boundaries and unsupported cases:
There is no public API or persisted-format change. Other transaction policies keep the existing base implementation. The change adds only the callback and validation already used by value-only reads. Validation covers synchronous snapshot and non-snapshot entity reads, prepared visibility, and unprepared read-your-own-write in normal and checked-Status builds. Coroutine-specific and named wide-column payload cases were not separately tested, and the full repository suite was not run.

Reviewed By: rban1

Differential Revision: D121793013

fbshipit-source-id: e300642ed360b640814056e4d95cced5f5c180d8
2026-10-01 15:04:26 -07:00
Josh Kang 4d180247ae Add PreparedFileInfo serialization APIs (#15274)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15274

Expose `SerializePreparedFileInfo()` and `DeserializePreparedFileInfo()` so opaque metadata produced by SST writers or DBs can be persisted and transferred across process boundaries. The representation uses RocksDB `OptionTypeInfo` adapters for `InternalKey` and `TableProperties` and includes a CRC32C integrity check.

Deserialization now rejects malformed nested string-map entries, including missing key/value separators and invalid hex, with `InvalidArgument` instead of asserting in debug builds or accepting malformed input in release builds. Valid serialized data and the wire format are unchanged. The `PreparedFileInfo` type metadata is also initialized on first use to avoid throwable namespace-scope initialization.

Reviewed By: xingbowang

Differential Revision: D121897852

fbshipit-source-id: 59a3d221bfd20e6c257bd60477823a12d05a35c5
2026-10-01 13:45:31 -07:00
Jay Huh ded3cb72a5 Fix trace replay on the default column family and error reporting in multi-threaded replay (#15296)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15296

**Why:** trace replay (`DB::NewDefaultReplayer()`, db_bench `replay`) cannot replay a trace of a DB with only the default column family, and multi-threaded replay reports success when records fail. Together these make db_bench replay look successful while replaying no reads.

**Previous behavior:**
- `TraceExecutionHandler` resolves a record's column family ID only against the `handles` passed to `NewDefaultReplayer()`. Records on a column family missing from `handles`, including the default one, fail with `Corruption: Invalid Column Family ID.`. An empty `handles` also trips `assert(!handles.empty())` in debug builds.
- db_bench fills its handle list (`db_.cfh`) only when `--num_column_families` > 1, so with the default of 1 it passes an empty list. Every Get, MultiGet and iterator record fails. Write records are not affected, because `DB::Write()` resolves the column family from the batch itself.
- In multi-threaded `Replay()` (`num_threads` > 1), `BackgroundWork()` sends only decode errors to the error callback. Execution errors are dropped, so `Replay()` returns OK. Single-threaded replay stops and returns the error, including a NotSupported from execution (for example `DB::Write()` of a DeleteRange with `row_cache` set). It skips only records whose trace type it cannot decode.
- Result in db_bench on a single column family DB: `--trace_replay_threads=1` prints `Replay failed. Error: Corruption: Invalid Column Family ID.`. `--trace_replay_threads=4` prints `Replay completed` with `rocksdb.number.keys.read` = 0.

**New behavior:**
- The replay handler always includes the DB's default column family, so `handles` may be empty or leave it out. A handle the caller passes for a column family is still the one used.
- Multi-threaded `Replay()` reports execution errors through the same callback as decode errors. It stops scheduling new records and returns the error of the failed record with the earliest trace timestamp, as the existing code already does for decode errors. Get records that find no key still count as success, since the handler treats NotFound as OK.
- As in single-threaded replay, NotSupported is skipped only when decoding returns it (a trace type the replayer does not support). NotSupported from executing a record stops the replay.
- The scheduling loop no longer reads the shared status `bg_s` while worker threads write it. It already did so for decode errors; with execution errors reported, TSAN flags it in the new test (see Test Plan).

**Implementation:**
- `TraceExecutionHandler` constructor: drop the non-empty assert; after the caller's handles, insert `db->DefaultColumnFamily()`. `unordered_map::insert` does not overwrite, so a caller's handle wins.
- `TraceExecutionHandler` constructor: skip the default column family if `DefaultColumnFamily()` returns nullptr (some test `DB` mocks do), so such DBs behave as before.
- `ReplayerImpl::BackgroundWork()`: call `error_cb` when `Accept()` returns an error. On a decode error, skip `error_cb` for NotSupported, matching single-threaded replay.
- `ReplayerImpl::Replay()`: `error_cb` no longer filters out NotSupported (the decode path now does that); it still keeps the error of the earliest record.
- `ReplayerImpl::Replay()`: `error_cb` also sets an `std::atomic<bool> bg_failed` while holding `mtx`, and the scheduling loop checks that flag instead of `bg_s.ok()`. `bg_s` is read only after `WaitForJobsAndJoinAllThreads()`.
- Document both contracts in `db.h` (`NewDefaultReplayer`), `trace_record.h` (`NewExecutionHandler`) and `replayer.h` (`Replay`).
- Release notes in `unreleased_history/bug_fixes/`.

**Safety, compatibility, limitations:**
- No API or ABI change.
- Callers that already pass the default column family handle see no change from the first fix.
- Behavior change: multi-threaded replay callers that got OK while records failed now get the error, including NotSupported from execution. That matches single-threaded replay and the new `Replay()` comment.
- A record read by the scheduling loop while a failing worker is setting `bg_failed` may still be scheduled. This is allowed by the `Replay()` comment (records already scheduled still run) and does not change the returned status, which is that of the earliest failed record.
- Cost: one extra map entry per replayer, and one branch per record in threaded replay. The loop's `bg_s.ok()` read becomes a relaxed atomic load.
- Read records on column families that are neither passed nor the default still fail with Corruption, as before. Write records never used `handles` and are unchanged.

Reviewed By: xingbowang

Differential Revision: D122626982

fbshipit-source-id: 9a0973448710bac25233feedbaf264696ac85035
2026-10-01 11:43:44 -07:00
Josh Kang aeebcbded4 Add coroutine external table reads (#15291)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15291

Add experimental coroutine interfaces for implementing asynchronous basic and full `ExternalTableReader`s. Implementations opt in through `CoroExternalTableReaderBase<Mode>` by providing `GetCoroutine()` and may override `MultiGetCoroutine()`; the default implementation runs the individual Get operations concurrently. The external-table adapter uses these APIs when supported and otherwise retains the existing synchronous behavior.

As a smaller supporting fix, Single Get coroutine reads using non-mmap, non-direct I/O on a kFSBuffer-capable `FileSystem` now use `FSRandomAccessFile::SubmitReadAsync()` instead of falling back to synchronous `Read()`.

Reviewed By: xingbowang

Differential Revision: D122187491

fbshipit-source-id: 217e2953756938bbd3f30358a997a7ca3ed5543e
2026-10-01 09:28:24 -07:00
Xingbo Wang 11516577ab Pass blob file checksums through blob file opens (#15268)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15268

Why this is required: remote file systems need an immutable blob-file checksum and checksum-function name when RocksDB opens a finalized blob file. This lets the file system distinguish file generations and safely validate or key cached extents.

Previous behavior: integrated BlobDB opened normal read-path blob files with the column family's ordinary FileOptions. Although finalized checksum metadata was stored in BlobFileMetaData and BlobFileAddition, it was not propagated through point reads, range reads, MultiGet, retry opens, or recovery verification. Delayed reads of direct-write files could also cache a finalized file reader opened without metadata.

New behavior: every finalized integrated BlobDB file open receives its checksum value and checksum-function name through FileOptions. Active direct-write files continue to use empty checksum fields until finalization. Delayed reads pinned to a pre-flush SuperVersion use the finalized metadata once the file has been sealed.

Implementation: add internal BlobFileOpenInfo transport across Version, BlobSource, and BlobFileCache. Populate it from version metadata and recovery additions. At the actual cache miss/open, copy the base FileOptions and set both checksum fields. Direct-write sealing registers owned checksum metadata with BlobFileCache, atomically evicts footer-less readers, and prevents a racing pre-seal refresh from repopulating the cache without metadata. Metadata is released after write-path ownership and old-memtable protection end.

Safety boundaries: this does not change public APIs, persisted formats, or blob cache keys. Cache hits do not copy checksum strings. Files with no generated checksum, including active direct-write files, retain empty checksum fields. Existing footer-skip, corruption, and reader-refresh behavior remains in place. Integration in downstream file systems is outside this change.

Tests and known limitations: filesystem-capture tests cover regular blob reads and delayed direct-write reads. After rebasing onto the latest master, blob_file_cache_test, blob_source_test, db_blob_basic_test, and db_blob_direct_write_test passed both normally and with ASSERT_STATUS_CHECKED=1 (95 tests in each configuration). Fast arc lint passed. make format-auto and make check-sources could not operate because their scripts assume a Git checkout while this checkout uses Sapling; all files in the patch were formatted directly with clang-format.

Reviewed By: chaitanyadatye, anand1976

Differential Revision: D121630172

fbshipit-source-id: 601d1097e4a3caa60f0d9ba1794e78e81855980c
2026-09-30 14:55:26 -07:00
Jay Huh 9d358e63d6 Widen compaction service counts to full kNumOfReasons (#14924)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14924

Widen the serialized "counts" array in CompactionServiceResult from kNumOfReasons - 1 back to the full kNumOfReasons, so the count for the newest CompactionReason is included in the remote compaction result.

The width was previously reduced by one to match readers that expected the narrower array; restoring it changes what is written. This is safe only because deserialization of "counts" is now size-tolerant (a reader expecting fewer elements drops the extra, one expecting more zero-fills the missing). **It must not land until that size-tolerant read D106321320 has been deployed to every primary, including rollback targets, so should be >= 2 releases ahead; otherwise a primary can still or fallback to running the strict reader would reject the wider result and fail deserialization of this field.**

== Test plan ==
**Legend**

CompactionReason is an enum whose last entry is kNumOfReasons (currently 21). CompactionStats::counts is an int[kNumOfReasons] holding one count per reason; it is serialized into the remote compaction result and read back by the primary.
- "narrow" = kNumOfReasons - 1 counts (omits the newest reason); what current code writes.
- "wide" = kNumOfReasons counts (includes the newest reason).
- strict reader: accepts only its exact expected element count; any other count fails deserialization (the plain OptionTypeInfo::Array behavior).
- tolerant reader: accepts any count -- more elements than expected are dropped, fewer are zero-filled; never fails on a count mismatch (this change, SizeTolerantDeserializeArray).

**Unit test**

CompactionServiceTest.CompatCheckCountsWidthTolerance -- verifies that deserializing "counts" tolerates a serialized element count equal to, one greater than, or one less than the reader's expected width (extras dropped, missing zero-filled), and that the binary serializes exactly the expected number of elements.

**Manual cross-version verification (ldb remote_compaction)**

Test 1: expect-narrow tolerant-read primary reads wide counts a worker write (extra reason dropped)
```
$ tolerant_wide remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_narrow remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4915 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 2: expect-wide tolerant-read primary reads narrow counts a worker writes (missing reason zero-filled)
```
$ strict_narrow remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_wide remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4911 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Test 3: expect-wide tolerant-read primary reads wide counts a worker writes
```
$ tolerant_wide remote_compaction_worker  --db=$DB --job_dir=$JOB &
$ tolerant_wide remote_compaction_primary --db=$DB --job_dir=$JOB
remote_compaction_worker: OK (4917 bytes result)
remote_compaction_primary: OK
$ grep 'remote compaction result' $DB/LOG
[default] [JOB 3] Received remote compaction result, output path: /tmp/cs_run/output, files: 000022.sst
```
=> PASS: the primary parsed and installed the remote result.

Reviewed By: hx235

Differential Revision: D111068809

fbshipit-source-id: d78dd4b7501cb74e60bcb27dccd869b73052ac6d
2026-09-30 12:39:00 -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 ff29163e47 Fix GCC range-loop warning in table test (#15292)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15292

D120637416 introduced a range-for whose `const std::string&` loop variable binds to temporary strings created from a string-literal initializer list. GCC diagnoses this with `-Wrange-loop-construct`, breaking warnings-as-errors builds. Iterate by value to make the intended string construction explicit without changing test behavior.

___

Differential Revision: D122474818

fbshipit-source-id: c7f30a374dc7569ceb1a4ecc6f7f8c3c3637be00
2026-09-29 18:58:04 -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
Omkar Gawde b4e068e1ed Reuse MANIFEST batch traversal after apply errors (#15288)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15288

Follow-up to D122242630 (https://github.com/facebook/rocksdb/pull/15286), addressing its review.

Previous behavior:
Before D122242630, when `VersionSet::ProcessManifestWrites` failed to build a batch's new `Version`, it returned without removing the batch from `manifest_writers_` (the two `FIXME? manifest_writers_ still has requested updates` comments). The writers are destroyed when their `LogAndApply` call returns, so later MANIFEST updates, including flushes and compactions, could hang behind the dangling entries or corrupt memory. It needs a fatal LSM consistency-check failure, or a `MemoryLimit` from charging `CacheEntryRole::kFileMetadata` (off by default) to a full block cache with `strict_capacity_limit`. It was found by reading the code.

D122242630 fixed that, but found the end of a failed batch with a second scan that copied the grouping loop's stop conditions, which can drift apart, and added no release note.

New behavior:
A failed batch completes the same writers as with D122242630, but only the grouping loop decides where it ends: `ProcessManifestWrites` records the first `LogAndApplyHelper` failure in `batch_apply_status`, later iterations skip version construction but still run the loop's end-of-batch checks, and the batch then completes through `finish_batch`. This also labels the inner-loop `break`s and adds a release note.

Safety, compatibility, and limitations:
No API, option, or format change. The success path and the `SaveTo` failure path are unchanged.

Reviewed By: pdillinger

Differential Revision: D122417768

fbshipit-source-id: c6d474ace32446d6b413b1a1b3cf2fa49a02447d
2026-09-29 17:31:56 -07:00
Xingbo Wang eed2f96676 Wait for in-flight secondary read-view reclamation (#15289)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15289

Why the change is required:

Secondary catch-up must release reader-free retired read views before scanning
for obsolete files. T290043099 and duplicate T290043100 exposed a race where
that ordering was lost, leaving two obsolete table files open and failing
DBSecondaryTest.SecondaryCloseFiles. The assertion failure also exposed unsafe
test cleanup because the secondary DB could outlive the test-local Env.

Previous behavior:

Retiring a read view always woke the background reclaimer. The worker could
remove a reader-free view from the retirement queue and pause before destroying
its SuperVersions. Synchronous cleanup then saw no queued work, ignored the
in-flight reclamation, and continued to obsolete-file discovery while the old
files were still referenced. The test also used a static lambda that captured
the first fixture across repeated runs and closed the secondary only on the
success path.

New behavior:

Catch-up synchronously reclaims reader-free views, or waits when a competing
background worker has already claimed them. Views still held by active readers
remain non-blocking and are reclaimed asynchronously after the last reader
releases them. The test now closes the secondary before its local Env on every
exit path and creates its verification lambda per test invocation.

Implementation:

Publishing a replacement view no longer wakes the background reclaimer; the
immediately following synchronous cleanup owns that work. Reader release still
wakes the worker. Cleanup waits on the existing in-flight counter when a worker
won an earlier race. The test uses a Defer guard for lifetime ordering and
removes the repeat-unsafe static lambda.

Safety, compatibility, and limitations:

This changes no public API, ABI, file format, recovery behavior, or read
consistency. Cleanup does not wait for views retained by active readers, so the
non-blocking multi-column-family iterator behavior is preserved. It waits only
for reclamation already in progress. Validation covered the internal Linux
ASAN/UBSAN target; the external GitHub Actions matrix was not run. Repository
make-based format/source checks require a Git worktree and were unavailable in
this Sapling checkout.

Reviewed By: jaykorean

Differential Revision: D121816425

fbshipit-source-id: a86cfff43a0cedd10b90e526a56e862f18d2a58d
2026-09-29 16:47:03 -07:00
Omkar Gawde 5fa16200aa Release MANIFEST writers after batch-build failures (#15286)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15286

**What**: `LogAndApply` completes every MANIFEST writer in a batch when version construction fails before persistence.

**How**: `VersionSet::ProcessManifestWrites` centralizes queue completion in `finish_batch`, invokes it for `VersionBuilder::Apply` and `SaveTo` failures, and extends assembly failures through the normal group boundary. Completion propagates the failure through callbacks, signals writers owned by other callers, and wakes the next leader.

**Why**: Stack-backed writers otherwise remain queued after their caller returns, leaving dangling pointers, blocked writes, and flush or compaction state that never receives its failure callback.

Reviewed By: xingbowang

Differential Revision: D122242630

fbshipit-source-id: bab4ac8f665269ba57dd86579299c1d5935fa907
2026-09-29 11:59:49 -07:00
Omkar Gawde 7510773057 Fix ClipColumnFamily key lifetime and atomic-flush wait alignment (#15273)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15273

**What**: `ClipColumnFamily` keeps boundary keys valid across unlocked writes, and atomic-flush waits pair every column family with the correct memtable bound.
**How**: `DBImpl::ClipColumnFamily` copies boundary keys from the current `Version` while holding the DB mutex, with a sync point that lets the regression test replace that version before deletion. `DBImpl::AtomicFlushMemTables` and `DBImpl::RetryFlushesForErrorRecovery` construct wait bounds in `cfds` order and supply no-bound fallbacks when a column family is absent from the flush-request map. Both wait sites gain a debug invariant asserting each id matches the map entry for the cfd at the same index.
**Why**: Background version replacement could leave `ClipColumnFamily` reading freed metadata, while unordered or missing atomic-flush map entries could cause early completion or out-of-bounds access for idle or duplicate column families.

Reviewed By: xingbowang

Differential Revision: D121840843

fbshipit-source-id: 8e49b5c953a1aa29a0244fc0aae2ca3ab84be74f
2026-09-28 14:31:40 -07:00
xingbowang 5eed79a0e1 Scale container shared memory by runner core count (#15284)
Summary:
GitHub Actions container jobs previously used a fixed 16 GB `/dev/shm` allocation regardless of runner size, while the clang-tidy container did not set one explicitly. This over-allocated shared memory on smaller runners and gave larger, more highly parallel runners no additional shared-memory capacity.

Scale container shared memory to 2 GB per runner core across the PR, nightly, nightly-candidate, weekly, and clang-tidy workflows:

- 4-core jobs use 8 GB.
- 16-core jobs use 32 GB.
- 32-core jobs use 64 GB.
- The nightly-candidate ARM job uses 8 GB, matching its four-way build.

The clang-tidy job now explicitly receives 8 GB. Existing unrelated container options, including the mini crash test's `nofile` limit, remain unchanged.

## Safety and compatibility

This only changes container resource configuration for GitHub Actions jobs. It does not change RocksDB code, APIs, persistent formats, or non-containerized jobs.

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

Test Plan:
- `make check-workflow-yaml`
- `git diff --check upstream/main...2026_09_28_shm`

Both checks pass. The CI workloads themselves were not executed locally, so the new shared-memory allocations will be exercised by PR and scheduled workflow runs.

Reviewed By: omkarhgawde

Differential Revision: D122218094

Pulled By: xingbowang

fbshipit-source-id: aa577c37683022ba01c4a073a011dbe0032abbf6
2026-09-28 14:10:43 -07:00
Omkar Gawde 3d381e1bd4 Fix heap-use-after-free in DBSecondaryTest.SecondaryCloseFiles Env teardown (#15283)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15283

**What**: `DBSecondaryTest.SecondaryCloseFiles` no longer calls into a destroyed `Env` when the test body returns early, fixing a ThreadSanitizer heap-use-after-free at secondary DB close.
**How**: The test opens `db_secondary_` (a fixture member) on a stack-local `traced_env`. A `Defer` now closes the secondary before `traced_env` is destroyed on every exit path, so the later `~DBSecondaryTestBase` close never reaches `DBImpl::CloseHelper`'s `env_->UnSchedule` on a freed Env. This mirrors the sibling `DBSecondaryCatchUpFaultTest` fixture, which already closes the secondary in its destructor before its Env member for the same reason.
**Why**: On any early `ASSERT_*` return before the explicit close, `traced_env` (a body local) was freed first while `db_secondary_` (a fixture member) was closed later in the base destructor, which made a virtual call on the freed Env. ThreadSanitizer flagged this as a land-blocking heap-use-after-free in the `build-linux-clang21-mini-tsan` CI job.

Failed CI run: https://fburl.com/jo6gtllf

___

Differential Revision: D122195601

fbshipit-source-id: 3d8b427ba50759c40f15d2b512379594b6057234
2026-09-28 13:06:36 -07:00
Steph Pontikes f8d3141e5c Thread column family names to table readers and add ExternalTableFactory recovery (#15265)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15265

This change adds column family name reflection to an external table reader and allows the user to extract an external table factory from a top level table factory, if it exists

Motivation

Adding column family metadata to the reader allows the reader to handle CF specific metadata, also add external table reflection

Previous/New Behavior

No change in behavior. Added a defaulted parameter threaded through many constructors to preserve existing API contract while introducing new capability. Added a function which allows unwrapping of an external table factory

Nothing much to test

Reviewed By: xingbowang

Differential Revision: D121335729

fbshipit-source-id: 74f9b1be6a8f301bdbe4540de0656b83e26f88ab
2026-09-28 12:04:07 -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
Josh Kang dbc2db5228 Make external table APIs more consistent with native tables (#15241)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15241

Native `TableFactory` implementations receive the RocksDB `WritableFileWriter` in `NewTableBuilder()` and `RandomAccessFileReader` in `NewTableReader()`. These wrappers provide buffering, checksums, rate limiting, statistics, listeners, tracing, and direct-I/O handling. External table implementations previously received a limited writable-file adapter and reopened readers from a path, bypassing parts of this wrapper logic.

This change makes `WritableFileWriter` and `RandomAccessFileReader` implement `FSWritableFile` and `FSRandomAccessFile`, respectively, so the same RocksDB wrappers can be passed to external tables. `ExternalTableFactory::NewTableReader()` now receives the already-opened reader and file size. The filesystem interfaces expose standard append, flush, sync, fsync, file metadata, synchronous reads, asynchronous reads, and batched reads. `RandomAccessFileReader::SubmitReadAsync()` schedules its wrapped coroutine read on the configured filesystem read executor, and the DB table-cache and `SstFileReader` construction paths pass that filesystem through. `SstFileReader` also supplies its configured statistics, listeners, rate limiter, and file temperature, removing the need for a separate external-table metrics filesystem wrapper.

For direct I/O, `MultiRead()` checks whether requests are already sorted. When alignment and merging are needed, it constructs a sorted index only for unsorted input, preserving caller request order and result mapping. Fully aligned requests using aligned caller buffers bypass this path and may be supplied in any order.

Reviewed By: xingbowang

Differential Revision: D121035539

fbshipit-source-id: 27cb897726c31cb1a4dc811c65c8d79e690d82b1
2026-09-24 11:46:13 -07:00
Xingbo Wang 07cbacadb4 Avoid materializing huge SSTs in size-boundary test (#15264)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15264

Why the change is required:
LargeSizeSstFileWriter creates approximately 12 GiB of SST output in the shared /dev/shm used by build-linux. With 32 concurrent test jobs in a 16 GiB tmpfs, this can exhaust the filesystem and make unrelated fault-injection and repair shards fail with ENOSPC.

Previous behavior:
The test wrote an approximately 8 GiB large-key SST and an approximately 4 GiB large-value SST even though it never read or ingested either file. It validated the size limits and successful Finish calls, but the physical output competed with every other test process.

New behavior:
The test still runs the real SstFileWriter and table builder for over-limit rejection and maximum-size acceptance, including Finish, sync, and close. The generated byte stream is counted and discarded instead of materialized in the shared tmpfs.

Implementation:
Add a test-local FileSystem wrapper whose writable file counts appended bytes and records sync and close calls. Inject it only into LargeSizeSstFileWriter and verify that the writer-reported logical size matches the observed append stream, the lifecycle completes, and no physical SST is created.

Safety, compatibility, and limitations:
Production code, shared filesystem implementations, serialized formats, and other tests are unchanged. The fake does not validate SST contents, but the previous test did not read the generated files either. Table construction still consumes substantial memory, so the existing HasBigMem guard remains; this change addresses tmpfs exhaustion only.

Reviewed By: rban1

Differential Revision: D121483663

fbshipit-source-id: 055372bc07e3a1f7289e34ebc35694bc3c4bfad9
2026-09-24 09:46:56 -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
Omkar Gawde 0675830967 Verify prefetched block checksums with retry-safe cleanup (#15254)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15254

**What**: `IODispatcher` verifies prefetched data blocks before serving or caching them and safely falls back when block construction fails.

**How**: `CreateAndPinBlockFromBuffer` validates every returned span and, when `ReadOptions::verify_checksums` is enabled, verifies its checksum and records the standard counters. `PollAndProcessAsyncIO` handles coalesced blocks independently and retires shared request state before failed entries fall back through `SyncRead`; `ReleaseFailedSyncPrefetchMemory` returns limiter capacity for unmaterialized synchronous blocks.

**Why**: Unverified prefetches could serve or cache bit rot, while newly detected failures could otherwise leave async state referencing released provider buffers or permanently consume bounded prefetch capacity.

Reviewed By: xingbowang

Differential Revision: D121277687

fbshipit-source-id: 65cc1d5b9dcc53bdc64f8ee5d672bfe2cda7b40b
2026-09-23 11:34:25 -07:00
Xingbo Wang 14a82121d4 Add shared diff and pull request summary guidance (#15261)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/15261

Why the change is required:
Internal Phabricator diffs and external GitHub pull requests need the same substantive explanation of a RocksDB change. Existing local instructions focus on code generation and review but do not define what a summary must tell reviewers, so summaries can omit prior behavior, safety boundaries, compatibility, or validation evidence and can drift between the two review surfaces.

Previous behavior:
There was no CLAUDE.md at the internal_repo_rocksdb wrapper root, the RocksDB CLAUDE.md did not point authors to summary guidance, and there was no shared document covering both internal diff and external pull request summaries.

New behavior:
The wrapper-level CLAUDE.md requires authors to consult a shared summary guideline before writing or updating an internal diff summary. The repository CLAUDE.md applies the same guideline to external pull request summaries. The guideline requires motivation, previous and new behavior, implementation, safety and compatibility boundaries, and test evidence, with RocksDB-specific advice for correctness, persisted formats, concurrency, and performance changes. Examples are separated by access boundary: the internal wrapper contains Phabricator diff examples, while the repository guideline contains publicly accessible GitHub pull request examples.

Implementation:
Add a concise wrapper-level CLAUDE.md, add claude_md/pull_request_summary_guideline.md as the shared source of truth, and link it from repo/CLAUDE.md. Keep annotated internal diff references in the wrapper-level file and corresponding public pull request references in the repository guideline, including a recent change from Peter Dillinger.

Safety boundaries and unsupported cases:
This is documentation-only and changes no runtime, API, build, test, or CI behavior. The repository guideline contains no internal links, so external readers do not need Meta access. The guidance is manually followed by authors and agents; it does not mechanically validate summaries, replace reviewer judgment, or require a rigid section layout for small changes.

___

Differential Revision: D121388476

fbshipit-source-id: bb09ab7ba8ef6dc2b2830a42735322890536622f
2026-09-23 08:44:55 -07:00
639 changed files with 28018 additions and 5498 deletions

No files matched your search

+1
View File
@@ -12,6 +12,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
with:
+2 -2
View File
@@ -1,4 +1,4 @@
name: facebook/rocksdb/nightly
name: facebook/rocksdb/nightly-candidate
on: workflow_dispatch
permissions: {}
jobs:
@@ -10,7 +10,7 @@ jobs:
labels: arm64large
container:
image: ubuntu-2004:202111-02
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
+8 -8
View File
@@ -11,7 +11,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
with:
@@ -33,7 +33,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
env:
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
steps:
@@ -47,7 +47,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=32gb
env:
CC: clang-21
CXX: clang++-21
@@ -84,7 +84,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -103,7 +103,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -161,7 +161,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -174,7 +174,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -189,7 +189,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
+18 -18
View File
@@ -92,7 +92,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -110,7 +110,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -135,7 +135,7 @@ jobs:
labels: 32-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=64gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -160,7 +160,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -180,7 +180,7 @@ jobs:
labels: 32-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=64gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -209,7 +209,7 @@ jobs:
shard: [0, 1, 2, 3]
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -232,7 +232,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -252,7 +252,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/setup-ccache"
@@ -306,7 +306,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -373,7 +373,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -394,7 +394,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: gcc:latest
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- run: apt-get update -y && apt-get install -y libgflags-dev
@@ -422,7 +422,7 @@ jobs:
crash_duration: 240
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb --ulimit nofile=1048576:1048576
options: --shm-size=8gb --ulimit nofile=1048576:1048576
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -457,7 +457,7 @@ jobs:
shard: [0, 1, 2]
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=64gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -484,7 +484,7 @@ jobs:
shard: [0, 1, 2]
container:
image: ghcr.io/facebook/rocksdb_ubuntu:24.1
options: --shm-size=16gb
options: --shm-size=64gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -507,7 +507,7 @@ jobs:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -614,7 +614,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -637,7 +637,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
@@ -740,7 +740,7 @@ jobs:
labels: 4-core-ubuntu
container:
image: evolvedbinary/rocksjava:alpine3_x64-be
options: --shm-size=16gb
options: --shm-size=8gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/install-maven"
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
timeout-minutes: 840
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
options: --shm-size=32gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
+60 -4
View File
@@ -241,6 +241,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"table/plain/plain_table_index.cc",
"table/plain/plain_table_key_coding.cc",
"table/plain/plain_table_reader.cc",
"table/range_del_block.cc",
"table/sst_file_dumper.cc",
"table/sst_file_reader.cc",
"table/sst_file_writer.cc",
@@ -415,13 +416,27 @@ cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
"table/mock_table.cc",
"test_util/mock_time_env.cc",
"test_util/secondary_cache_test_util.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testharness.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
"utilities/agg_merge/test_agg_merge.cc",
"utilities/cassandra/test_utils.cc",
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=True)
], deps=[":rocksdb_lib"], headers=[
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
], link_whole=False, extra_test_libs=True)
cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
"db/db_test_util.cc",
@@ -429,13 +444,27 @@ cpp_library_wrapper(name="rocksdb_with_faiss_test_lib", srcs=[
"table/mock_table.cc",
"test_util/mock_time_env.cc",
"test_util/secondary_cache_test_util.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testharness.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
"utilities/agg_merge/test_agg_merge.cc",
"utilities/cassandra/test_utils.cc",
], deps=[":rocksdb_with_faiss_lib"], headers=[], link_whole=False, extra_test_libs=True)
], deps=[":rocksdb_with_faiss_lib"], headers=[
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
], link_whole=False, extra_test_libs=True)
cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
"test_util/testutil.cc",
@@ -444,7 +473,12 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
"tools/simulated_hybrid_file_system.cc",
"tools/tool_hooks.cc",
"tools/trace_analyzer_tool.cc",
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
], deps=[":rocksdb_lib"], headers=[
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/simulated_hybrid_file_system.h",
"tools/trace_analyzer_tool.h",
], link_whole=False, extra_test_libs=False)
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
@@ -468,10 +502,28 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
"db_stress_tool/expected_value.cc",
"db_stress_tool/multi_ops_txns_stress.cc",
"db_stress_tool/no_batched_ops_stress.cc",
"test_util/simple_external_table_factory.cc",
"test_util/testutil.cc",
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
"tools/trace_analyzer_tool.cc",
], headers=[])
], headers=[
"db_stress_tool/db_stress_common.h",
"db_stress_tool/db_stress_compaction_service.h",
"db_stress_tool/db_stress_compression_manager.h",
"db_stress_tool/db_stress_driver.h",
"db_stress_tool/db_stress_filters.h",
"db_stress_tool/db_stress_listener.h",
"db_stress_tool/db_stress_shared_state.h",
"db_stress_tool/db_stress_test_base.h",
"db_stress_tool/db_stress_wide_merge_operator.h",
"db_stress_tool/expected_state.h",
"db_stress_tool/expected_value.h",
"db_stress_tool/multi_ops_txns_stress.h",
"test_util/simple_external_table_factory.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
])
cpp_binary_wrapper(name="ldb", srcs=["tools/ldb.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
@@ -4761,6 +4813,7 @@ cpp_unittest_wrapper(name="compression_test",
cpp_unittest_wrapper(name="configurable_test",
srcs=["options/configurable_test.cc"],
source_headers=["options/configurable_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5465,6 +5518,7 @@ cpp_unittest_wrapper(name="periodic_task_scheduler_test",
cpp_unittest_wrapper(name="persistent_cache_test",
srcs=["utilities/persistent_cache/persistent_cache_test.cc"],
source_headers=["utilities/persistent_cache/persistent_cache_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5483,6 +5537,7 @@ cpp_unittest_wrapper(name="point_lock_manager_stress_test",
cpp_unittest_wrapper(name="point_lock_manager_test",
srcs=["utilities/transactions/lock/point/point_lock_manager_test.cc"],
source_headers=["utilities/transactions/lock/point/point_lock_manager_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
@@ -5693,6 +5748,7 @@ cpp_unittest_wrapper(name="trace_analyzer_test",
cpp_unittest_wrapper(name="transaction_test",
srcs=["utilities/transactions/transaction_test.cc"],
source_headers=["utilities/transactions/transaction_test.h"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
+12
View File
@@ -244,6 +244,11 @@ from an implementation detail instead of an explicit option.
## Important tips
### Pull request and diff summaries
* Before writing or updating an external RocksDB pull request summary or its
corresponding internal Phabricator diff summary, read and follow
`claude_md/pull_request_summary_guideline.md`.
### Build system
* There are 3 build system. Make for git clones, BUCK (meta internal) for hg
clones, and CMake for some special cases.
@@ -310,6 +315,7 @@ build (and where noted, run tests) across:
| Axis | Must support |
|------|--------------|
| OS | Linux (x86_64 + ARM), macOS, Windows |
| Architecture | Little-endian and big-endian targets, including Linux on s390x |
| Compiler | GCC, Clang (libstdc++ **and** libc++), AppleClang, **MSVC (VS2022)**, MinGW (Linux cross-compile, build-only, no gflags) |
| Build system | Make, CMake, and BUCK (internal) -- keep all in sync (see "Build system" above) |
| Config | release (`-fno-rtti`), `ASSERT_STATUS_CHECKED`, ASAN/UBSAN/TSAN, folly, unity build, JNI/Java |
@@ -324,6 +330,12 @@ abstractions; otherwise guard with `#ifdef OS_WIN` (POSIX `<unistd.h>` ->
Windows `<process.h>`). Because libc++ is also tested, include what you use
rather than relying on libstdc++ transitive includes.
Do not use `static_assert` to reject a supported architecture for an optional
feature. Check an existing `port::` capability such as `port::kLittleEndian`
and disable only the unsupported feature so the rest of RocksDB still builds.
Use a portable representation when the feature itself must work across
architectures.
### Unit Test
* After all of the unit tests are added, review them and try to extract common
reusable utility functions to reduce code duplication due to copy past between
+11
View File
@@ -650,6 +650,9 @@ if(USE_FOLLY)
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../glog* OUTPUT_VARIABLE
GLOG_INST_PATH)
set(Boost_DIR ${BOOST_INST_PATH}/lib/cmake/Boost-1.83.0)
# Boost's per-library CMake configs find their dependencies (e.g.
# boost_assert) only through the package search path.
set(CMAKE_PREFIX_PATH ${BOOST_INST_PATH} ${CMAKE_PREFIX_PATH})
if(EXISTS ${FMT_INST_PATH}/lib64)
set(fmt_DIR ${FMT_INST_PATH}/lib64/cmake/fmt)
else()
@@ -696,7 +699,13 @@ if(USE_FOLLY)
exec_program(sed ARGS -i 's/gflags_shared//g'
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
# folly-config.cmake only checks for gflags under a target name that this
# install doesn't define, then falls back to our Findgflags.cmake, which
# mistakes the gflags_static target name for a library path. gflags is
# resolved below instead.
set(CMAKE_DISABLE_FIND_PACKAGE_gflags TRUE)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
unset(CMAKE_DISABLE_FIND_PACKAGE_gflags)
# Fix gflags library name for debug builds. The getdeps gflags shared
# library is versioned (e.g. libgflags_debug.so.2.3), so resolve the
@@ -972,6 +981,7 @@ set(SOURCES
table/plain/plain_table_index.cc
table/plain/plain_table_key_coding.cc
table/plain/plain_table_reader.cc
table/range_del_block.cc
table/sst_file_dumper.cc
table/sst_file_reader.cc
table/sst_file_writer.cc
@@ -1688,6 +1698,7 @@ if(WITH_TESTS)
db/db_with_timestamp_test_util.cc
monitoring/thread_status_updater_debug.cc
table/mock_table.cc
test_util/simple_external_table_factory.cc
utilities/agg_merge/test_agg_merge.cc
utilities/cassandra/test_utils.cc
)
+111
View File
@@ -1,6 +1,117 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.13.2 (10/21/2026)
### Bug Fixes
* Fixed blob direct write with more than one concurrent flush of a column family: a later flush sealed and published the blob files of the oldest pending memtable instead of its own, so flushes failed with `Corruption: VersionBuilder: Blob file #N already added` and the database stopped.
## 11.13.1 (10/23/2026)
### Bug Fixes
* Fixed data loss with `min_tombstones_for_range_conversion` and external file ingestion. An iterator whose view predated the ingestion could convert the deleted keys around the ingested keys into a range tombstone that deleted them. This happened when the file got no new sequence number (sequence number zero, `ingest_behind`, or `allow_db_generated_files`), and when an iterator was created during the ingestion.
* Fixed a data loss bug where an iterator with `ReadOptions::read_tier = kMemtableTier` could, with `min_tombstones_for_range_conversion` enabled, insert a range tombstone that deleted live keys stored only in SST files. Read-path range tombstone conversion now runs only for iterators with `read_tier = kReadAllTier`.
## 11.13.0 (10/21/2026)
### New Features
* Added `FileSystem::IsPreallocationSupported()`. A `FileSystem` that never preallocates file space can return false so that DB open skips reopening and truncating the last WAL file.
### Public API Changes
* Added the experimental `DBOptions::track_options_file_number_in_manifest`
rollout option (with C and Java bindings). The option is persisted in each
OPTIONS file. When enabled, RocksDB durably creates an OPTIONS file and then
commits its file number in MANIFEST. Column-family create/drop commits the
configuration snapshot and membership in one MANIFEST record. A newer tracked
OPTIONS file without a commit is ignored after a crash, while a newer OPTIONS
file written with tracking disabled retains the legacy highest-numbered-file
behavior. Batched column-family changes publish one snapshot; after partial
success it can be a safe superset that persisted-options APIs filter against
live MANIFEST membership. Ordinary `DB::Open` continues to use caller-supplied
options and does not depend on the operational snapshot.
* Added `EncodeEntry()`, `PackSequenceAndType()`, and `UnPackSequenceAndType()` in `rocksdb/utilities/types_util.h` to construct internal keys and pack or unpack their sequence numbers and entry types. `kInternalKeyFooterSize` exposes the footer size in bytes.
* Added `ldb update_manifest --rebind_options_file --options_file=OPTIONS-<number>` for offline repair of a committed MANIFEST OPTIONS file number. Operators must select a trusted snapshot that has MANIFEST tracking enabled and provide any external DB/CF allocation roots that are not serialized in OPTIONS.
### Behavior Changes
* With `max_bgerror_resume_count <= 0` (auto resume disabled), a retryable or file-scoped IO error during compaction or flush is now a hard background error that stops writes until `DB::Resume()` is called. Previously, compaction errors were silently retried, and flush errors with a healthy WAL were soft errors that let writes continue while flushes were stopped.
### Bug Fixes
* Fixed two more `SstFileManager` trash accounting leaks: retrying a failed trash deletion immediately (for example with rate limiting disabled) after the file had disappeared, and a trash file that was deleted but whose directory sync then failed. In both cases the file's size stayed in the total trash size and tracked size until restart.
* Fixed `SstFileManager` trash accounting leaking the size of a queued trash file that disappeared before deletion (for example because its directory was renamed). The leaked bytes stayed in the total trash size until restart, which could push every later deletion past `max_trash_db_ratio` and make it immediate instead of rate limited.
* Fixed `MultiGet` with `ReadOptions::async_io` returning `NotSupported` when io_uring initialization or an asynchronous read submission fails. It now checks async I/O support on the reading thread and falls back to synchronous reads when needed.
* Fixed subset checkpoints so their newest OPTIONS file lists only the column
families retained by the checkpoint. This applies with both legacy
highest-numbered OPTIONS selection and MANIFEST-tracked OPTIONS selection, and
prevents `LoadLatestOptions` from recreating column families that the subset
checkpoint intentionally dropped from its copied MANIFEST.
### Performance Improvements
* Perf context counters and timers no longer call the thread-local init function of `perf_context` on every update. With `kEnableCount`, a counter update costs about 60% less.
## 11.12.0 (10/07/2026)
### New Features
* Added `FullExternalTableFactory` and related full-mode external table APIs that preserve RocksDB internal keys, enabling external tables to support nonzero sequence numbers, point and single deletions, merge operands, blob indexes, wide-column entities, snapshots, live DB writes, and multi-level compactions.
### Public API Changes
* Added experimental coroutine capability interfaces for basic and full `ExternalTableReader` implementations. Coroutine-capable external readers can initiate file reads through `FSRandomAccessFile::SubmitReadAsync()`.
* `ExternalTableFactory::NewTableReader()` now receives an already-opened `FSRandomAccessFile` and its file size instead of requiring implementations to reopen `file_path`. The supplied file preserves RocksDB's direct-I/O alignment handling, rate limiting, read statistics and listener notifications, prefetching, and synchronous, asynchronous, and batched reads.
* Added `PutRangeDeletionBlock()`, `GetRangeDeletionBlock()`, and `IsDeleteRangeSupported()` to the external table interfaces so formats can opt in to range deletions using RocksDB's raw range-deletion metadata block.
* Opaque `PreparedFileInfo` metadata can now be serialized to a checksummed representation with `SerializePreparedFileInfo()` and restored with `DeserializePreparedFileInfo()` for reuse across processes.
* Removed the deprecated `IngestExternalFileOptions::write_global_seqno` option and its C, Java, and `ldb` APIs, `TableProperties::external_sst_file_global_seqno_offset`, and the `file_offset` output from `ExternalTableReader::GetPropertiesBlock()`. Existing external SST files whose global sequence number was rewritten without updating the properties-block checksum remain readable.
* `TablePropertiesNames` has no apparent use in the public API so has been deprecated there.
* `TableProperties::creation_time` is renamed to `oldest_ancestor_time`, which better describes its meaning, along with `TablePropertiesNames::kCreationTime` to `kOldestAncestorTime`. The misspelled `SstFileMetaData::oldest_ancester_time` is renamed to `oldest_ancestor_time`. The old names remain available as deprecated aliases and will be removed in a future release.
* Added `WriteBufferFlushPolicy` and `WriteBufferManager` policy selection for flushing the oldest or largest column family, including configurable batched flushing of the largest column families across DBs sharing a manager, with C and Java bindings.
### Behavior Changes
* When `ExternalTableBuilderBase::PutPropertiesBlock()` returns `NotSupported`, RocksDB now overrides `num_entries`, `raw_key_size`, and `raw_value_size` from `ExternalTableBuilderBase::GetTableProperties()` with the logical totals tracked by the external-table adapter. Format-specific properties such as `data_size` remain unchanged.
* The `FSWritableFile` passed to `ExternalTableFactory::NewTableBuilder()` now reports logical file size including RocksDB-buffered data and exposes the underlying file's direct-I/O mode and required buffer alignment.
* `Iterator::Refresh()` now returns `NotSupported` on iterators from a secondary DB's `DB::NewIterators()` only, because those iterators are pinned to one cross-column-family read view. Iterators from `DB::NewIterator()` are unaffected; to advance a batch, call `DB::NewIterators()` again after `TryCatchUpWithPrimary()`.
* `SstFileReader` now reports standard SST read statistics and file I/O listener notifications, and honors the configured read rate limiter.
### Bug Fixes
* Fixed full recovery from retryable background errors to atomically advance WAL replay boundaries for all column families, preventing partial recovery of multi-column-family write batches after a failed recovery flush.
* Fixed RocksDB builds on big-endian architectures such as s390x by disabling the optional persistent fault-injection log, whose binary format is little-endian-only.
* Fixed `DB::ClipColumnFamily()` deleting a non-L0 SST file whose largest key equals `begin_key`, which lost keys at the start of the kept range.
* Fixed swapped `oldest_key_time` and `newest_key_time` values in the `table_file_creation` event log entry.
* Fixed `ExternalTableReader::VerifyChecksum()` results not being honored by the external table adapter.
* Fixed a bug in manifest handling that could lead to a hang or memory corruption in the case of LSM sanity checks being fatally violated (exceptional; see `force_consistency_checks`).
* Fixed `MultiGet()` with `ReadOptions::read_tier == kBlockCacheTier` reading data blocks from disk when `allow_mmap_reads` is false. Keys whose data block is not in the block cache now return `Status::Incomplete()`, the same as `Get()`.
* Fixed trace replay (`DB::NewDefaultReplayer()`, db_bench `replay`) failing every read record (Get, MultiGet, iterator seek) on the default column family with `Corruption: Invalid Column Family ID` when the default column family handle was not passed, which is always the case in db_bench with `--num_column_families=1`.
* Fixed multi-threaded `Replayer::Replay()` ignoring errors from executing trace records and returning OK. It now stops and returns the error, including `Status::NotSupported()` from executing a record, as single-threaded replay does.
* Fixed secondary DB `NewIterators()` returning iterators from different database states or blocking for `TryCatchUpWithPrimary()` by serving the last completed catch-up view.
### Performance Improvements
* Single Get coroutine reads using non-mmap, non-direct I/O on a `kFSBuffer`-capable `FileSystem` now use `FSRandomAccessFile::SubmitReadAsync()` instead of falling back to synchronous `Read()`. When rate-limited, these reads acquire their full token budget before submission to preserve a single filesystem-owned buffer, which can make physical I/O burstier than reads using caller-provided buffers.
## 11.11.0 (09/21/2026)
### New Features
* External tables now support DB-assigned global sequence numbers when `IngestExternalFileOptions::allow_global_seqno` is enabled, allowing overlapping external tables to be ingested across multiple levels when compactions are disabled. This includes the deprecated `write_global_seqno` path when the reader supplies the properties block's file offset.
* Added checksum handoff support for SST file writes when `BlockBasedTableOptions::checksum` is `kCRC32c`, allowing block and trailer CRC32C checksums to be passed from the table builder to the file writer for buffered-data validation and filesystem in-flight write protection.
* Enhanced `sst_dump --command=recompress` to be more powerful for benchmarking, including through automation with a new `--json` option for machine-readable output.
### Public API Changes
* Changed the experimental async read statistics API by removing `DB::AsyncCallback::EnableStats()` and changing `OnComplete(const PerfContext*, const IOStatsContext*)` to `OnComplete()`. Callers must configure TLS statistics before each async or coroutine read. `DB::GetAsync()` and `DB::MultiGetAsync()` callbacks must copy per-operation statistics from TLS during `OnComplete()`; coroutine users must copy them from inside a `CoroStackableDB` hook before suspending again. Each operation leaves TLS statistics collection disabled.
* Added `Compressor::MaybeOverrideCompressionOptions()` (EXPERIMENTAL), an omnibus hook letting a custom compressor adjust the subset of `CompressionOptions` that the SST builder applies rather than the compressor itself: `parallel_threads`, `max_compressed_bytes_per_kb`, `auto_skip`, and `auto_skip_min_sample_every` (and potentially more in the future). This deprecates `Compressor::GetRecommendedParallelThreads()`, which it subsumes; the default implementation still honors an overridden `GetRecommendedParallelThreads()` for compatibility.
* Added experimental `CoroDB::CoGet()` and `CoroDB::CoMultiGet()` overloads for point reads from an `SstFileReader`.
* Added experimental `DB::GetWithMetadata()` and `DB::MultiGetWithMetadata()` APIs with opt-in output metadata for key timestamps and explicit-snapshot newer-version detection.
* Added a `DB::NewIterators()` overload that accepts parallel vectors of `ReadOptions` and column family handles, allowing each returned iterator to use independent read settings while sharing a consistent database state. The vectors must have the same size, all snapshots must be null or identical, and all `tailing` settings must match.
* Added `ReadOptions::custom_context`, an opaque, non-owning pointer passed through table reads so external table implementations can consume application-defined read options.
### Behavior Changes
* `ExternalTableReader::MultiGet()` is now used by RocksDB's `DB::MultiGet()` path. Previously, the external table adapter used `TableReader`'s fallback, which called `ExternalTableReader::Get()` separately for each key.
* `DB::OpenAndCompact()` now requires `output_directory` to be a simple name for a new subdirectory when the new `use_session_tmp_dir_for_remote_compaction` DB option is enabled. The option places that subdirectory under the canonical `<dbname>/session_tmp/` root, and `DB::Open()` deletes stale contents under the root after recovery. The disabled default preserves the existing `<dbname>/<output_directory>` layout without automatic cleanup. Previously, remote-compaction output directories were created directly under `<dbname>`, which made it challenging to safely identify and delete obsolete files.
* Several block-based SST table properties now better support compression benchmarking and optimization: `uncompressed_data_size` is now persisted in the file (previously it was computed only at write time and read back as 0); a new `lsm_info_at_creation` property records the LSM level and "bottommost" status at the file's creation, distinguishing "not applicable" (e.g. SstFileWriter files) from "unknown" (older files); and the `compression_options` property now records the full compression manager configuration instead of only its name.
### Bug Fixes
* Fixed a usually-minor performance bug where `Cache::ApplyToAllEntries()` used single-entry batches instead of the intended 256-entry default for `ApplyToAllEntriesOptions::average_entries_per_lock`. All of the Cache implementations (LRU and HCC) benefit from the fix, but the disfavored LRU cache was especially impacted by repeated mutex acquisition and release.
* Fixed stats-disabled coroutine and asynchronous reads inheriting enabled TLS statistics state when interleaved with stats-enabled reads on the same executor. Each read now uses its captured per-call configuration, while enabled request counters remain isolated across suspensions.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) requested with `OpenAndCompactOptions::allow_resumption=true` but for which resumption was internally disabled (incompatible with `paranoid_file_checks` or output-iteration verification) could fail a retried attempt, because the output directory was not cleaned before starting fresh and output files left by a previously interrupted attempt collided with the reused file numbers.
* Fixed a bug where a remote compaction (`CompactionService` / `DB::OpenAndCompact`) could fail when the primary rotated its MANIFEST while the worker was opening the primary's directory read-only. The open reads `CURRENT`, and the primary publishes a new MANIFEST by renaming a freshly written `CURRENT` over the old one; on file systems without POSIX read-after-unlink semantics (remote/object stores) the replaced object is then reported as gone (`PathNotFound`/`NotFound`, or `TryAgain` for the MANIFEST equivalent) instead of remaining readable. `DB::OpenAndCompact()` now retries the secondary open a bounded, configurable number of times for those transient statuses, so a concurrent rotation no longer fails the compaction (which, for a `CompactionService` that does not fall back to a local compaction, became a background error on the primary).
* Fixed secondary DB `NewIterators()` returning iterators from different database states when `TryCatchUpWithPrimary()` runs concurrently.
* Fixed a bug where a `SeekToFirst()` on an iterator previously used for a `Seek()` could end the scan early and silently omit keys with `ReadOptions::async_io`, or otherwise run with readahead effectively disabled. Affects scans using `ReadOptions::prefix_same_as_start` together with `ReadOptions::auto_readahead_size` (enabled by default); memory-mapped reads are unaffected.
### Performance Improvements
* `DB::MultiGetEntityLazy()` now reads all keys of a batch under a single shared per-column-family SuperVersion pin, and a single `LazyWideColumnsBatch::MultiResolve()` coalesces the resulting blob reads across keys: one read per blob file (and one per SST for embedded blobs), for whole-column and byte-range reads alike, instead of reading each key's blobs separately.
## 11.10.0 (08/28/2026)
### New Features
* Added `BlockBasedTableOptions::optimize_key_common_prefix` as part of new `format_version=8`. When enabled, each data block and index block stores the common user-key prefix of its keys once instead of at every restart point, reducing block size and block-cache footprint for prefix-heavy data and speeding up Seek for bytewise-ordered keys.
+1
View File
@@ -591,6 +591,7 @@ ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
FOLLY_DEPENDENT_HEADERS := \
include/rocksdb/coro_db.h \
include/rocksdb/coro_external_table.h \
include/rocksdb/utilities/coro_stackable_db.h
# Some headers like in port/ are platform-specific
DEV_HEADERS_TO_CHECK := $(filter-out $(FOLLY_DEPENDENT_HEADERS), $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/'))
+77 -11
View File
@@ -35,6 +35,49 @@ from util import ColorString
# tests to export as libraries for inclusion in other projects
_EXPORTED_TEST_LIBS = ["env_basic_test"]
_TEST_LIB_HEADERS = [
"db/db_test_util.h",
"db/db_with_timestamp_test_util.h",
"table/mock_table.h",
"test_util/mock_time_env.h",
"test_util/secondary_cache_test_util.h",
"test_util/simple_external_table_factory.h",
"test_util/testharness.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h",
"utilities/agg_merge/test_agg_merge.h",
"utilities/cassandra/test_utils.h",
]
_TEST_HEADERS = {
"configurable_test": ["options/configurable_test.h"],
"persistent_cache_test": ["utilities/persistent_cache/persistent_cache_test.h"],
"point_lock_manager_test": [
"utilities/transactions/lock/point/point_lock_manager_test.h",
],
"transaction_test": ["utilities/transactions/transaction_test.h"],
}
_STRESS_LIB_HEADERS = [
"db_stress_tool/db_stress_common.h",
"db_stress_tool/db_stress_compaction_service.h",
"db_stress_tool/db_stress_compression_manager.h",
"db_stress_tool/db_stress_driver.h",
"db_stress_tool/db_stress_filters.h",
"db_stress_tool/db_stress_listener.h",
"db_stress_tool/db_stress_shared_state.h",
"db_stress_tool/db_stress_test_base.h",
"db_stress_tool/db_stress_wide_merge_operator.h",
"db_stress_tool/expected_state.h",
"db_stress_tool/expected_value.h",
"db_stress_tool/multi_ops_txns_stress.h",
"test_util/simple_external_table_factory.h",
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/trace_analyzer_tool.h"
]
# Parse src.mk files as a Dictionary of
# VAR_NAME => list of files
def parse_src_mk(repo_path):
@@ -183,27 +226,34 @@ def generate_buck(repo_path, deps_map):
":rocksdb_lib",
],
)
# rocksdb_test_lib
BUCK.add_library(
"rocksdb_test_lib",
test_lib_sources = (
src_mk.get("MOCK_LIB_SOURCES", [])
+ src_mk.get("TEST_LIB_SOURCES", [])
+ src_mk.get("EXP_LIB_SOURCES", [])
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
)
# Older release source lists may not compile every test helper yet.
test_lib_headers = [
header for header in _TEST_LIB_HEADERS
if header[:-2] + ".cc" in test_lib_sources
]
# rocksdb_test_lib
BUCK.add_library(
"rocksdb_test_lib",
test_lib_sources,
[":rocksdb_lib"],
extra_test_libs=True,
headers=test_lib_headers,
)
# rocksdb_with_faiss_test_lib
BUCK.add_library(
"rocksdb_with_faiss_test_lib",
src_mk.get("MOCK_LIB_SOURCES", [])
+ src_mk.get("TEST_LIB_SOURCES", [])
+ src_mk.get("EXP_LIB_SOURCES", [])
+ src_mk.get("ANALYZER_LIB_SOURCES", []),
test_lib_sources,
deps=[
":rocksdb_with_faiss_lib",
],
extra_test_libs=True,
headers=test_lib_headers,
)
# rocksdb_tools_lib
BUCK.add_library(
@@ -212,6 +262,12 @@ def generate_buck(repo_path, deps_map):
+ src_mk.get("ANALYZER_LIB_SOURCES", [])
+ ["test_util/testutil.cc"],
[":rocksdb_lib"],
headers=[
"test_util/testutil.h",
"tools/block_cache_analyzer/block_cache_trace_analyzer.h",
"tools/simulated_hybrid_file_system.h",
"tools/trace_analyzer_tool.h",
],
)
# rocksdb_cache_bench_tools_lib
BUCK.add_library(
@@ -225,12 +281,19 @@ def generate_buck(repo_path, deps_map):
src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
[":rocksdb_lib"],
)
stress_sources = (
src_mk.get("ANALYZER_LIB_SOURCES", [])
+ src_mk.get("STRESS_LIB_SOURCES", [])
+ ["test_util/testutil.cc"]
)
# rocksdb_stress_lib
BUCK.add_rocksdb_library(
"rocksdb_stress_lib",
src_mk.get("ANALYZER_LIB_SOURCES", [])
+ src_mk.get("STRESS_LIB_SOURCES", [])
+ ["test_util/testutil.cc"],
stress_sources,
headers=[
header for header in _STRESS_LIB_HEADERS
if header[:-2] + ".cc" in stress_sources
],
)
# ldb binary
BUCK.add_binary(
@@ -353,6 +416,7 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":" + test_library]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
else:
if with_faiss:
@@ -361,6 +425,7 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":rocksdb_with_faiss_test_lib"]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
else:
BUCK.register_test(
@@ -368,6 +433,7 @@ def generate_buck(repo_path, deps_map):
test_src,
deps=json.dumps(deps["extra_deps"] + [":rocksdb_test_lib"]),
extra_compiler_flags=json.dumps(deps["extra_compiler_flags"]),
source_headers=_TEST_HEADERS.get(test, []),
)
BUCK.export_file("tools/db_crashtest.py")
BUCK.export_file("tools/fault_injection_log_parser.py")
+6 -1
View File
@@ -162,12 +162,17 @@ add_c_test_wrapper()
).encode("utf-8")
)
def register_test(self, test_name, src, deps, extra_compiler_flags):
def register_test(self, test_name, src, deps, extra_compiler_flags, source_headers):
with open(self.path, "ab") as targets_file:
targets_file.write(
targets_cfg.unittests_template.format(
test_name=test_name,
test_cc=str(src),
source_headers=(
f" source_headers=[{pretty_list(source_headers, 16)}],\n"
if source_headers
else ""
),
deps=deps,
extra_compiler_flags=extra_compiler_flags,
).encode("utf-8")
+1 -1
View File
@@ -30,7 +30,7 @@ cpp_binary_wrapper(name="{name}", srcs=[{srcs}], deps=[{deps}], extra_preprocess
unittests_template = """
cpp_unittest_wrapper(name="{test_name}",
srcs=["{test_cc}"],
deps={deps},
{source_headers} deps={deps},
extra_compiler_flags={extra_compiler_flags})
"""
@@ -142,6 +142,11 @@ uint64_t rocksdb_table_properties_column_family_id(
return props->rep.column_family_id;
}
uint64_t rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props) {
return props->rep.oldest_ancestor_time;
}
uint64_t rocksdb_table_properties_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.creation_time;
@@ -172,11 +177,6 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
return props->rep.fast_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props) {
return props->rep.external_sst_file_global_seqno_offset;
}
uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props) {
return props->rep.tail_start_offset;
@@ -102,6 +102,10 @@ rocksdb_table_properties_fixed_key_len(const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_column_family_id(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_creation_time(const rocksdb_table_properties_t* props);
@@ -122,10 +126,6 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_fast_compression_estimated_data_size(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props);
extern ROCKSDB_LIBRARY_API uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props);
@@ -741,6 +741,16 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
return opt->rep.write_dbid_to_manifest;
}
void rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.track_options_file_number_in_manifest = v;
}
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt) {
return opt->rep.track_options_file_number_in_manifest;
}
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.write_identity_file = v;
@@ -2057,16 +2067,6 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
return opt->rep.ingest_behind;
}
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.write_global_seqno = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.write_global_seqno;
}
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.verify_checksums_before_ingest = v;
@@ -204,6 +204,14 @@ rocksdb_options_set_background_close_inactive_wals(rocksdb_options_t* opt,
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_background_close_inactive_wals(rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_prefix_seek_opt_in_only(
rocksdb_options_t* opt, unsigned char v);
@@ -816,14 +824,6 @@ extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_ingestexternalfileoptions_get_ingest_behind(
rocksdb_ingestexternalfileoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt);
extern ROCKSDB_LIBRARY_API void
rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v);
+6 -6
View File
@@ -556,12 +556,6 @@ static void rocksdb_roundtrip_test_rocksdb_ingestexternalfileoptions(void) {
rocksdb_ingestexternalfileoptions_set_verify_file_checksum(obj, 0);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_verify_file_checksum(obj) == 0);
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 1);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 1);
rocksdb_ingestexternalfileoptions_set_write_global_seqno(obj, 0);
CheckCondition(
rocksdb_ingestexternalfileoptions_get_write_global_seqno(obj) == 0);
rocksdb_ingestexternalfileoptions_destroy(obj);
}
@@ -919,6 +913,12 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 1);
rocksdb_options_set_track_and_verify_wals(obj, 0);
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 0);
rocksdb_options_set_track_options_file_number_in_manifest(obj, 1);
CheckCondition(
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 1);
rocksdb_options_set_track_options_file_number_in_manifest(obj, 0);
CheckCondition(
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 0);
rocksdb_options_set_two_write_queues(obj, 1);
CheckCondition(rocksdb_options_get_two_write_queues(obj) == 1);
rocksdb_options_set_two_write_queues(obj, 0);
+2 -1
View File
@@ -276,7 +276,8 @@ struct KeyGen {
// location, so we fix hash bits starting at the bottom of that word.
auto hi_hash = uint64_t{0x9e3779b97f4a7c13U} ^
(key_hash << 1 << (FLAGS_degenerate_hash_bits - 1));
uint64_t un_hi, un_lo;
uint64_t un_hi;
uint64_t un_lo;
BijectiveUnhash2x64(hi_hash, key_hash, &un_hi, &un_lo);
un_lo ^= BitwiseAnd(FLAGS_seed, INT32_MAX);
EncodeFixed64(key_data, un_lo);
+7 -2
View File
@@ -32,9 +32,9 @@ class CacheReservationManager {
// for an incremental amount of memory used (i.e, incremental_memory_used)
class CacheReservationHandle {
public:
virtual ~CacheReservationHandle() {}
virtual ~CacheReservationHandle() = default;
};
virtual ~CacheReservationManager() {}
virtual ~CacheReservationManager() = default;
virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
// TODO(hx235): replace the usage of
// `UpdateCacheReservation(memory_used_delta, increase)` with
@@ -235,6 +235,11 @@ class ConcurrentCacheReservationManager
cache_res_mgr_ = cache_res_mgr;
cache_res_handle_ = std::move(cache_res_handle);
}
// No copying allowed
CacheReservationHandle(const CacheReservationHandle&) = delete;
CacheReservationHandle& operator=(const CacheReservationHandle&) = delete;
CacheReservationHandle(CacheReservationHandle&&) = delete;
CacheReservationHandle& operator=(CacheReservationHandle&&) = delete;
~CacheReservationHandle() override {
std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_);
+2 -2
View File
@@ -407,8 +407,8 @@ TEST(CacheReservationHandleTest, HandleTest) {
std::size_t mem_used = 0;
const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry;
const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1,
handle_2;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1;
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_2;
// To test consecutive CacheReservationManager::MakeCacheReservation works
// correctly in terms of returning the handle as well as updating cache
+12 -3
View File
@@ -975,7 +975,8 @@ void FixedHyperClockTable::Erase(const UniqueId64x2& hashed_key) {
// Could be multiple entries in rare cases. Erase them all.
// Optimistically increment acquire counter
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
SlotMeta old_meta, meta;
SlotMeta old_meta;
SlotMeta meta;
h->meta.Apply(add_acquire, &old_meta, &meta);
// Check if it's an entry visible to lookups
if (meta.IsVisible()) {
@@ -1738,7 +1739,8 @@ inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
// Must be at least something to match
assert(hashed_key || shift > 0);
SlotMeta old_meta, new_meta;
SlotMeta old_meta;
SlotMeta new_meta;
// (Optimistically) increment acquire counter.
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
h.meta.Apply(add_acquire, &old_meta, &new_meta);
@@ -1856,6 +1858,12 @@ class AutoHyperClockTable::ChainRewriteLock {
assert(saved_head_.IsLocked());
}
// No copying allowed
ChainRewriteLock(const ChainRewriteLock&) = delete;
ChainRewriteLock& operator=(const ChainRewriteLock&) = delete;
ChainRewriteLock(ChainRewriteLock&&) = delete;
ChainRewriteLock& operator=(ChainRewriteLock&&) = delete;
~ChainRewriteLock() {
if (!IsEnd()) {
// Release lock
@@ -3122,7 +3130,8 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
if (probably_equal) {
// Increment acquire counter for definitive check
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
SlotMeta old_meta, new_meta;
SlotMeta old_meta;
SlotMeta new_meta;
h->meta.Apply(add_acquire, &old_meta, &new_meta);
// Check if it's a referencable (sharable) entry
if (LIKELY(old_meta.IsShareable())) {
+4
View File
@@ -29,6 +29,10 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
const CompressedSecondaryCacheResultHandle&) = delete;
CompressedSecondaryCacheResultHandle& operator=(
const CompressedSecondaryCacheResultHandle&) = delete;
CompressedSecondaryCacheResultHandle(CompressedSecondaryCacheResultHandle&&) =
delete;
CompressedSecondaryCacheResultHandle& operator=(
CompressedSecondaryCacheResultHandle&&) = delete;
bool IsReady() override { return true; }
+2 -1
View File
@@ -508,7 +508,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
std::string str1 = rnd.RandomString(1001);
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length()));
item1.release(); // Appease clang-analyze "potential memory leak"
// Appease clang-analyze "potential memory leak"
[[maybe_unused]] TestItem* released_item1 = item1.release();
Cache::Handle* handle;
handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW);
+2 -1
View File
@@ -1366,7 +1366,8 @@ TEST_P(BasicSecondaryCacheTest, BasicFailTest) {
// str1.length()).IsInvalidArgument());
ASSERT_OK(
cache->Insert(k1.AsSlice(), item1.get(), GetHelper(), str1.length()));
item1.release(); // Appease clang-analyze "potential memory leak"
// Appease clang-analyze "potential memory leak"
[[maybe_unused]] TestItem* released_item1 = item1.release();
Cache::Handle* handle;
handle = cache->Lookup(k2.AsSlice(), nullptr, /*context*/ this,
+8 -2
View File
@@ -90,7 +90,7 @@ class CacheShardBase {
class ShardedCacheBase : public Cache {
public:
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
virtual ~ShardedCacheBase() = default;
~ShardedCacheBase() override = default;
int GetNumShardBits() const;
uint32_t GetNumShards() const;
@@ -143,7 +143,13 @@ class ShardedCache : public ShardedCacheBase {
sizeof(CacheShard) * GetNumShards()))),
destroy_shards_in_dtor_(false) {}
virtual ~ShardedCache() {
// No copying allowed
ShardedCache(const ShardedCache&) = delete;
ShardedCache& operator=(const ShardedCache&) = delete;
ShardedCache(ShardedCache&&) = delete;
ShardedCache& operator=(ShardedCache&&) = delete;
~ShardedCache() override {
if (destroy_shards_in_dtor_) {
ForEachShard([](CacheShard* cs) { cs->~CacheShard(); });
}
+2 -2
View File
@@ -36,7 +36,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
#endif
}
~TieredSecondaryCache() override {}
~TieredSecondaryCache() override = default;
const char* Name() const override { return "TieredSecondaryCache"; }
@@ -75,7 +75,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
class ResultHandle : public SecondaryCacheResultHandle {
public:
~ResultHandle() override {}
~ResultHandle() override = default;
bool IsReady() override {
if (inner_handle_ && inner_handle_->IsReady()) {
+1 -1
View File
@@ -241,7 +241,7 @@ class FullTypedCacheHelperFns : public BasicTypedCacheHelperFns<TValue> {
if (source != CacheTier::kVolatileTier) {
return Status::InvalidArgument();
}
if constexpr (sizeof(TCreateContext) > 0) {
if constexpr (!std::is_empty_v<TCreateContext>) {
TCreateContext* tcontext = static_cast<TCreateContext*>(context);
tcontext->Create(&value, out_charge, data, type, allocator);
} else {
+104
View File
@@ -28,6 +28,98 @@ A behavior change is not automatically a defect. Report a finding when the
intent, documentation, downstream-impact analysis, or mitigation has a concrete
gap or ambiguity that could lead to an unintended compatibility regression.
## DB Mutex Review Perspective
For every non-trivial change, determine whether the changed code, or any
transitive callee it adds or changes, can run while `DBImpl::mutex_` is held.
State the result explicitly, including when the DB mutex is not affected.
`DBImpl::mutex_` is the DB-wide metadata and coordination mutex. It protects
cross-object invariants involving `ColumnFamilyData`, memtable and
`SuperVersion` publication, `VersionSet`, snapshots, flush/compaction queues,
background-job state, and file lifetime. It is shared by all column families
and can be a hot lock. It is not the filesystem `db_lock_`, and it should not
replace data-path synchronization already provided by `WriteThread`,
`wal_write_mutex_`, atomics, referenced `SuperVersion`s, or component-local
synchronization.
When a DB-mutex critical section is affected:
### Locking and correctness
- Trace the full call path and mark the exact lock and unlock boundaries. Do
not inspect only the lines that directly acquire the mutex.
- State the locking contract. Helpers that require the lock should document it
and normally use `mutex_.AssertHeld()`. Check whether each caller expects the
lock to be held on return.
- Treat calls that can release and re-acquire the mutex as concurrency
boundaries. Examples include `VersionSet::LogAndApply()`, condition-variable
waits, write-thread coordination, and notification helpers. Revalidate state
inspected before such a call.
- Keep atomic metadata transitions under the mutex. Examples include moving a
memtable between mutable and immutable state, reserving compaction inputs,
installing a `Version` or `SuperVersion`, updating scheduling counters and
queues, and changing file-lifetime protection.
- For expensive work, prefer a prepare/run/install structure: reserve and pin
the required state while locked, do CPU or I/O work while unlocked, then
re-lock, validate, and publish. Before unlocking, copy mutable options and
acquire every required object reference or lifetime reservation.
- Check object lifetime across every unlocked region. Do not use a raw
`ColumnFamilyData*`, `Version*`, memtable, file metadata pointer, or DB-owned
container element after unlocking unless an explicit reference,
`SuperVersion`, job object, or pending-output reservation keeps it alive.
- Preserve the snapshot/input-selection boundary. Flush and compaction must not
miss a snapshot whose visible key versions they may remove. Missing a newly
created snapshot is a correctness bug; retaining a recently released
snapshot is conservative but safe.
- Never invoke untrusted or re-entrant code while holding the mutex. The mutex
is non-reentrant, so a callback into RocksDB can self-deadlock.
- Check lock ordering. `options_mutex_` is acquired before the DB mutex. When
both the DB mutex and `wal_write_mutex_` are needed, the DB mutex is acquired
first. Establish and document the order for other locks.
- Keep cleanup outside the critical section when possible. File deletion,
`JobContext::Clean()`, expensive `SuperVersion` destruction, logging, and
listener callbacks should normally run after protected state is detached or
snapshotted.
- Do not add the DB mutex to steady-state point reads or the normal write data
phase without strong justification. Reads normally use a referenced
`SuperVersion`; writes use `WriteThread` and component-specific
synchronization.
### Critical-section performance
- Compare the CPU and I/O performed under the mutex before and after the
change. Include call frequency, loop bounds, input-size scaling, hidden work
in callees, and worst-case cost.
- Account for filesystem or network I/O, waits, callbacks, logging and
formatting, allocation, container growth, copies, sorting, scans, reference
release and destructors, cache misses, and repeated metadata lookups.
- Do not allow a significant regression in mutex-held work without clear
justification, hold-time or contention measurements, and representative
benchmarks.
- Treat unavoidable work under the mutex as hot-path code and keep it highly
optimized. Precompute, allocate, copy, and perform I/O outside the lock;
remove repeated scans and lookups; cache or batch work where practical; and
keep locked operations O(1) or tightly bounded when possible.
- Treat new blocking I/O, waits, callbacks into user or plugin code, unbounded
loops, or work that scales with DB size, files, column families, snapshots,
or pending jobs as a blocker unless it is unavoidable and supported by strong
measurements and explicit design justification.
### Validation and evidence
- Test unlock windows with sync points rather than sleeps. Cover relevant races
with memtable switches, snapshot create/release, column-family drop,
flush/compaction install, shutdown, and background errors. Run TSAN where
appropriate.
- Distinguish wait time from hold time. `PerfContext::db_mutex_lock_nanos` and
`DB_MUTEX_WAIT_MICROS` primarily expose acquisition wait; use contention
profiles or scoped instrumentation to find long critical sections.
- Report affected critical sections, before/after cost, workload assumptions,
optimization choices, and remaining risk. If exact measurement is not
practical, provide a conservative static bound and do not claim no regression
without evidence.
IMPORTANT — Where the review goes: Your FINAL RESPONSE TEXT is the review.
The PR comment is generated verbatim from your final response, so the
complete, polished review MUST appear there in full. Do NOT end your turn with
@@ -307,6 +399,9 @@ in isolation may be a critical bug when you consider the full call chain.
- **Callee side effects**: Does the change call functions that mutate shared
state (counters, seqnos, metadata)? Are the mutations correct for the new
calling context? (Use the callee-chain analysis from context.md)
- Apply the DB Mutex Review Perspective above to locking contracts, unlocked
object lifetimes, stale-state validation, lock ordering, and snapshot
boundaries.
#### Agent: Cross-Component & Adversarial Reviewer
@@ -410,6 +505,9 @@ Write findings to `findings-caller-audit.md`.
- Loop optimization opportunities
- Branch prediction (LIKELY/UNLIKELY)
- Zero-overhead design verification
- Apply the DB Mutex Review Perspective above, with special focus on transitive
CPU and I/O, before/after cost, critical-section hold time, and benchmark
evidence
#### Agent: API & Compatibility Reviewer
- Public API backwards compatibility
@@ -593,6 +691,8 @@ Rules for this structure:
- [ ] Multi-component interactions (compaction, recovery, snapshots, iterators)
- [ ] Configuration dependencies and unexpected option combinations
- [ ] Potential pitfalls from caller-chain analysis
- [ ] DB-mutex reachability checked through transitive callees; affected lock
and unlock boundaries documented
### Review Phase (verified by agents)
- [ ] Database semantics preserved (snapshot isolation, key ordering)
@@ -606,6 +706,10 @@ Rules for this structure:
- [ ] Code follows RocksDB style conventions
- [ ] Behavioral contracts with upstream callers preserved
- [ ] All callers enumerated with parameter range table
- [ ] Before/after CPU and I/O under the DB mutex compared, with call frequency,
scaling, hidden callee work, and worst-case cost included
- [ ] No significant DB-mutex-held work regression; unavoidable work is highly
optimized, bounded, and supported by measurements when material
## Output Structure
Your **final response text is the primary output** — it becomes the PR comment
+106
View File
@@ -0,0 +1,106 @@
# RocksDB Diff and Pull Request Summary Guidance
This guidance applies to both internal Phabricator diff summaries and external
GitHub pull request summaries for changes under `fbcode/internal_repo_rocksdb/`.
## Goal
Write each diff or pull request summary for a reviewer who has not read the
patch. The summary must explain the behavioral story and the evidence for it,
not merely inventory the edited files or symbols.
Every new or updated diff or pull request summary must clearly cover:
1. Why the change is required.
2. What the previous behavior was, including the failure mode or limitation.
3. What the new behavior is.
4. What changed in the implementation.
5. Safety boundaries, compatibility, and unsupported or intentionally deferred
cases.
6. Tests performed and known limitations of the validation.
Use explicit headings for a broad, subtle, or high-risk change. A small,
focused change may combine these points into a few paragraphs, but none may be
left implicit when it affects a review decision.
## What Good RocksDB Summaries Make Concrete
- Name the exact conditions that activate a bug or behavior: relevant options,
call ordering, storage mode, concurrency mode, file format, platform, or
workload shape. Also state important cases that are not affected.
- Explain the causal chain from the old behavior to its externally observable
result. For correctness fixes, distinguish a symptom from the underlying
invariant violation.
- Describe the new contract before implementation mechanics. Then identify the
essential implementation choices and why they are sufficient.
- Call out API, ABI, serialized-format, behavioral, and performance
compatibility where relevant. For additive persisted fields, state how older
data is interpreted and whether older readers remain supported.
- State safety boundaries precisely: defaults, feature gates, fallback paths,
ownership and lifetime constraints, memory-ordering assumptions, and cases
deliberately postponed to a follow-up.
- Support performance claims with a baseline, workload and configuration,
measurement method, and results. Report meaningful tradeoffs, not only the
winning metric.
- Make the test plan evidence-based. Name the tests and configurations, say
what behavior each proves, and report failures, timeouts, untested platforms,
or stale pre-rebase results honestly. For a regression test, say how it fails
without the fix when that was verified.
- Separate incidental cleanup or a pre-existing test fix from the primary
change, and explain why it belongs in the diff.
Avoid vague claims such as "improves performance," "fixes an issue," or "tests
pass." Avoid copying the patch into prose, exhaustive file-by-file narration,
raw command output, and claims broader than the evidence.
## Suggested Structure
Adapt this structure to the change; do not keep empty or irrelevant sections.
```text
Summary:
Why the change is required:
<User or system impact, and why the current behavior is inadequate.>
Previous behavior:
<Old contract or mechanism, triggering conditions, and observable result.>
New behavior:
<New contract and externally visible effect.>
Implementation:
<Essential design and code changes; omit routine edit-by-edit narration.>
Safety, compatibility, and limitations:
<Defaults, unaffected paths, compatibility, unsupported cases, and follow-ups.>
Test Plan:
<Exact tests/benchmarks, configurations, what they demonstrate, and gaps.>
```
For a focused bug fix, a cohesive narrative can be clearer than six headings:
problem and root cause, trigger and unaffected cases, fix, then quantified or
counterfactual validation. For a multi-part change, labeled bullets such as
`Fix`, `Add`, and `Change` can make the implementation and review surface easy
to scan.
## Reference Pull Request Summaries
- [PR #15216](https://github.com/facebook/rocksdb/pull/15216) is a strong
focused bug-fix example. It traces stale iterator state to readahead behavior
and dropped keys, identifies the exact prerequisites and unaffected mmap
case, gives before/after measurements, and explains how the regression test
fails when the fix is reverted.
- [PR #15218](https://github.com/facebook/rocksdb/pull/15218) is a concise
example of explicit `Why`, `Previous behavior`, `New behavior`,
`Implementation`, and `Safety boundaries and unsupported cases` sections.
- Peter Dillinger's [PR #15231](https://github.com/facebook/rocksdb/pull/15231)
is a strong recent multi-part example. Its `Fix`/`Add`/`Change` bullets
separate the behaviors, its compatibility sentence explains how old files
read back, and its test plan maps each new persisted property to concrete
coverage while explaining an adjacent test adjustment.
Use these pull requests as models for reasoning, specificity, and evidence. Do
not copy their length or headings mechanically; scale the summary to the risk
and complexity of the change.
+7
View File
@@ -25,6 +25,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
crash_test_with_multiops_wup_txn \
crash_test_with_optimistic_txn \
crash_test_with_tiered_storage \
crash_test_with_full_external_table \
liveness_crash_test \
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
@@ -36,6 +37,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
blackbox_crash_test_with_multiops_wup_txn \
blackbox_crash_test_with_optimistic_txn \
blackbox_crash_test_with_tiered_storage \
blackbox_crash_test_with_full_external_table \
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
whitebox_crash_test_with_wc_txn whitebox_crash_test_with_wp_txn \
whitebox_crash_test_with_wup_txn \
@@ -86,6 +88,8 @@ crash_test_with_tiered_storage: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) whitebox_crash_test_with_tiered_storage
$(CRASHTEST_MAKE) blackbox_crash_test_with_tiered_storage
crash_test_with_full_external_table: blackbox_crash_test_with_full_external_table
crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wc_txn
@@ -132,6 +136,9 @@ blackbox_crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_full_external_table: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --test_full_external_table blackbox $(CRASH_TEST_EXT_ARGS)
blackbox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --optimistic_txn blackbox $(CRASH_TEST_EXT_ARGS)
+7
View File
@@ -42,6 +42,13 @@ class ArenaWrappedDBIter : public Iterator {
std::unique_ptr<ColumnFamilyData, ColumnFamilyDataUnrefDeleter>;
public:
ArenaWrappedDBIter() = default;
// No copying allowed
ArenaWrappedDBIter(const ArenaWrappedDBIter&) = delete;
ArenaWrappedDBIter& operator=(const ArenaWrappedDBIter&) = delete;
ArenaWrappedDBIter(ArenaWrappedDBIter&&) = delete;
ArenaWrappedDBIter& operator=(ArenaWrappedDBIter&&) = delete;
~ArenaWrappedDBIter() override {
if (db_iter_ != nullptr) {
DestroyDBIter();
+2
View File
@@ -24,6 +24,8 @@ class AttributeGroupIteratorImpl : public AttributeGroupIterator {
AttributeGroupIteratorImpl(const AttributeGroupIteratorImpl&) = delete;
AttributeGroupIteratorImpl& operator=(const AttributeGroupIteratorImpl&) =
delete;
AttributeGroupIteratorImpl(AttributeGroupIteratorImpl&&) = delete;
AttributeGroupIteratorImpl& operator=(AttributeGroupIteratorImpl&&) = delete;
bool Valid() const override { return impl_.Valid(); }
void SeekToFirst() override { impl_.SeekToFirst(); }
+2
View File
@@ -68,6 +68,8 @@ class BlobFileBuilder {
BlobFileBuilder(const BlobFileBuilder&) = delete;
BlobFileBuilder& operator=(const BlobFileBuilder&) = delete;
BlobFileBuilder(BlobFileBuilder&&) = delete;
BlobFileBuilder& operator=(BlobFileBuilder&&) = delete;
~BlobFileBuilder();
+88 -24
View File
@@ -38,12 +38,14 @@ BlobFileCache::BlobFileCache(Cache* cache,
}
Status BlobFileCache::GetBlobFileReader(
const ReadOptions& read_options, uint64_t blob_file_number,
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
CacheHandleGuard<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry) {
assert(blob_file_reader);
assert(blob_file_reader->IsEmpty());
const uint64_t blob_file_number = blob_file.file_number;
// NOTE: sharing same Cache with table_cache
const Slice key = GetSliceForKey(&blob_file_number);
@@ -74,18 +76,8 @@ Status BlobFileCache::GetBlobFileReader(
std::unique_ptr<BlobFileReader> reader;
{
assert(file_options_);
Status s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/false, &reader);
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
reader.reset();
s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
/*skip_footer_validation=*/true, &reader);
}
Status s = OpenBlobFileReader(read_options, blob_file,
allow_footer_skip_retry, &reader);
if (!s.ok()) {
ROCKS_LOG_WARN(immutable_options_->logger,
"BlobFileCache open failed for blob file %" PRIu64
@@ -106,7 +98,7 @@ Status BlobFileCache::GetBlobFileReader(
}
}
reader.release();
[[maybe_unused]] BlobFileReader* released_reader = reader.release();
*blob_file_reader = cache_.Guard(handle);
@@ -114,7 +106,7 @@ Status BlobFileCache::GetBlobFileReader(
}
Status BlobFileCache::OpenBlobFileReaderUncached(
const ReadOptions& read_options, uint64_t blob_file_number,
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
std::unique_ptr<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry) {
assert(blob_file_reader);
@@ -123,21 +115,77 @@ Status BlobFileCache::OpenBlobFileReaderUncached(
Statistics* const statistics = immutable_options_->stats;
RecordTick(statistics, NO_FILE_OPENS);
Status s = OpenBlobFileReader(read_options, blob_file,
allow_footer_skip_retry, blob_file_reader);
if (!s.ok()) {
RecordTick(statistics, NO_FILE_ERRORS);
}
return s;
}
void BlobFileCache::RegisterBlobFileChecksum(
const BlobFileOpenInfo& blob_file) {
assert(blob_file.file_checksum.empty() ==
blob_file.file_checksum_func_name.empty());
if (blob_file.file_checksum.empty()) {
return;
}
const Slice key = GetSliceForKey(&blob_file.file_number);
MutexLock cache_lock(&mutex_.Get(key));
{
MutexLock checksum_lock(&blob_file_checksums_mutex_);
BlobFileChecksum& checksum = blob_file_checksums_[blob_file.file_number];
checksum.value = blob_file.file_checksum.ToString();
checksum.function_name = blob_file.file_checksum_func_name.ToString();
}
// Readers opened while the file was active did not carry finalized metadata
// and may also have observed a footer-less file size.
cache_.get()->Erase(key);
}
void BlobFileCache::UnregisterBlobFileChecksum(uint64_t blob_file_number) {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
blob_file_checksums_.erase(blob_file_number);
}
Status BlobFileCache::OpenBlobFileReader(
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
bool allow_footer_skip_retry,
std::unique_ptr<BlobFileReader>* blob_file_reader) {
assert(file_options_);
assert(blob_file.file_checksum.empty() ==
blob_file.file_checksum_func_name.empty());
FileOptions file_options = *file_options_;
if (!blob_file.file_checksum.empty()) {
file_options.file_checksum = blob_file.file_checksum.ToString();
file_options.file_checksum_func_name =
blob_file.file_checksum_func_name.ToString();
} else {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
const auto it = blob_file_checksums_.find(blob_file.file_number);
if (it != blob_file_checksums_.end()) {
file_options.file_checksum = it->second.value;
file_options.file_checksum_func_name = it->second.function_name;
} else {
file_options.file_checksum.clear();
file_options.file_checksum_func_name.clear();
}
}
Status s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
*immutable_options_, read_options, file_options, column_family_id_,
blob_file_read_hist_, blob_file.file_number, io_tracer_,
/*skip_footer_validation=*/false, blob_file_reader);
if (!s.ok() && s.IsCorruption() && allow_footer_skip_retry) {
blob_file_reader->reset();
s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
*immutable_options_, read_options, file_options, column_family_id_,
blob_file_read_hist_, blob_file.file_number, io_tracer_,
/*skip_footer_validation=*/true, blob_file_reader);
}
if (!s.ok()) {
RecordTick(statistics, NO_FILE_ERRORS);
}
return s;
}
@@ -178,7 +226,7 @@ Status BlobFileCache::InsertBlobFileReader(
}
Status BlobFileCache::RefreshBlobFileReader(
uint64_t blob_file_number,
const BlobFileOpenInfo& blob_file,
std::unique_ptr<BlobFileReader>* blob_file_reader,
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader) {
assert(blob_file_reader);
@@ -186,10 +234,26 @@ Status BlobFileCache::RefreshBlobFileReader(
assert(cached_blob_file_reader);
assert(cached_blob_file_reader->IsEmpty());
const Slice key = GetSliceForKey(&blob_file_number);
const Slice key = GetSliceForKey(&blob_file.file_number);
MutexLock lock(&mutex_.Get(key));
TypedHandle* handle = cache_.Lookup(key);
if (blob_file.file_checksum.empty()) {
MutexLock checksum_lock(&blob_file_checksums_mutex_);
if (blob_file_checksums_.find(blob_file.file_number) !=
blob_file_checksums_.end()) {
// This reader can have been opened just before finalization registered
// checksum metadata. Never let such a racing refresh repopulate the
// cache.
if (handle) {
*cached_blob_file_reader = cache_.Guard(handle);
}
blob_file_reader->reset();
return Status::OK();
}
}
if (handle) {
BlobFileReader* const cached_reader = cache_.Value(handle);
assert(cached_reader != nullptr);
+35 -6
View File
@@ -6,9 +6,12 @@
#pragma once
#include <cinttypes>
#include <string>
#include <unordered_map>
#include "cache/typed_cache.h"
#include "db/blob/blob_file_reader.h"
#include "db/blob/blob_read_request.h"
#include "rocksdb/rocksdb_namespace.h"
#include "util/mutexlock.h"
@@ -31,21 +34,35 @@ class BlobFileCache {
BlobFileCache(const BlobFileCache&) = delete;
BlobFileCache& operator=(const BlobFileCache&) = delete;
BlobFileCache(BlobFileCache&&) = delete;
BlobFileCache& operator=(BlobFileCache&&) = delete;
// Returns a cached reader for `blob_file_number`, opening and caching it on
// miss. If `allow_footer_skip_retry` is true, a footer-validation corruption
// retries once without requiring a footer.
~BlobFileCache() = default;
// Returns a cached reader for `blob_file`, opening and caching it on miss.
// Checksum metadata is forwarded to the FileSystem through FileOptions when
// the file is opened. If `allow_footer_skip_retry` is true, a
// footer-validation corruption retries once without requiring a footer.
Status GetBlobFileReader(const ReadOptions& read_options,
uint64_t blob_file_number,
const BlobFileOpenInfo& blob_file,
CacheHandleGuard<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry = false);
// Opens a blob file reader without inserting it into the cache.
Status OpenBlobFileReaderUncached(
const ReadOptions& read_options, uint64_t blob_file_number,
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
std::unique_ptr<BlobFileReader>* blob_file_reader,
bool allow_footer_skip_retry = false);
// Makes finalized checksum metadata available to direct-write fallback reads
// whose pinned Version predates the blob-file addition. Any cached
// footer-less reader is evicted as part of the same per-file transition.
void RegisterBlobFileChecksum(const BlobFileOpenInfo& blob_file);
// Stops retaining checksum metadata once no direct-write fallback can refer
// to the file. Manifest-backed callers continue to provide it directly.
void UnregisterBlobFileChecksum(uint64_t blob_file_number);
// Inserts a freshly opened uncached reader unless another thread already
// cached the same blob file.
Status InsertBlobFileReader(
@@ -58,7 +75,7 @@ class BlobFileCache {
// reader instead so a racing refresh cannot reintroduce an older active-file
// view after the blob file grows.
Status RefreshBlobFileReader(
uint64_t blob_file_number,
const BlobFileOpenInfo& blob_file,
std::unique_ptr<BlobFileReader>* blob_file_reader,
CacheHandleGuard<BlobFileReader>* cached_blob_file_reader);
@@ -72,6 +89,11 @@ class BlobFileCache {
}
private:
Status OpenBlobFileReader(const ReadOptions& read_options,
const BlobFileOpenInfo& blob_file,
bool allow_footer_skip_retry,
std::unique_ptr<BlobFileReader>* blob_file_reader);
using CacheInterface =
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
using TypedHandle = CacheInterface::TypedHandle;
@@ -85,6 +107,13 @@ class BlobFileCache {
HistogramImpl* blob_file_read_hist_;
std::shared_ptr<IOTracer> io_tracer_;
struct BlobFileChecksum {
std::string value;
std::string function_name;
};
std::unordered_map<uint64_t, BlobFileChecksum> blob_file_checksums_;
mutable port::Mutex blob_file_checksums_mutex_;
static constexpr size_t kNumberOfMutexStripes = 1 << 7;
};
+21 -18
View File
@@ -115,13 +115,13 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
// First try: reader should be opened and put in cache
CacheHandleGuard<BlobFileReader> first;
const ReadOptions read_options;
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&first));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
ASSERT_NE(first.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -129,8 +129,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
// Second try: reader should be served from cache
CacheHandleGuard<BlobFileReader> second;
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&second));
ASSERT_OK(
blob_file_cache.GetBlobFileReader(read_options, blob_file, &second));
ASSERT_NE(second.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -163,6 +163,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
CacheHandleGuard<BlobFileReader> first;
CacheHandleGuard<BlobFileReader> second;
@@ -172,16 +173,15 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
// Disabling sync points to prevent infinite recursion
SyncPoint::GetInstance()->DisableProcessing();
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options,
blob_file_number, &second));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file,
&second));
ASSERT_NE(second.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
&first));
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file, &first));
ASSERT_NE(first.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
@@ -245,21 +245,22 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
const ReadOptions read_options;
std::unique_ptr<BlobFileReader> stale_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file_number, &stale_reader,
read_options, blob_file, &stale_reader,
/*allow_footer_skip_retry=*/true));
std::unique_ptr<BlobFileReader> initial_cached_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file_number, &initial_cached_reader,
read_options, blob_file, &initial_cached_reader,
/*allow_footer_skip_retry=*/true));
CacheHandleGuard<BlobFileReader> cached_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file_number, &initial_cached_reader, &cached_reader));
blob_file, &initial_cached_reader, &cached_reader));
ASSERT_NE(cached_reader.GetValue(), nullptr);
const uint64_t initial_file_size = cached_reader.GetValue()->GetFileSize();
@@ -269,20 +270,20 @@ TEST_F(BlobFileCacheTest, RefreshBlobFileReaderPrefersLargestObservedFileSize) {
std::unique_ptr<BlobFileReader> fresh_reader;
ASSERT_OK(blob_file_cache.OpenBlobFileReaderUncached(
read_options, blob_file_number, &fresh_reader,
read_options, blob_file, &fresh_reader,
/*allow_footer_skip_retry=*/true));
ASSERT_GT(fresh_reader->GetFileSize(), initial_file_size);
CacheHandleGuard<BlobFileReader> refreshed_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file_number, &fresh_reader, &refreshed_reader));
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &fresh_reader,
&refreshed_reader));
ASSERT_NE(refreshed_reader.GetValue(), nullptr);
ASSERT_GT(refreshed_reader.GetValue()->GetFileSize(), initial_file_size);
BlobFileReader* const largest_reader = refreshed_reader.GetValue();
CacheHandleGuard<BlobFileReader> preserved_reader;
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(
blob_file_number, &stale_reader, &preserved_reader));
ASSERT_OK(blob_file_cache.RefreshBlobFileReader(blob_file, &stale_reader,
&preserved_reader));
ASSERT_NE(preserved_reader.GetValue(), nullptr);
ASSERT_EQ(preserved_reader.GetValue(), largest_reader);
ASSERT_EQ(stale_reader.get(), nullptr);
@@ -312,12 +313,13 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
// Note: there is no blob file with the below number
constexpr uint64_t blob_file_number = 123;
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
CacheHandleGuard<BlobFileReader> reader;
const ReadOptions read_options;
ASSERT_TRUE(
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
.IsIOError());
ASSERT_EQ(reader.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
@@ -352,6 +354,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
&file_options, column_family_id,
blob_file_read_hist, nullptr /*IOTracer*/);
const BlobFileOpenInfo blob_file{blob_file_number, Slice(), Slice()};
// Insert into cache should fail since it has zero capacity and
// strict_capacity_limit is set
@@ -359,7 +362,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
const ReadOptions read_options;
ASSERT_TRUE(
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
blob_file_cache.GetBlobFileReader(read_options, blob_file, &reader)
.IsMemoryLimit());
ASSERT_EQ(reader.GetValue(), nullptr);
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
+4
View File
@@ -52,6 +52,8 @@ class SharedBlobFileMetaData {
SharedBlobFileMetaData(SharedBlobFileMetaData&&) = delete;
SharedBlobFileMetaData& operator=(SharedBlobFileMetaData&&) = delete;
~SharedBlobFileMetaData() = default;
uint64_t GetBlobFileSize() const;
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
@@ -110,6 +112,8 @@ class BlobFileMetaData {
BlobFileMetaData(BlobFileMetaData&&) = delete;
BlobFileMetaData& operator=(BlobFileMetaData&&) = delete;
~BlobFileMetaData() = default;
const std::shared_ptr<SharedBlobFileMetaData>& GetSharedMeta() const {
return shared_meta_;
}
+52 -19
View File
@@ -330,6 +330,11 @@ Status BlobFilePartitionManager::FinalizeBlobFile(
*addition =
BlobFileAddition(file_number, blob_count, total_blob_bytes,
std::move(checksum_method), std::move(checksum_value));
if (blob_file_cache_ != nullptr) {
const BlobFileOpenInfo blob_file{file_number, addition->GetChecksumValue(),
addition->GetChecksumMethod()};
blob_file_cache_->RegisterBlobFileChecksum(blob_file);
}
return Status::OK();
}
@@ -538,10 +543,11 @@ Status BlobFilePartitionManager::WriteBlob(
return Status::OK();
}
void BlobFilePartitionManager::RotateCurrentGeneration() {
void BlobFilePartitionManager::RotateCurrentGeneration(uint64_t memtable_id) {
MutexLock lock(&mutex_);
GenerationBatch batch;
batch.memtable_id = memtable_id;
batch.sealed_files = std::move(current_generation_sealed_files_);
current_generation_sealed_files_.clear();
@@ -564,8 +570,19 @@ void BlobFilePartitionManager::RotateCurrentGeneration() {
pending_generations_.emplace_back(std::move(batch));
}
BlobFilePartitionManager::GenerationBatch*
BlobFilePartitionManager::FindPendingGeneration(uint64_t memtable_id) {
for (auto& batch : pending_generations_) {
if (batch.memtable_id == memtable_id) {
return &batch;
}
}
return nullptr;
}
Status BlobFilePartitionManager::PrepareFlushAdditions(
const WriteOptions& write_options, size_t num_generations,
const WriteOptions& write_options,
const std::vector<uint64_t>& memtable_ids,
std::vector<BlobFileAddition>* additions,
std::vector<BlobFileGarbage>* garbages,
std::vector<std::vector<uint64_t>>* generation_blob_file_numbers) {
@@ -578,13 +595,13 @@ Status BlobFilePartitionManager::PrepareFlushAdditions(
}
MutexLock lock(&mutex_);
if (num_generations > pending_generations_.size()) {
return Status::Corruption(
"Missing blob direct write generation metadata for flush");
}
for (size_t i = 0; i < num_generations; ++i) {
GenerationBatch& batch = pending_generations_[i];
for (const uint64_t memtable_id : memtable_ids) {
GenerationBatch* const batch_ptr = FindPendingGeneration(memtable_id);
if (batch_ptr == nullptr) {
return Status::Corruption(
"Missing blob direct write generation metadata for flush");
}
GenerationBatch& batch = *batch_ptr;
while (!batch.deferred_files.empty()) {
DeferredFile deferred = std::move(batch.deferred_files.front());
batch.deferred_files.pop_front();
@@ -663,12 +680,22 @@ Status BlobFilePartitionManager::MarkBlobWriteAsGarbage(uint64_t file_number,
return Status::Corruption(message);
}
void BlobFilePartitionManager::CommitPreparedGenerations(
size_t num_generations) {
MutexLock lock(&mutex_);
while (num_generations-- > 0 && !pending_generations_.empty()) {
pending_generations_.pop_front();
void BlobFilePartitionManager::CommitFlushedGenerations(
uint64_t earliest_unflushed_memtable_id) {
std::vector<uint64_t> sealed_file_numbers;
{
MutexLock lock(&mutex_);
while (!pending_generations_.empty() &&
pending_generations_.front().memtable_id <
earliest_unflushed_memtable_id) {
for (const auto& sealed_file :
pending_generations_.front().sealed_files) {
sealed_file_numbers.push_back(sealed_file.addition.GetBlobFileNumber());
}
pending_generations_.pop_front();
}
}
RemoveFilePartitionMappings(sealed_file_numbers);
}
Status BlobFilePartitionManager::SyncAllOpenFiles(
@@ -753,6 +780,9 @@ void BlobFilePartitionManager::UnprotectSealedBlobFileNumbers(
// manifest-visible path. Evict it here so delayed protection does not
// leave an obsolete blob reader behind until DB close.
blob_file_cache_->Evict(file_number);
if (file_to_partition_.find(file_number) == file_to_partition_.end()) {
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
}
}
}
}
@@ -772,6 +802,10 @@ void BlobFilePartitionManager::RemoveFilePartitionMappings(
// this file before it was sealed. Drop it now so future manifest-visible
// reads reopen against the finalized on-disk size and footer state.
blob_file_cache_->Evict(file_number);
if (protected_blob_file_refs_.find(file_number) ==
protected_blob_file_refs_.end()) {
blob_file_cache_->UnregisterBlobFileChecksum(file_number);
}
}
}
}
@@ -809,9 +843,9 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
}
Status s;
const BlobFileOpenInfo blob_file{blob_idx.file_number(), Slice(), Slice()};
CacheHandleGuard<BlobFileReader> reader;
s = blob_file_cache->GetBlobFileReader(read_options, blob_idx.file_number(),
&reader,
s = blob_file_cache->GetBlobFileReader(read_options, blob_file, &reader,
/*allow_footer_skip_retry=*/true);
if (!s.ok()) {
return s;
@@ -836,7 +870,7 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache->OpenBlobFileReaderUncached(
read_options, blob_idx.file_number(), &fresh_reader,
read_options, blob_file, &fresh_reader,
/*allow_footer_skip_retry=*/true);
if (!s.ok()) {
return s;
@@ -850,8 +884,7 @@ Status BlobFilePartitionManager::ResolveBlobDirectWriteIndex(
if (s.ok()) {
blob_value->PinSelf(fresh_contents->data());
CacheHandleGuard<BlobFileReader> ignored;
blob_file_cache
->RefreshBlobFileReader(blob_idx.file_number(), &fresh_reader, &ignored)
blob_file_cache->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored)
.PermitUncheckedError();
}
+22 -14
View File
@@ -88,27 +88,31 @@ class BlobFilePartitionManager {
const Slice& key,
const WideColumns& columns) const;
// Move the current active partition files into the next immutable
// memtable-generation batch. Called from SwitchMemtable() while DB mutex is
// held. No I/O is performed here.
void RotateCurrentGeneration();
// Move the current active partition files into the generation of the
// memtable `memtable_id`, which just became immutable. Called from
// SwitchMemtable() while DB mutex is held. No I/O is performed here.
void RotateCurrentGeneration(uint64_t memtable_id);
// Seal the first `num_generations` queued immutable generations and return
// all blob additions and initial-garbage updates that must be registered
// with the matching flush. Generations stay queued until
// CommitPreparedGenerations() is called. If `generation_blob_file_numbers`
// is non-null, it receives the sealed blob file numbers for each prepared
// generation in the same FIFO order.
// Seal the queued generations of the memtables `memtable_ids` and return all
// blob additions and initial-garbage updates that must be registered with
// the flush of those memtables. Concurrent flushes pick different
// memtables, so generations are matched by memtable ID, not by queue
// position. Generations stay queued until CommitFlushedGenerations() removes
// them. If `generation_blob_file_numbers` is non-null, it receives the
// sealed blob file numbers for each memtable, in `memtable_ids` order.
Status PrepareFlushAdditions(const WriteOptions& write_options,
size_t num_generations,
const std::vector<uint64_t>& memtable_ids,
std::vector<BlobFileAddition>* additions,
std::vector<BlobFileGarbage>* garbages,
std::vector<std::vector<uint64_t>>*
generation_blob_file_numbers = nullptr);
// Remove the first `num_generations` prepared immutable generations after
// their blob-file additions and garbage edits were committed to MANIFEST.
void CommitPreparedGenerations(size_t num_generations);
// Remove the generations of the memtables older than
// `earliest_unflushed_memtable_id`, whose flush results are installed, and
// stop tracking their sealed files as manager-owned. A flush that finishes
// before an older one is installed with it, or rolled back if it fails, so
// the flush that prepared a generation cannot remove it on its own.
void CommitFlushedGenerations(uint64_t earliest_unflushed_memtable_id);
// In v1 flush-on-write mode each AddRecord flushes to the OS immediately, so
// there is nothing extra to do for the non-sync write path.
@@ -200,6 +204,8 @@ class BlobFilePartitionManager {
};
struct GenerationBatch {
// The memtable that became immutable when this generation was rotated.
uint64_t memtable_id = 0;
// Files that were still open when the memtable became immutable.
std::deque<DeferredFile> deferred_files;
// Files already sealed for this memtable generation.
@@ -208,6 +214,8 @@ class BlobFilePartitionManager {
// Clears all active-file bookkeeping from a partition after sealing.
void ResetPartitionState(Partition* partition);
// Returns the queued generation of `memtable_id`, or null.
GenerationBatch* FindPendingGeneration(uint64_t memtable_id);
// Marks a blob file as manager-owned until it is committed or abandoned.
void AddFilePartitionMapping(uint64_t file_number, uint32_t partition_idx);
// Stops tracking a blob file after open/seal failure.
+9 -7
View File
@@ -278,22 +278,24 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
return s;
}
FSReadRequest read_req;
read_req.offset = read_offset;
read_req.len = read_size;
if (file_reader->use_direct_io()) {
constexpr char* scratch = nullptr;
read_req.scratch = nullptr;
AlignedBufferAllocationContext direct_io_context{direct_io_buffer};
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
&direct_io_context, &dbg);
file_reader->Read(io_options, &read_req, &direct_io_context, &dbg);
} else {
buf->reset(new char[read_size]);
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
nullptr, &dbg);
read_req.scratch = buf->get();
file_reader->Read(io_options, &read_req, nullptr, &dbg);
}
s = std::move(read_req.status);
if (!s.ok()) {
return s;
}
*slice = read_req.result;
if (slice->size() != read_size) {
return Status::Corruption("Incomplete blob read from " +
+2
View File
@@ -53,6 +53,8 @@ class BlobFileReader {
BlobFileReader(const BlobFileReader&) = delete;
BlobFileReader& operator=(const BlobFileReader&) = delete;
BlobFileReader(BlobFileReader&&) = delete;
BlobFileReader& operator=(BlobFileReader&&) = delete;
~BlobFileReader();
+5 -6
View File
@@ -82,8 +82,7 @@ Status ReadAndVerifySimpleGen2BlobRecord(
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(opts, record_offset, record_size, &result, buf, nullptr,
&dbg);
s = file->Read(record_offset, record_size, opts, &result, buf, &dbg);
}
if (!s.ok()) {
return s;
@@ -171,8 +170,8 @@ Status ReadSimpleGen2BlobRange(const ReadOptions& read_options,
IODebugContext dbg;
Status s = file->PrepareIOOptions(read_options, opts, &dbg);
if (s.ok()) {
s = file->Read(opts, record_offset + range_offset, range_length, &result,
buf, nullptr, &dbg);
s = file->Read(record_offset + range_offset, range_length, opts, &result,
buf, &dbg);
}
if (!s.ok()) {
return s;
@@ -424,12 +423,12 @@ IOStatus WriteSimpleGen2BlobRecord(WritableFileWriter* file,
return io_s;
}
if (!payload.empty()) {
io_s = file->Append(opts, payload);
io_s = file->Append(payload, opts);
if (!io_s.ok()) {
return io_s;
}
}
return file->Append(opts, Slice(trailer.data(), trailer.size()));
return file->Append(Slice(trailer.data(), trailer.size()), opts);
}
} // namespace ROCKSDB_NAMESPACE
+4
View File
@@ -55,6 +55,10 @@ class BlobIndex {
BlobIndex(const BlobIndex&) = default;
BlobIndex& operator=(const BlobIndex&) = default;
BlobIndex(BlobIndex&&) = default;
BlobIndex& operator=(BlobIndex&&) = default;
~BlobIndex() = default;
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
+1 -1
View File
@@ -29,7 +29,7 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
Status s = file_->Read(next_byte_, static_cast<size_t>(size), IOOptions(),
slice, buf, nullptr);
next_byte_ += size;
if (!s.ok()) {
+2
View File
@@ -43,6 +43,8 @@ class BlobLogSequentialReader {
// No copying allowed
BlobLogSequentialReader(const BlobLogSequentialReader&) = delete;
BlobLogSequentialReader& operator=(const BlobLogSequentialReader&) = delete;
BlobLogSequentialReader(BlobLogSequentialReader&&) = delete;
BlobLogSequentialReader& operator=(BlobLogSequentialReader&&) = delete;
~BlobLogSequentialReader();
+6 -6
View File
@@ -40,7 +40,7 @@ Status BlobLogWriter::Sync(const WriteOptions& write_options) {
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Sync(opts, use_fsync_);
s = use_fsync_ ? dest_->Fsync(opts) : dest_->Sync(opts);
}
if (s.ok()) {
RecordTick(statistics_, BLOB_DB_BLOB_FILE_SYNCED);
@@ -58,7 +58,7 @@ Status BlobLogWriter::WriteHeader(const WriteOptions& write_options,
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(opts, Slice(str));
s = dest_->Append(Slice(str), opts);
}
if (s.ok()) {
block_offset_ += str.size();
@@ -92,7 +92,7 @@ Status BlobLogWriter::AppendFooter(const WriteOptions& write_options,
IOOptions opts;
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(opts, Slice(str));
s = dest_->Append(Slice(str), opts);
}
if (s.ok()) {
block_offset_ += str.size();
@@ -181,13 +181,13 @@ Status BlobLogWriter::EmitPhysicalRecord(const WriteOptions& write_options,
IOOptions opts;
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
if (s.ok()) {
s = dest_->Append(opts, Slice(headerbuf));
s = dest_->Append(Slice(headerbuf), opts);
}
if (s.ok()) {
s = dest_->Append(opts, key);
s = dest_->Append(key, opts);
}
if (s.ok()) {
s = dest_->Append(opts, val);
s = dest_->Append(val, opts);
}
if (do_flush_ && s.ok()) {
s = dest_->Flush(opts);
+2
View File
@@ -37,6 +37,8 @@ class BlobLogWriter {
// No copying allowed
BlobLogWriter(const BlobLogWriter&) = delete;
BlobLogWriter& operator=(const BlobLogWriter&) = delete;
BlobLogWriter(BlobLogWriter&&) = delete;
BlobLogWriter& operator=(BlobLogWriter&&) = delete;
~BlobLogWriter();
+15 -2
View File
@@ -14,6 +14,16 @@
namespace ROCKSDB_NAMESPACE {
// Non-owning metadata needed when opening a blob file for reading. The
// checksum slices must remain valid while this object is in use. A file without
// finalized checksum metadata, such as an active direct-write blob file, uses
// empty slices for both checksum fields.
struct BlobFileOpenInfo {
uint64_t file_number = 0;
Slice file_checksum;
Slice file_checksum_func_name;
};
// A read Blob request structure for use in BlobSource::MultiGetBlob and
// BlobFileReader::MultiGetBlob.
struct BlobReadRequest {
@@ -50,10 +60,13 @@ struct BlobReadRequest {
BlobReadRequest() = default;
BlobReadRequest(const BlobReadRequest& other) = default;
BlobReadRequest& operator=(const BlobReadRequest& other) = default;
BlobReadRequest(BlobReadRequest&& other) = default;
BlobReadRequest& operator=(BlobReadRequest&& other) = default;
~BlobReadRequest() = default;
};
using BlobFileReadRequests =
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
autovector<BlobReadRequest>>;
// A byte-range (partial) blob read request for the lazy blob-read multi
@@ -97,7 +110,7 @@ struct BlobRangeReadRequest {
};
using BlobFileRangeReadRequests =
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
std::tuple<BlobFileOpenInfo, uint64_t /* file_size */,
autovector<BlobRangeReadRequest>>;
} // namespace ROCKSDB_NAMESPACE
+30 -26
View File
@@ -50,9 +50,10 @@ Status AppendBlobRefreshRetryFailure(const Status& stale_status,
template <typename ReadFn>
Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
const ReadOptions& read_options,
uint64_t file_number, const ReadFn& read) {
const BlobFileOpenInfo& blob_file,
const ReadFn& read) {
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache->GetBlobFileReader(read_options, file_number,
Status s = blob_file_cache->GetBlobFileReader(read_options, blob_file,
&blob_file_reader);
if (!s.ok()) {
return s;
@@ -66,11 +67,11 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
const Status stale_status = s;
blob_file_reader.Reset();
blob_file_cache->Evict(file_number);
blob_file_cache->Evict(blob_file.file_number);
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache->OpenBlobFileReaderUncached(
read_options, file_number, &fresh_reader,
read_options, blob_file, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
return AppendBlobRefreshRetryFailure(stale_status, s);
@@ -83,7 +84,7 @@ Status ReadBlobWithReaderRetry(BlobFileCache* blob_file_cache,
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
return s;
}
@@ -201,7 +202,7 @@ Status BlobSource::PutBlobIntoCache(
const Status s = InsertEntryIntoCache(cache_key, blob->get(), &cache_handle,
Cache::Priority::BOTTOM);
if (s.ok()) {
blob->release();
[[maybe_unused]] BlobContents* released_blob = blob->release();
assert(cache_handle != nullptr);
*cached_blob =
@@ -272,9 +273,9 @@ Status BlobSource::InsertEntryIntoCache(const Slice& key, BlobContents* value,
}
Status BlobSource::GetBlob(const ReadOptions& read_options,
const Slice& user_key, uint64_t file_number,
uint64_t offset, uint64_t file_size,
uint64_t value_size,
const Slice& user_key,
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
CompressionType compression_type,
FilePrefetchBuffer* prefetch_buffer,
PinnableSlice* value, uint64_t* bytes_read) {
@@ -282,7 +283,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
Status s;
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
const CacheKey cache_key =
GetCacheKey(blob_file.file_number, file_size, offset);
CacheHandleGuard<BlobContents> blob_handle;
@@ -332,8 +334,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
uint64_t read_size = 0;
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
@@ -377,7 +378,8 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
}
Status BlobSource::GetBlobRange(const ReadOptions& read_options,
const Slice& user_key, uint64_t file_number,
const Slice& user_key,
const BlobFileOpenInfo& blob_file,
uint64_t offset, uint64_t file_size,
uint64_t value_size,
CompressionType compression_type,
@@ -394,7 +396,8 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
Status s;
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
const CacheKey cache_key =
GetCacheKey(blob_file.file_number, file_size, offset);
CacheHandleGuard<BlobContents> blob_handle;
@@ -435,8 +438,7 @@ Status BlobSource::GetBlobRange(const ReadOptions& read_options,
// case (offset/size mismatch surfacing as Corruption), not a payload
// checksum failure.
s = ReadBlobWithReaderRetry(
blob_file_cache_, read_options, file_number,
[&](BlobFileReader* reader) {
blob_file_cache_, read_options, blob_file, [&](BlobFileReader* reader) {
if (compression_type != reader->GetCompressionType()) {
return Status::Corruption(
"Compression type mismatch when reading blob");
@@ -827,7 +829,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
uint64_t total_bytes_read = 0;
uint64_t bytes_read_in_file = 0;
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
// sort blob_reqs_in_file by file offset.
std::sort(
blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
@@ -835,7 +837,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
return lhs.offset < rhs.offset;
});
MultiGetBlobFromOneFile(read_options, file_number, file_size,
MultiGetBlobFromOneFile(read_options, blob_file, file_size,
blob_reqs_in_file, &bytes_read_in_file);
total_bytes_read += bytes_read_in_file;
@@ -847,7 +849,7 @@ void BlobSource::MultiGetBlob(const ReadOptions& read_options,
}
void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
uint64_t file_number,
const BlobFileOpenInfo& blob_file,
uint64_t /*file_size*/,
autovector<BlobReadRequest>& blob_reqs,
uint64_t* bytes_read) {
@@ -865,6 +867,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
Mask cache_hit_mask = 0;
uint64_t total_bytes = 0;
const uint64_t file_number = blob_file.file_number;
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
if (blob_cache_) {
@@ -936,7 +939,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
}
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
&blob_file_reader);
if (!s.ok()) {
for (size_t i = 0; i < _blob_reqs.size(); ++i) {
@@ -976,7 +979,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
std::unique_ptr<BlobFileReader> fresh_reader;
s = blob_file_cache_->OpenBlobFileReaderUncached(
read_options, file_number, &fresh_reader,
read_options, blob_file, &fresh_reader,
/*allow_footer_skip_retry=*/false);
if (!s.ok()) {
for (const auto& blob_req : _blob_reqs) {
@@ -1037,7 +1040,7 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
if (install_fresh_reader) {
CacheHandleGuard<BlobFileReader> ignored_reader;
blob_file_cache_
->RefreshBlobFileReader(file_number, &fresh_reader, &ignored_reader)
->RefreshBlobFileReader(blob_file, &fresh_reader, &ignored_reader)
.PermitUncheckedError();
}
}
@@ -1103,7 +1106,7 @@ void BlobSource::MultiGetBlobRange(
uint64_t total_bytes_read = 0;
uint64_t bytes_read_in_file = 0;
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
for (auto& [blob_file, file_size, blob_reqs_in_file] : blob_reqs) {
// Sort by effective read offset so the file system layer can coalesce
// adjacent sub-range reads within one MultiRead.
std::sort(blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
@@ -1113,7 +1116,7 @@ void BlobSource::MultiGetBlobRange(
rhs.offset + rhs.range_offset;
});
MultiGetBlobRangeFromOneFile(read_options, file_number, file_size,
MultiGetBlobRangeFromOneFile(read_options, blob_file, file_size,
blob_reqs_in_file, &bytes_read_in_file);
total_bytes_read += bytes_read_in_file;
@@ -1126,7 +1129,7 @@ void BlobSource::MultiGetBlobRange(
}
void BlobSource::MultiGetBlobRangeFromOneFile(
const ReadOptions& read_options, uint64_t file_number,
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
uint64_t /*file_size*/, autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read) {
const size_t num_blobs = blob_reqs.size();
@@ -1140,6 +1143,7 @@ void BlobSource::MultiGetBlobRangeFromOneFile(
Mask cache_hit_mask = 0;
uint64_t total_bytes = 0;
const uint64_t file_number = blob_file.file_number;
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
// Probe the whole-value cache per request; on a hit, slice the requested
@@ -1196,7 +1200,7 @@ void BlobSource::MultiGetBlobRangeFromOneFile(
// is simply surfaced per request. Hence no reader-refresh retry and no
// cache-fill (a partial value cannot represent the whole-record cache entry).
CacheHandleGuard<BlobFileReader> blob_file_reader;
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
Status s = blob_file_cache_->GetBlobFileReader(read_options, blob_file,
&blob_file_reader);
if (!s.ok()) {
for (auto& blob_req : _blob_reqs) {
+16 -10
View File
@@ -67,6 +67,8 @@ class BlobSource {
BlobSource(const BlobSource&) = delete;
BlobSource& operator=(const BlobSource&) = delete;
BlobSource(BlobSource&&) = delete;
BlobSource& operator=(BlobSource&&) = delete;
~BlobSource();
@@ -80,8 +82,9 @@ class BlobSource {
// sets "*bytes_read" to the size of on-disk (possibly compressed) blob
// record.
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
uint64_t file_number, uint64_t offset, uint64_t file_size,
uint64_t value_size, CompressionType compression_type,
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
CompressionType compression_type,
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
uint64_t* bytes_read);
@@ -105,10 +108,11 @@ class BlobSource {
// *bytes_read (when non-null) is the number of bytes read from the file; on a
// hit it is 0.
Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
uint64_t file_number, uint64_t offset, uint64_t file_size,
uint64_t value_size, CompressionType compression_type,
uint64_t range_offset, size_t range_length,
PinnableSlice* value, uint64_t* bytes_read);
const BlobFileOpenInfo& blob_file, uint64_t offset,
uint64_t file_size, uint64_t value_size,
CompressionType compression_type, uint64_t range_offset,
size_t range_length, PinnableSlice* value,
uint64_t* bytes_read);
// Reads a SimpleGen2Blob payload (see db/blob/blob_gen2_format.h) through the
// blob value cache and BLOB_DB_* statistics. This is the counterpart to
@@ -232,7 +236,8 @@ class BlobSource {
// "*bytes_read" to the total size of on-disk (possibly compressed) blob
// records.
void MultiGetBlobFromOneFile(const ReadOptions& read_options,
uint64_t file_number, uint64_t file_size,
const BlobFileOpenInfo& blob_file,
uint64_t file_size,
autovector<BlobReadRequest>& blob_reqs,
uint64_t* bytes_read);
@@ -250,14 +255,15 @@ class BlobSource {
// Byte-range (partial) multi-read counterpart of MultiGetBlobFromOneFile for
// a single blob file. See MultiGetBlobRange.
void MultiGetBlobRangeFromOneFile(const ReadOptions& read_options,
uint64_t file_number, uint64_t file_size,
const BlobFileOpenInfo& blob_file,
uint64_t file_size,
autovector<BlobRangeReadRequest>& blob_reqs,
uint64_t* bytes_read);
inline Status GetBlobFileReader(
const ReadOptions& read_options, uint64_t blob_file_number,
const ReadOptions& read_options, const BlobFileOpenInfo& blob_file,
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
return blob_file_cache_->GetBlobFileReader(read_options, blob_file,
blob_file_reader);
}
+93 -75
View File
@@ -104,6 +104,10 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
&checksum_method, &checksum_value));
}
BlobFileOpenInfo OpenInfoWithoutChecksum(uint64_t file_number) {
return BlobFileOpenInfo{file_number, Slice(), Slice()};
}
} // anonymous namespace
class BlobSourceTest : public DBTestBase {
@@ -212,10 +216,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -251,10 +255,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -299,10 +303,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -339,10 +343,10 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
ASSERT_TRUE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i]);
ASSERT_TRUE(values[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -385,7 +389,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_offsets[i]));
ASSERT_TRUE(blob_source
.GetBlob(read_options, keys[i], blob_file_number,
.GetBlob(read_options, keys[i],
OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read)
@@ -427,7 +432,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
blob_offsets[i]));
ASSERT_TRUE(blob_source
.GetBlob(read_options, keys[i], file_number,
.GetBlob(read_options, keys[i],
OpenInfoWithoutChecksum(file_number),
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer, &values[i],
&bytes_read)
@@ -522,8 +528,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
compression, blob_offsets, blob_sizes);
CacheHandleGuard<BlobFileReader> blob_file_reader;
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
&blob_file_reader));
ASSERT_OK(blob_source.GetBlobFileReader(
read_options, OpenInfoWithoutChecksum(file_number), &blob_file_reader));
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
@@ -541,10 +547,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
blob_offsets[i], file_size, blob_sizes[i],
compression, nullptr /*prefetch_buffer*/,
&values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
blob_offsets[i], file_size, blob_sizes[i], compression,
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
ASSERT_EQ(bytes_read,
@@ -564,10 +570,10 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
blob_offsets[i]));
// Compressed blob size is passed in GetBlob
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], file_number,
blob_offsets[i], file_size, blob_sizes[i],
compression, nullptr /*prefetch_buffer*/,
&values[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(file_number),
blob_offsets[i], file_size, blob_sizes[i], compression,
nullptr /*prefetch_buffer*/, &values[i], &bytes_read));
ASSERT_EQ(values[i], blobs[i] /*uncompressed blob*/);
ASSERT_NE(values[i].size(), blob_sizes[i] /*compressed size*/);
ASSERT_EQ(bytes_read,
@@ -674,7 +680,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
keys[j], blob_offsets[j], blob_sizes[j], kNoCompression,
&value_buf[i * num_blobs + j], &statuses_buf[i * num_blobs + j]);
}
blob_reqs.emplace_back(file_number, file_size, blob_reqs_in_file[i]);
blob_reqs.emplace_back(OpenInfoWithoutChecksum(file_number), file_size,
blob_reqs_in_file[i]);
}
get_perf_context()->Reset();
@@ -731,7 +738,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
}
// Add a fake multi-get blob request.
blob_reqs.emplace_back(fake_file_number, file_size, fake_blob_reqs_in_file);
blob_reqs.emplace_back(OpenInfoWithoutChecksum(fake_file_number), file_size,
fake_blob_reqs_in_file);
blob_source.MultiGetBlob(read_options, blob_reqs, &bytes_read);
@@ -865,8 +873,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
statistics->Reset().PermitUncheckedError();
// Get half of blobs
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
uint64_t fs_read_bytes = 0;
uint64_t ca_read_bytes = 0;
@@ -911,10 +920,10 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
ASSERT_FALSE(blob_source.TEST_BlobInCache(blob_file_number, file_size,
blob_offsets[i]));
ASSERT_OK(blob_source.GetBlob(read_options, keys[i], blob_file_number,
blob_offsets[i], file_size, blob_sizes[i],
kNoCompression, prefetch_buffer,
&value_buf[i], &bytes_read));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[i], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[i], file_size, blob_sizes[i], kNoCompression,
prefetch_buffer, &value_buf[i], &bytes_read));
ASSERT_EQ(value_buf[i], blobs[i]);
ASSERT_TRUE(value_buf[i].IsPinned());
ASSERT_EQ(bytes_read,
@@ -935,8 +944,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
kNoCompression, &value_buf[i], &statuses_buf[i]);
}
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
uint64_t blob_bytes = 0;
for (size_t i = 0; i < num_blobs; ++i) {
@@ -985,8 +995,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
get_perf_context()->Reset();
statistics->Reset().PermitUncheckedError();
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number,
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(blob_file_number), file_size,
blob_reqs, &bytes_read);
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_TRUE(statuses_buf[i].IsIncomplete());
@@ -1029,8 +1040,9 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
get_perf_context()->Reset();
statistics->Reset().PermitUncheckedError();
blob_source.MultiGetBlobFromOneFile(read_options, non_existing_file_number,
file_size, blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(
read_options, OpenInfoWithoutChecksum(non_existing_file_number),
file_size, blob_reqs, &bytes_read);
for (size_t i = 0; i < num_blobs; ++i) {
ASSERT_TRUE(statuses_buf[i].IsIOError());
@@ -1104,8 +1116,9 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
PinnableSlice value;
uint64_t bytes_read = 0;
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &value, &bytes_read));
ASSERT_EQ(value, blobs[0]);
const std::string blob_file_path =
@@ -1114,10 +1127,10 @@ TEST_F(BlobSourceTest, GetBlobPreservesCorruptionDetailsWhenRefreshOpenFails) {
PinnableSlice invalid_value;
bytes_read = 0;
Status s =
blob_source.GetBlob(read_options, keys[0], blob_file_number, file_size,
file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &invalid_value, &bytes_read);
Status s = blob_source.GetBlob(
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
file_size, file_size, blob_sizes[0], kNoCompression, prefetch_buffer,
&invalid_value, &bytes_read);
ASSERT_TRUE(s.IsCorruption());
ASSERT_EQ(bytes_read, 0);
@@ -1178,8 +1191,9 @@ TEST_F(BlobSourceTest,
PinnableSlice value;
uint64_t bytes_read = 0;
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], blob_file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, prefetch_buffer, &value, &bytes_read));
read_options, keys[0], OpenInfoWithoutChecksum(blob_file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
prefetch_buffer, &value, &bytes_read));
ASSERT_EQ(value, blobs[0]);
const std::string blob_file_path =
@@ -1193,8 +1207,9 @@ TEST_F(BlobSourceTest,
&values[0], &statuses[0]);
bytes_read = 0;
blob_source.MultiGetBlobFromOneFile(read_options, blob_file_number, file_size,
blob_reqs, &bytes_read);
blob_source.MultiGetBlobFromOneFile(read_options,
OpenInfoWithoutChecksum(blob_file_number),
file_size, blob_reqs, &bytes_read);
ASSERT_TRUE(statuses[0].IsCorruption());
ASSERT_EQ(bytes_read, 0);
@@ -1303,8 +1318,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
CacheHandleGuard<BlobFileReader> file_reader;
ReadOptions read_options;
ASSERT_OK(
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
ASSERT_OK(blob_source.GetBlobFileReader(
read_options, OpenInfoWithoutChecksum(file_number), &file_reader));
ASSERT_NE(file_reader.GetValue(), nullptr);
const uint64_t file_size = file_reader.GetValue()->GetFileSize();
ASSERT_EQ(file_reader.GetValue()->GetCompressionType(), kNoCompression);
@@ -1322,7 +1337,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
get_perf_context()->Reset();
// key0 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
OpenInfoWithoutChecksum(file_number),
blob_offsets[0], file_size, blob_sizes[0],
kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
@@ -1331,17 +1347,18 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// key0 should be evicted and key0's dummy item is inserted into secondary
// cache. key1 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
blob_offsets[1], file_size, blob_sizes[1],
kNoCompression, nullptr /* prefetch_buffer */,
&values[1], nullptr /* bytes_read */));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
// Release cache handle
values[1].Reset();
// key0 should be filled to the primary cache from the blob file. key1
// should be evicted and key1's dummy item is inserted into secondary cache.
ASSERT_OK(blob_source.GetBlob(read_options, keys[0], file_number,
ASSERT_OK(blob_source.GetBlob(read_options, keys[0],
OpenInfoWithoutChecksum(file_number),
blob_offsets[0], file_size, blob_sizes[0],
kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
@@ -1354,10 +1371,10 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// key0 should be evicted and is inserted into secondary cache.
// key1 should be filled to the primary cache from the blob file.
ASSERT_OK(blob_source.GetBlob(read_options, keys[1], file_number,
blob_offsets[1], file_size, blob_sizes[1],
kNoCompression, nullptr /* prefetch_buffer */,
&values[1], nullptr /* bytes_read */));
ASSERT_OK(blob_source.GetBlob(
read_options, keys[1], OpenInfoWithoutChecksum(file_number),
blob_offsets[1], file_size, blob_sizes[1], kNoCompression,
nullptr /* prefetch_buffer */, &values[1], nullptr /* bytes_read */));
ASSERT_EQ(values[1], blobs[1]);
ASSERT_TRUE(
blob_source.TEST_BlobInCache(file_number, file_size, blob_offsets[1]));
@@ -1422,9 +1439,10 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
// fetch key0 from the blob file to the primary cache.
// key1 is evicted and inserted into the secondary cache.
ASSERT_OK(blob_source.GetBlob(
read_options, keys[0], file_number, blob_offsets[0], file_size,
blob_sizes[0], kNoCompression, nullptr /* prefetch_buffer */,
values.data(), nullptr /* bytes_read */));
read_options, keys[0], OpenInfoWithoutChecksum(file_number),
blob_offsets[0], file_size, blob_sizes[0], kNoCompression,
nullptr /* prefetch_buffer */, values.data(),
nullptr /* bytes_read */));
ASSERT_EQ(values[0], blobs[0]);
// Release cache handle
@@ -1606,8 +1624,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
@@ -1625,8 +1643,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
uint64_t blob_bytes = 0;
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
size_t charge = 0;
@@ -1728,8 +1746,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), 0);
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), 0);
@@ -1744,8 +1762,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
uint64_t blob_bytes = 0;
for (size_t i = 0; i < kNumBlobs; ++i) {
ASSERT_OK(blob_source.GetBlob(
read_options, keys_[i], kBlobFileNumber, blob_offsets[i],
blob_file_size_, blob_sizes[i], kNoCompression,
read_options, keys_[i], OpenInfoWithoutChecksum(kBlobFileNumber),
blob_offsets[i], blob_file_size_, blob_sizes[i], kNoCompression,
nullptr /* prefetch_buffer */, &values[i], nullptr /* bytes_read */));
// Release cache handle
+95 -1
View File
@@ -7,6 +7,7 @@
#include <array>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
@@ -19,15 +20,19 @@
#include "db/column_family.h"
#include "db/db_test_util.h"
#include "db/db_with_timestamp_test_util.h"
#include "env/composite_env_wrapper.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "rocksdb/file_system.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/trace_record.h"
#include "rocksdb/utilities/replayer.h"
#include "test_util/sync_point.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/file_checksum_helper.h"
#include "utilities/fault_injection_env.h"
namespace ROCKSDB_NAMESPACE {
@@ -38,6 +43,49 @@ class DBBlobBasicTest : public DBTestBase {
: DBTestBase("db_blob_basic_test", /* env_do_fsync */ false) {}
};
class BlobFileChecksumCapturingFS : public FileSystemWrapper {
public:
explicit BlobFileChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
: FileSystemWrapper(base) {}
static const char* kClassName() { return "BlobFileChecksumCapturingFS"; }
const char* Name() const override { return kClassName(); }
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
if (fname.find(".blob") != std::string::npos) {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_ = opts.file_checksum;
file_checksum_func_name_ = opts.file_checksum_func_name;
++capture_count_;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
std::string GetFileChecksum() {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_;
}
std::string GetFileChecksumFuncName() {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_func_name_;
}
int GetCaptureCount() {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
private:
std::mutex mu_;
std::string file_checksum_;
std::string file_checksum_func_name_;
int capture_count_ = 0;
};
TEST_F(DBBlobBasicTest, GetBlob) {
Options options = GetDefaultOptions();
options.enable_blob_files = true;
@@ -65,6 +113,41 @@ TEST_F(DBBlobBasicTest, GetBlob) {
.IsIncomplete());
}
TEST_F(DBBlobBasicTest, BlobFileChecksumInFileOptions) {
auto capturing_fs =
std::make_shared<BlobFileChecksumCapturingFS>(env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.env = env.get();
options.enable_blob_files = true;
options.min_blob_size = 0;
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
Defer close_db([this]() { Close(); });
Reopen(options);
constexpr char key[] = "key";
constexpr char blob_value[] = "blob_value";
ASSERT_OK(Put(key, blob_value));
ASSERT_OK(Flush());
std::vector<ColumnFamilyMetaData> column_family_metadata;
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
ASSERT_EQ(column_family_metadata.size(), 1);
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
ASSERT_FALSE(blob_metadata.checksum_value.empty());
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
ASSERT_EQ(Get(key), blob_value);
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
blob_metadata.checksum_method);
}
TEST_F(DBBlobBasicTest, BlobFileWritableFileMaxBufferSize) {
constexpr uint64_t kBlobWriterBufferSize = 128 * 1024;
@@ -754,6 +837,15 @@ TEST_F(DBBlobBasicTest, MultiGetBlobsFromCache) {
ASSERT_EQ(values[2], third_value);
}
// Load the SST blocks into the block cache. The inlined key does not read
// the blob file, so the blob cache stays empty.
{
ReadOptions fill_block_cache_options;
std::string value;
ASSERT_OK(db_->Get(fill_block_cache_options, first_key, &value));
ASSERT_EQ(value, first_value);
}
// Try again with no I/O allowed. The first (inlined) value should be
// successfully read; however, the two blob values could only be read from the
// blob file, so for those the read should return Incomplete.
@@ -1637,11 +1729,13 @@ TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
public testing::WithParamInterface<std::string> {
public:
~DBBlobBasicIOErrorTest() override { Close(); }
protected:
DBBlobBasicIOErrorTest() : sync_point_(GetParam()) {
fault_injection_env_.reset(new FaultInjectionTestEnv(env_));
}
~DBBlobBasicIOErrorTest() { Close(); }
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
std::string sync_point_;
+234 -2
View File
@@ -10,7 +10,9 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <functional>
#include <future>
#include <limits>
#include <mutex>
#include <set>
@@ -40,6 +42,8 @@
#include "rocksdb/utilities/replayer.h"
#include "test_util/sync_point.h"
#include "util/compression.h"
#include "util/defer.h"
#include "util/file_checksum_helper.h"
#include "utilities/fault_injection_env.h"
namespace ROCKSDB_NAMESPACE {
@@ -121,6 +125,59 @@ bool IsBlobFilePath(const std::string& fname) {
file_type == kBlobFile;
}
class BlobFileChecksumCapturingFileSystem : public FileSystemWrapper {
public:
explicit BlobFileChecksumCapturingFileSystem(
const std::shared_ptr<FileSystem>& fs)
: FileSystemWrapper(fs) {}
static const char* kClassName() {
return "BlobFileChecksumCapturingFileSystem";
}
const char* Name() const override { return kClassName(); }
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
if (IsBlobFilePath(fname)) {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_ = opts.file_checksum;
file_checksum_func_name_ = opts.file_checksum_func_name;
++capture_count_;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
void Reset() {
std::lock_guard<std::mutex> lock(mu_);
file_checksum_.clear();
file_checksum_func_name_.clear();
capture_count_ = 0;
}
std::string GetFileChecksum() const {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_;
}
std::string GetFileChecksumFuncName() const {
std::lock_guard<std::mutex> lock(mu_);
return file_checksum_func_name_;
}
int GetCaptureCount() const {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
private:
mutable std::mutex mu_;
std::string file_checksum_;
std::string file_checksum_func_name_;
int capture_count_ = 0;
};
// Keeps track of when an active blob file stops being writer-visible so the
// test filesystem can model current-size visibility only while the file remains
// active.
@@ -1405,7 +1462,9 @@ TEST_F(DBBlobDirectWriteTest,
std::vector<BlobFileGarbage> garbages1;
// Simulate a flush retry: prepare once, skip commit, then prepare the same
// generation again and verify it reuses the already sealed blob file.
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
const std::vector<uint64_t> memtable_ids = {
cfh->cfd()->imm()->GetEarliestMemTableID()};
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
&additions1, &garbages1));
ASSERT_EQ(additions1.size(), 1U);
ASSERT_TRUE(garbages1.empty());
@@ -1414,7 +1473,7 @@ TEST_F(DBBlobDirectWriteTest,
std::vector<BlobFileAddition> additions2;
std::vector<BlobFileGarbage> garbages2;
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
&additions2, &garbages2));
ASSERT_EQ(additions2.size(), 1U);
ASSERT_TRUE(garbages2.empty());
@@ -1755,6 +1814,54 @@ TEST_F(
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->value().ToString(), values[target_idx]);
}
TEST_F(DBBlobDirectWriteTest, DirectWriteDelayedReadUsesFinalizedFileChecksum) {
auto capturing_fs = std::make_shared<BlobFileChecksumCapturingFileSystem>(
env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
Options options = GetDirectWriteOptions();
options.env = env.get();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
Defer close_db([this]() { Close(); });
Reopen(options);
const std::string key = "delayed-read-key";
const std::string value(96, 'v');
ASSERT_OK(Put(key, value));
ReadOptions read_options;
read_options.allow_unprepared_value = true;
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
iter->Seek(key);
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->key(), key);
ASSERT_TRUE(iter->value().empty());
ASSERT_OK(Flush());
std::vector<ColumnFamilyMetaData> column_family_metadata;
db_->GetAllColumnFamilyMetaData(&column_family_metadata);
ASSERT_EQ(column_family_metadata.size(), 1);
ASSERT_EQ(column_family_metadata[0].blob_files.size(), 1);
const BlobMetaData& blob_metadata = column_family_metadata[0].blob_files[0];
ASSERT_FALSE(blob_metadata.checksum_value.empty());
ASSERT_EQ(blob_metadata.checksum_method, "FileChecksumCrc32c");
capturing_fs->Reset();
ASSERT_TRUE(iter->PrepareValue());
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->value().ToString(), value);
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_EQ(capturing_fs->GetFileChecksum(), blob_metadata.checksum_value);
ASSERT_EQ(capturing_fs->GetFileChecksumFuncName(),
blob_metadata.checksum_method);
const int capture_count = capturing_fs->GetCaptureCount();
ASSERT_EQ(Get(key), value);
ASSERT_EQ(capturing_fs->GetCaptureCount(), capture_count);
}
TEST_F(DBBlobDirectWriteTest, OrderedTraceUsesLogicalBatchForBlobDirectWrite) {
AssertOrderedTraceStoresLogicalPut(GetDirectWriteOptions(),
/*expect_blob_files=*/true);
@@ -1772,6 +1879,131 @@ TEST_F(DBBlobDirectWriteTest,
AssertOrderedTraceStoresLogicalPut(options, /*expect_blob_files=*/false);
}
TEST_F(DBBlobDirectWriteTest, OverlappingFlushesPublishTheirOwnBlobFiles) {
for (bool atomic_flush : {false, true}) {
SCOPED_TRACE("atomic_flush=" + std::to_string(atomic_flush));
Options options = GetDirectWriteOptions();
options.atomic_flush = atomic_flush;
options.max_background_jobs = 8;
options.max_write_buffer_number = 4;
options.disable_auto_compactions = true;
DestroyAndReopen(options);
const std::string value(1024, 'v');
// The flush of the memtable holding "key1" waits while it writes its
// table file, until a second flush, of the memtable holding "key2", has
// finished its own table file.
std::atomic<int> table_writes{0};
std::atomic<bool> released{false};
std::promise<void> first_flush_writing;
std::promise<void> release_first_flush;
std::shared_future<void> release_first_flush_future =
release_first_flush.get_future().share();
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
if (table_writes.fetch_add(1) == 0) {
first_flush_writing.set_value();
release_first_flush_future.wait();
}
});
SyncPoint::GetInstance()->SetCallBack(
atomic_flush ? "DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV"
: "DBImpl::FlushMemTableToOutputFile:Finish",
[&](void*) {
if (!released.exchange(true)) {
release_first_flush.set_value();
}
});
SyncPoint::GetInstance()->EnableProcessing();
FlushOptions no_wait;
no_wait.wait = false;
no_wait.allow_write_stall = true;
ASSERT_OK(Put("key1", value));
ASSERT_OK(db_->Flush(no_wait));
first_flush_writing.get_future().wait();
ASSERT_OK(Put("key2", value));
ASSERT_OK(db_->Flush(no_wait));
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_OK(dbfull()->TEST_GetBGError());
ASSERT_EQ(2, NumTableFilesAtLevel(0));
Reopen(options);
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
}
TEST_F(DBBlobDirectWriteTest, RetryAfterOlderOverlappingFlushFails) {
Options options = GetDirectWriteOptions();
options.max_background_jobs = 8;
options.max_write_buffer_number = 4;
options.disable_auto_compactions = true;
options.max_bgerror_resume_count = 0;
DestroyAndReopen(options);
const std::string value(1024, 'v');
// The flush of the memtable holding "key2" finishes while the older flush,
// of the memtable holding "key1", is still writing its table file. The
// newer flush's results wait to be installed after the older one's, but the
// older flush fails, which rolls the newer memtable back too.
std::atomic<int> table_writes{0};
std::atomic<bool> released{false};
std::atomic<bool> failed{false};
std::promise<void> first_flush_writing;
std::promise<void> release_first_flush;
std::shared_future<void> release_first_flush_future =
release_first_flush.get_future().share();
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
if (table_writes.fetch_add(1) == 0) {
first_flush_writing.set_value();
release_first_flush_future.wait();
}
});
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::FlushMemTableToOutputFile:Finish", [&](void*) {
if (!released.exchange(true)) {
release_first_flush.set_value();
}
});
SyncPoint::GetInstance()->SetCallBack(
"FlushJob::WriteLevel0Table:s", [&](void* arg) {
if (released.load() && !failed.exchange(true)) {
IOStatus injected = IOStatus::IOError("injected");
injected.SetRetryable(true);
*static_cast<Status*>(arg) = injected;
}
});
SyncPoint::GetInstance()->EnableProcessing();
FlushOptions no_wait;
no_wait.wait = false;
no_wait.allow_write_stall = true;
ASSERT_OK(Put("key1", value));
ASSERT_OK(db_->Flush(no_wait));
first_flush_writing.get_future().wait();
ASSERT_OK(Put("key2", value));
ASSERT_OK(db_->Flush(no_wait));
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_TRUE(failed.load());
// Retrying the flush must still find the blob files of both memtables.
ASSERT_OK(dbfull()->Resume());
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_OK(dbfull()->TEST_GetBGError());
Reopen(options);
ASSERT_EQ(value, Get("key1"));
ASSERT_EQ(value, Get("key2"));
}
class DBBlobDirectWriteWithTimestampTest : public DBBasicTestWithTimestampBase {
protected:
DBBlobDirectWriteWithTimestampTest()
+3 -2
View File
@@ -396,7 +396,7 @@ Status BuildTable(
relevant_mapping,
ioptions.compaction_style == CompactionStyle::kCompactionStyleFIFO
? meta->file_creation_time
: meta->oldest_ancester_time);
: meta->oldest_ancestor_time);
s = builder->Finish();
}
if (io_status->ok()) {
@@ -452,7 +452,8 @@ Status BuildTable(
WritableFileWriter::PrepareIOOptions(tboptions.write_options, opts);
if (s.ok() && io_status->ok() && !empty) {
StopWatch sw(ioptions.clock, ioptions.stats, TABLE_SYNC_MICROS);
*io_status = file_writer->Sync(opts, ioptions.use_fsync);
*io_status = ioptions.use_fsync ? file_writer->Fsync(opts)
: file_writer->Sync(opts);
}
TEST_SYNC_POINT("BuildTable:BeforeCloseTableFile");
if (s.ok() && io_status->ok() && !empty) {
+171 -40
View File
@@ -195,6 +195,7 @@ using ROCKSDB_NAMESPACE::WALRecoveryMode;
using ROCKSDB_NAMESPACE::WritableFile;
using ROCKSDB_NAMESPACE::WriteBatch;
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
using ROCKSDB_NAMESPACE::WriteBufferFlushPolicy;
using ROCKSDB_NAMESPACE::WriteBufferManager;
using ROCKSDB_NAMESPACE::WriteOptions;
using ROCKSDB_NAMESPACE::WriteStallCondition;
@@ -418,6 +419,13 @@ struct rocksdb_readoptions_t {
std::function<bool(const TableProperties&)> table_filter_func;
std::shared_ptr<UserDefinedIndexFactory> table_index_factory;
rocksdb_readoptions_t() = default;
// No copying allowed
rocksdb_readoptions_t(const rocksdb_readoptions_t&) = delete;
rocksdb_readoptions_t& operator=(const rocksdb_readoptions_t&) = delete;
rocksdb_readoptions_t(rocksdb_readoptions_t&&) = delete;
rocksdb_readoptions_t& operator=(rocksdb_readoptions_t&&) = delete;
~rocksdb_readoptions_t() { ClearTableFilter(); }
void ClearTableFilter() {
@@ -804,14 +812,22 @@ struct rocksdb_open_and_compact_options_t {
};
struct rocksdb_compactionfilter_t : public CompactionFilter {
void* state_;
void (*destructor_)(void*);
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
unsigned char (*filter_)(void*, int level, const char* key, size_t key_length,
const char* existing_value, size_t value_length,
char** new_value, size_t* new_value_length,
unsigned char* value_changed);
const char* (*name_)(void*);
unsigned char ignore_snapshots_;
unsigned char* value_changed) = nullptr;
const char* (*name_)(void*) = nullptr;
unsigned char ignore_snapshots_ = true;
rocksdb_compactionfilter_t() = default;
// No copying allowed
rocksdb_compactionfilter_t(const rocksdb_compactionfilter_t&) = delete;
rocksdb_compactionfilter_t& operator=(const rocksdb_compactionfilter_t&) =
delete;
rocksdb_compactionfilter_t(rocksdb_compactionfilter_t&&) = delete;
rocksdb_compactionfilter_t& operator=(rocksdb_compactionfilter_t&&) = delete;
~rocksdb_compactionfilter_t() override { (*destructor_)(state_); }
@@ -837,11 +853,22 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
};
struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
void* state_;
void (*destructor_)(void*);
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
rocksdb_compactionfilter_t* (*create_compaction_filter_)(
void*, rocksdb_compactionfiltercontext_t* context);
const char* (*name_)(void*);
void*, rocksdb_compactionfiltercontext_t* context) = nullptr;
const char* (*name_)(void*) = nullptr;
rocksdb_compactionfilterfactory_t() = default;
// No copying allowed
rocksdb_compactionfilterfactory_t(const rocksdb_compactionfilterfactory_t&) =
delete;
rocksdb_compactionfilterfactory_t& operator=(
const rocksdb_compactionfilterfactory_t&) = delete;
rocksdb_compactionfilterfactory_t(rocksdb_compactionfilterfactory_t&&) =
delete;
rocksdb_compactionfilterfactory_t& operator=(
rocksdb_compactionfilterfactory_t&&) = delete;
~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); }
@@ -872,6 +899,12 @@ struct rocksdb_comparator_t : public Comparator {
rocksdb_comparator_t(size_t ts_size) : Comparator(ts_size) {}
// No copying allowed
rocksdb_comparator_t(const rocksdb_comparator_t&) = delete;
rocksdb_comparator_t& operator=(const rocksdb_comparator_t&) = delete;
rocksdb_comparator_t(rocksdb_comparator_t&&) = delete;
rocksdb_comparator_t& operator=(rocksdb_comparator_t&&) = delete;
~rocksdb_comparator_t() override { (*destructor_)(state_); }
int Compare(const Slice& a, const Slice& b) const override {
@@ -903,9 +936,16 @@ struct rocksdb_comparator_t : public Comparator {
};
struct rocksdb_filterpolicy_t : public FilterPolicy {
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
rocksdb_filterpolicy_t() = default;
// No copying allowed
rocksdb_filterpolicy_t(const rocksdb_filterpolicy_t&) = delete;
rocksdb_filterpolicy_t& operator=(const rocksdb_filterpolicy_t&) = delete;
rocksdb_filterpolicy_t(rocksdb_filterpolicy_t&&) = delete;
rocksdb_filterpolicy_t& operator=(rocksdb_filterpolicy_t&&) = delete;
~rocksdb_filterpolicy_t() override { (*destructor_)(state_); }
@@ -913,19 +953,29 @@ struct rocksdb_filterpolicy_t : public FilterPolicy {
};
struct rocksdb_mergeoperator_t : public MergeOperator {
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
char* (*full_merge_)(void*, const char* key, size_t key_length,
const char* existing_value, size_t existing_value_length,
const char* const* operands_list,
const size_t* operands_list_length, int num_operands,
unsigned char* success, size_t* new_value_length);
unsigned char* success,
size_t* new_value_length) = nullptr;
char* (*partial_merge_)(void*, const char* key, size_t key_length,
const char* const* operands_list,
const size_t* operands_list_length, int num_operands,
unsigned char* success, size_t* new_value_length);
void (*delete_value_)(void*, const char* value, size_t value_length);
unsigned char* success,
size_t* new_value_length) = nullptr;
void (*delete_value_)(void*, const char* value,
size_t value_length) = nullptr;
rocksdb_mergeoperator_t() = default;
// No copying allowed
rocksdb_mergeoperator_t(const rocksdb_mergeoperator_t&) = delete;
rocksdb_mergeoperator_t& operator=(const rocksdb_mergeoperator_t&) = delete;
rocksdb_mergeoperator_t(rocksdb_mergeoperator_t&&) = delete;
rocksdb_mergeoperator_t& operator=(rocksdb_mergeoperator_t&&) = delete;
~rocksdb_mergeoperator_t() override { (*destructor_)(state_); }
@@ -1007,12 +1057,19 @@ struct rocksdb_env_t {
};
struct rocksdb_slicetransform_t : public SliceTransform {
void* state_;
void (*destructor_)(void*);
const char* (*name_)(void*);
void* state_ = nullptr;
void (*destructor_)(void*) = nullptr;
const char* (*name_)(void*) = nullptr;
char* (*transform_)(void*, const char* key, size_t length,
size_t* dst_length);
unsigned char (*in_domain_)(void*, const char* key, size_t length);
size_t* dst_length) = nullptr;
unsigned char (*in_domain_)(void*, const char* key, size_t length) = nullptr;
rocksdb_slicetransform_t() = default;
// No copying allowed
rocksdb_slicetransform_t(const rocksdb_slicetransform_t&) = delete;
rocksdb_slicetransform_t& operator=(const rocksdb_slicetransform_t&) = delete;
rocksdb_slicetransform_t(rocksdb_slicetransform_t&&) = delete;
rocksdb_slicetransform_t& operator=(rocksdb_slicetransform_t&&) = delete;
~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
@@ -1336,6 +1393,14 @@ struct rocksdb_compactionservice_t : public CompactionService {
cancel_awaiting_jobs_(cancel_awaiting_jobs),
on_installation_(on_installation) {}
// No copying allowed
rocksdb_compactionservice_t(const rocksdb_compactionservice_t&) = delete;
rocksdb_compactionservice_t& operator=(const rocksdb_compactionservice_t&) =
delete;
rocksdb_compactionservice_t(rocksdb_compactionservice_t&&) = delete;
rocksdb_compactionservice_t& operator=(rocksdb_compactionservice_t&&) =
delete;
~rocksdb_compactionservice_t() override {
if (destructor_) {
(*destructor_)(state_);
@@ -5197,6 +5262,11 @@ uint64_t rocksdb_table_properties_column_family_id(
return props->rep.column_family_id;
}
uint64_t rocksdb_table_properties_oldest_ancestor_time(
const rocksdb_table_properties_t* props) {
return props->rep.oldest_ancestor_time;
}
uint64_t rocksdb_table_properties_creation_time(
const rocksdb_table_properties_t* props) {
return props->rep.creation_time;
@@ -5227,11 +5297,6 @@ uint64_t rocksdb_table_properties_fast_compression_estimated_data_size(
return props->rep.fast_compression_estimated_data_size;
}
uint64_t rocksdb_table_properties_external_sst_file_global_seqno_offset(
const rocksdb_table_properties_t* props) {
return props->rep.external_sst_file_global_seqno_offset;
}
uint64_t rocksdb_table_properties_tail_start_offset(
const rocksdb_table_properties_t* props) {
return props->rep.tail_start_offset;
@@ -6962,7 +7027,12 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(
// they delegate to a NewBloomFilterPolicy() instead of user
// supplied C functions.
struct Wrapper : public rocksdb_filterpolicy_t {
const FilterPolicy* rep_;
const FilterPolicy* rep_ = nullptr;
Wrapper() = default;
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
Wrapper(Wrapper&&) = delete;
Wrapper& operator=(Wrapper&&) = delete;
~Wrapper() override { delete rep_; }
const char* Name() const override { return rep_->Name(); }
const char* CompatibilityName() const override {
@@ -7002,7 +7072,12 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_format(
// they delegate to a NewRibbonFilterPolicy() instead of user
// supplied C functions.
struct Wrapper : public rocksdb_filterpolicy_t {
const FilterPolicy* rep_;
const FilterPolicy* rep_ = nullptr;
Wrapper() = default;
Wrapper(const Wrapper&) = delete;
Wrapper& operator=(const Wrapper&) = delete;
Wrapper(Wrapper&&) = delete;
Wrapper& operator=(Wrapper&&) = delete;
~Wrapper() override { delete rep_; }
const char* Name() const override { return rep_->Name(); }
const char* CompatibilityName() const override {
@@ -7761,6 +7836,35 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create(
return wbm;
}
static bool rocksdb_write_buffer_manager_parse_flush_policy(
int value, WriteBufferFlushPolicy* policy) {
switch (value) {
case rocksdb_write_buffer_manager_flush_policy_oldest:
*policy = WriteBufferFlushPolicy::kFlushOldest;
return true;
case rocksdb_write_buffer_manager_flush_policy_largest:
*policy = WriteBufferFlushPolicy::kFlushLargest;
return true;
case rocksdb_write_buffer_manager_flush_policy_largest_across_dbs:
*policy = WriteBufferFlushPolicy::kFlushLargestAcrossDBs;
return true;
default:
return false;
}
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_policy(
size_t buffer_size, bool allow_stall, int flush_policy) {
WriteBufferFlushPolicy policy;
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
return nullptr;
}
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep = std::make_shared<WriteBufferManager>(
buffer_size, std::shared_ptr<Cache>{}, allow_stall, policy);
return wbm;
}
rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall) {
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
@@ -7769,6 +7873,20 @@ rocksdb_write_buffer_manager_t* rocksdb_write_buffer_manager_create_with_cache(
return wbm;
}
rocksdb_write_buffer_manager_t*
rocksdb_write_buffer_manager_create_with_cache_and_policy(
size_t buffer_size, const rocksdb_cache_t* cache, bool allow_stall,
int flush_policy) {
WriteBufferFlushPolicy policy;
if (!rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
return nullptr;
}
rocksdb_write_buffer_manager_t* wbm = new rocksdb_write_buffer_manager_t;
wbm->rep = std::make_shared<WriteBufferManager>(buffer_size, cache->rep,
allow_stall, policy);
return wbm;
}
void rocksdb_write_buffer_manager_destroy(rocksdb_write_buffer_manager_t* wbm) {
delete wbm;
}
@@ -7809,6 +7927,19 @@ ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
wbm->rep->SetAllowStall(new_allow_stall);
}
int rocksdb_write_buffer_manager_flush_policy(
rocksdb_write_buffer_manager_t* wbm) {
return static_cast<int>(wbm->rep->flush_policy());
}
void rocksdb_write_buffer_manager_set_flush_policy(
rocksdb_write_buffer_manager_t* wbm, int flush_policy) {
WriteBufferFlushPolicy policy;
if (rocksdb_write_buffer_manager_parse_flush_policy(flush_policy, &policy)) {
wbm->rep->SetFlushPolicy(policy);
}
}
rocksdb_sst_file_manager_t* rocksdb_sst_file_manager_create(
rocksdb_env_t* env) {
rocksdb_sst_file_manager_t* sfm = new rocksdb_sst_file_manager_t;
@@ -9960,6 +10091,16 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
return opt->rep.write_dbid_to_manifest;
}
void rocksdb_options_set_track_options_file_number_in_manifest(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.track_options_file_number_in_manifest = v;
}
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
rocksdb_options_t* opt) {
return opt->rep.track_options_file_number_in_manifest;
}
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.write_identity_file = v;
@@ -11276,16 +11417,6 @@ unsigned char rocksdb_ingestexternalfileoptions_get_ingest_behind(
return opt->rep.ingest_behind;
}
void rocksdb_ingestexternalfileoptions_set_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.write_global_seqno = v;
}
unsigned char rocksdb_ingestexternalfileoptions_get_write_global_seqno(
rocksdb_ingestexternalfileoptions_t* opt) {
return opt->rep.write_global_seqno;
}
void rocksdb_ingestexternalfileoptions_set_verify_checksums_before_ingest(
rocksdb_ingestexternalfileoptions_t* opt, unsigned char v) {
opt->rep.verify_checksums_before_ingest = v;
+30
View File
@@ -6716,6 +6716,36 @@ int main(int argc, char** argv) {
300 == rocksdb_write_buffer_manager_buffer_size(write_buffer_manager));
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager = rocksdb_write_buffer_manager_create_with_policy(
200, true,
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
CheckCondition(write_buffer_manager != NULL);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_set_flush_policy(
write_buffer_manager,
rocksdb_write_buffer_manager_flush_policy_largest);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_set_flush_policy(write_buffer_manager, 999);
CheckCondition(
rocksdb_write_buffer_manager_flush_policy_largest ==
rocksdb_write_buffer_manager_flush_policy(write_buffer_manager));
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager =
rocksdb_write_buffer_manager_create_with_cache_and_policy(
200, lru, true,
rocksdb_write_buffer_manager_flush_policy_largest_across_dbs);
CheckCondition(write_buffer_manager != NULL);
rocksdb_write_buffer_manager_destroy(write_buffer_manager);
write_buffer_manager =
rocksdb_write_buffer_manager_create_with_policy(200, true, 999);
CheckCondition(write_buffer_manager == NULL);
rocksdb_cache_destroy(lru);
}
+2
View File
@@ -26,6 +26,8 @@ class CoalescingIterator : public Iterator {
// No copy allowed
CoalescingIterator(const CoalescingIterator&) = delete;
CoalescingIterator& operator=(const CoalescingIterator&) = delete;
CoalescingIterator(CoalescingIterator&&) = delete;
CoalescingIterator& operator=(CoalescingIterator&&) = delete;
bool Valid() const override { return impl_.Valid(); }
void SeekToFirst() override { impl_.SeekToFirst(); }
+126 -7
View File
@@ -33,6 +33,7 @@
#include "db/write_controller.h"
#include "file/sst_file_manager_impl.h"
#include "logging/logging.h"
#include "memtable/flush_initiator.h"
#include "monitoring/thread_status_util.h"
#include "options/options_helper.h"
#include "port/port.h"
@@ -612,7 +613,8 @@ ColumnFamilyData::ColumnFamilyData(
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, bool read_only, bool fast_sst_open)
const std::string& db_session_id, bool read_only,
bool options_are_sanitized, bool fast_sst_open)
: id_(id),
name_(name),
dummy_versions_(_dummy_versions),
@@ -622,7 +624,10 @@ ColumnFamilyData::ColumnFamilyData(
dropped_(false),
flush_skip_reschedule_(false),
internal_comparator_(cf_options.comparator),
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
initial_cf_options_(
options_are_sanitized
? cf_options
: SanitizeCfOptions(db_options, read_only, cf_options)),
ioptions_(db_options, initial_cf_options_),
mutable_cf_options_(initial_cf_options_),
is_delete_range_supported_(
@@ -672,7 +677,7 @@ ColumnFamilyData::ColumnFamilyData(
new InternalStats(ioptions_.num_levels, ioptions_.clock, this));
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
block_cache_tracer, io_tracer,
db_session_id, fast_sst_open));
db_session_id, fast_sst_open, name_));
blob_file_cache_.reset(
new BlobFileCache(_table_cache, &ioptions(), soptions(), id_,
internal_stats_->GetBlobFileReadHist(), io_tracer));
@@ -834,6 +839,11 @@ void ColumnFamilyData::SetDropped() {
// can't drop default CF
assert(id_ != 0);
dropped_ = true;
// A tracker can still contain accounting published while a different WBM
// policy was active. Cleanup therefore cannot depend on the current policy.
if (mem_ != nullptr) {
mem_->StopWBMTracking();
}
write_controller_token_.reset();
// remove from column_family_set
@@ -1246,7 +1256,10 @@ uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
MemTable* ColumnFamilyData::ConstructNewMemtable(
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
write_buffer_manager_, earliest_seq, id_);
write_buffer_manager_, earliest_seq, id_,
column_family_set_ != nullptr
? column_family_set_->flush_initiator()
: nullptr);
}
void ColumnFamilyData::CreateNewMemtable(SequenceNumber earliest_seq) {
@@ -1829,7 +1842,7 @@ ColumnFamilySet::ColumnFamilySet(
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
block_cache_tracer, io_tracer, db_id, db_session_id,
/*read_only*/ true)),
/*read_only=*/true, /*options_are_sanitized=*/false)),
default_cfd_cache_(nullptr),
db_name_(dbname),
db_options_(db_options),
@@ -1899,15 +1912,112 @@ size_t ColumnFamilySet::NumberOfColumnFamilies() const {
return column_families_.size();
}
void ColumnFamilySet::RefreshFlushableMemAccounting() {
if (flush_initiator_ == nullptr || write_buffer_manager_ == nullptr ||
!write_buffer_manager_->ShouldTrackFlushInitiator()) {
return;
}
if (bulk_flushable_mem_accounting_depth_ > 0) {
bulk_flushable_mem_accounting_dirty_ = true;
flush_initiator_->InvalidateLargestFlushableCFMem();
return;
}
// Keep candidate readers from consuming a mixture of the previous snapshot
// and the values published by this rebuild.
flush_initiator_->InvalidateLargestFlushableCFMem();
constexpr int kMaxRefreshAttempts = 3;
for (int attempt = 0; attempt < kMaxRefreshAttempts; ++attempt) {
const uint64_t update_seq =
flush_initiator_->GetFlushableMemUpdateSequence();
bool has_mutable_mem = false;
bool has_ineligible_cf = false;
size_t largest = 0;
size_t waiting_immutable_mem = 0;
for (auto cfd : *this) {
if (cfd->IsDropped() || !cfd->initialized()) {
continue;
}
const bool mutable_not_empty = !cfd->mem()->IsEmpty();
if (mutable_not_empty) {
cfd->mem()->RefreshWBMTracking();
}
const bool has_mutable =
mutable_not_empty && cfd->mem()->IsWBMTrackingActive();
has_mutable_mem = has_mutable_mem || has_mutable;
if (flush_initiator_->UsesTotalMutableMem()) {
waiting_immutable_mem +=
cfd->imm()->WBMTrackedUnstartedMemTablesMemoryUsage();
continue;
}
if (cfd->imm()->IsFlushPendingOrRunning()) {
has_ineligible_cf = true;
continue;
}
const size_t mutable_mem =
has_mutable ? cfd->mem()->WBMTrackedMemoryUsage() : 0;
const size_t immutable_mem =
cfd->imm()->WBMTrackedUnflushedMemTablesMemoryUsage();
waiting_immutable_mem += immutable_mem;
largest = std::max(largest, mutable_mem + immutable_mem);
}
if (flush_initiator_->UsesTotalMutableMem()) {
flush_initiator_->SetHasIneligibleCF(false);
flush_initiator_->SetHasFlushableCF(has_mutable_mem ||
waiting_immutable_mem > 0);
if (flush_initiator_->TrySetLargestFlushableCFMem(
0, waiting_immutable_mem, update_seq)) {
write_buffer_manager_->NotifyFlushInitiatorChanged();
return;
}
continue;
}
flush_initiator_->SetHasIneligibleCF(has_ineligible_cf);
flush_initiator_->SetHasFlushableCF(largest > 0);
if (flush_initiator_->TrySetLargestFlushableCFMem(
largest, waiting_immutable_mem, update_seq)) {
write_buffer_manager_->NotifyFlushInitiatorChanged();
return;
}
}
flush_initiator_->InvalidateLargestFlushableCFMem();
}
void ColumnFamilySet::BeginBulkFlushableMemAccountingUpdate() {
++bulk_flushable_mem_accounting_depth_;
if (bulk_flushable_mem_accounting_depth_ == 1) {
bulk_flushable_mem_accounting_dirty_ = true;
if (flush_initiator_ != nullptr && write_buffer_manager_ != nullptr &&
write_buffer_manager_->ShouldTrackFlushInitiator()) {
flush_initiator_->InvalidateLargestFlushableCFMem();
write_buffer_manager_->NotifyFlushInitiatorChanged();
}
}
}
void ColumnFamilySet::EndBulkFlushableMemAccountingUpdate() {
assert(bulk_flushable_mem_accounting_depth_ > 0);
--bulk_flushable_mem_accounting_depth_;
if (bulk_flushable_mem_accounting_depth_ == 0 &&
bulk_flushable_mem_accounting_dirty_) {
bulk_flushable_mem_accounting_dirty_ = false;
TEST_SYNC_POINT(
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh");
RefreshFlushableMemAccounting();
}
}
// under a DB mutex AND write thread
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
const std::string& name, uint32_t id, Version* dummy_versions,
const ColumnFamilyOptions& options, bool read_only) {
const ColumnFamilyOptions& options, bool read_only,
bool options_are_sanitized) {
assert(column_families_.find(name) == column_families_.end());
ColumnFamilyData* new_cfd = new ColumnFamilyData(
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
db_id_, db_session_id_, read_only, fast_sst_open_);
db_id_, db_session_id_, read_only, options_are_sanitized, fast_sst_open_);
column_families_.insert({name, id});
column_family_data_.insert({id, new_cfd});
auto ucmp = new_cfd->user_comparator();
@@ -1939,6 +2049,15 @@ void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
column_families_.erase(cfd->GetName());
running_ts_sz_.erase(cf_id);
ts_sz_for_record_.erase(cf_id);
if (bulk_flushable_mem_accounting_depth_ > 0) {
bulk_flushable_mem_accounting_dirty_ = true;
if (flush_initiator_ != nullptr) {
flush_initiator_->InvalidateLargestFlushableCFMem();
}
return;
}
TEST_SYNC_POINT("ColumnFamilySet::RemoveColumnFamily:Refresh");
RefreshFlushableMemAccounting();
}
// under a DB mutex OR from a write thread
+35 -13
View File
@@ -52,6 +52,7 @@ struct SuperVersionContext;
class BlobFileCache;
class BlobFilePartitionManager;
class BlobSource;
class FlushInitiator;
extern const double kIncSlowdownRatio;
// This file contains a list of data structures for managing column family
@@ -170,7 +171,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
ColumnFamilyHandleImpl(ColumnFamilyData* cfd, DBImpl* db,
InstrumentedMutex* mutex);
// destroy without mutex
virtual ~ColumnFamilyHandleImpl();
~ColumnFamilyHandleImpl() override;
virtual ColumnFamilyData* cfd() const { return cfd_; }
virtual DBImpl* db() const { return db_; }
@@ -653,17 +654,15 @@ class ColumnFamilyData {
private:
friend class ColumnFamilySet;
ColumnFamilyData(uint32_t id, const std::string& name,
Version* dummy_versions, Cache* table_cache,
WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options,
const ImmutableDBOptions& db_options,
const FileOptions* file_options,
ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer,
const std::string& db_id, const std::string& db_session_id,
bool read_only, bool fast_sst_open = false);
ColumnFamilyData(
uint32_t id, const std::string& name, Version* dummy_versions,
Cache* table_cache, WriteBufferManager* write_buffer_manager,
const ColumnFamilyOptions& options, const ImmutableDBOptions& db_options,
const FileOptions* file_options, ColumnFamilySet* column_family_set,
BlockCacheTracer* const block_cache_tracer,
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
const std::string& db_session_id, bool read_only,
bool options_are_sanitized, bool fast_sst_open = false);
std::vector<std::string> GetDbPaths() const;
@@ -849,7 +848,8 @@ class ColumnFamilySet {
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
Version* dummy_version,
const ColumnFamilyOptions& options,
bool read_only);
bool read_only,
bool options_are_sanitized);
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
const {
@@ -871,6 +871,19 @@ class ColumnFamilySet {
WriteBufferManager* write_buffer_manager() { return write_buffer_manager_; }
void SetFlushInitiator(FlushInitiator* flush_initiator) {
flush_initiator_ = flush_initiator;
}
FlushInitiator* flush_initiator() const { return flush_initiator_; }
void RefreshFlushableMemAccounting();
// Coalesces accounting rebuilds while a caller removes multiple column
// families. Both methods require the DB mutex.
void BeginBulkFlushableMemAccountingUpdate();
void EndBulkFlushableMemAccountingUpdate();
WriteController* write_controller() { return write_controller_; }
private:
@@ -898,6 +911,9 @@ class ColumnFamilySet {
// column families with non-zero user-defined timestamp size.
UnorderedMap<uint32_t, size_t> ts_sz_for_record_;
int bulk_flushable_mem_accounting_depth_ = 0;
bool bulk_flushable_mem_accounting_dirty_ = false;
uint32_t max_column_family_;
const FileOptions file_options_;
@@ -912,6 +928,7 @@ class ColumnFamilySet {
const ImmutableDBOptions* const db_options_;
Cache* table_cache_;
WriteBufferManager* write_buffer_manager_;
FlushInitiator* flush_initiator_ = nullptr;
WriteController* write_controller_;
BlockCacheTracer* const block_cache_tracer_;
std::shared_ptr<IOTracer> io_tracer_;
@@ -933,6 +950,11 @@ class RefedColumnFamilySet {
explicit iterator(ColumnFamilySet::iterator wrapped) : wrapped_(wrapped) {
MaybeRef(*wrapped_);
}
// No copying allowed
iterator(const iterator&) = delete;
iterator& operator=(const iterator&) = delete;
iterator(iterator&&) = delete;
iterator& operator=(iterator&&) = delete;
~iterator() { MaybeUnref(*wrapped_); }
inline void MaybeRef(ColumnFamilyData* cfd) {
if (cfd->GetID() != ColumnFamilyData::kDummyColumnFamilyDataId) {
+545 -38
View File
@@ -25,6 +25,7 @@
#include "rocksdb/iterator.h"
#include "rocksdb/listener.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_util.h"
#include "test_util/sync_point.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
@@ -32,6 +33,7 @@
#include "util/defer.h"
#include "util/string_util.h"
#include "utilities/fault_injection_env.h"
#include "utilities/fault_injection_fs.h"
#include "utilities/merge_operators.h"
namespace ROCKSDB_NAMESPACE {
@@ -113,7 +115,8 @@ class ColumnFamilyTestBase : public testing::Test {
}
void Build(int base, int n, int flush_every = 0) {
std::string key_space, value_space;
std::string key_space;
std::string value_space;
WriteBatch batch;
for (int i = 0; i < n; i++) {
@@ -179,6 +182,35 @@ class ColumnFamilyTestBase : public testing::Test {
db_.reset();
}
void AssertColumnFamilyNames(const std::vector<std::string>& expected) {
std::vector<std::string> column_families;
ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &column_families));
ASSERT_EQ(expected, column_families);
}
void LoadLatestColumnFamilyDescriptors(
std::vector<ColumnFamilyDescriptor>* descriptors) {
ConfigOptions config_options;
config_options.env = db_options_.env;
DBOptions loaded_db_options;
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
descriptors));
}
size_t CountOptionsFiles() {
std::vector<std::string> children;
EXPECT_OK(db_options_.env->GetChildren(dbname_, &children));
size_t count = 0;
for (const auto& child : children) {
uint64_t number = 0;
FileType type;
if (ParseFileName(child, &number, &type) && type == kOptionsFile) {
++count;
}
}
return count;
}
Status TryOpen(std::vector<std::string> cf,
std::vector<ColumnFamilyOptions> options = {}) {
std::vector<ColumnFamilyDescriptor> column_families;
@@ -514,8 +546,9 @@ class ColumnFamilyTest
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
testing::Values(test::kDefaultFormatVersion));
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
testing::Values(kLatestBbtFormatVersion));
INSTANTIATE_TEST_CASE_P(
FormatLatest, ColumnFamilyTest,
testing::Values(BlockBasedTableOptions::kLatestSupportedFormatVersion));
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
for (int iter = 0; iter < 3; ++iter) {
@@ -705,8 +738,11 @@ INSTANTIATE_TEST_CASE_P(
std::make_tuple(test::kDefaultFormatVersion, false)));
INSTANTIATE_TEST_CASE_P(
FormatLatest, FlushEmptyCFTestWithParam,
testing::Values(std::make_tuple(kLatestBbtFormatVersion, true),
std::make_tuple(kLatestBbtFormatVersion, false)));
testing::Values(
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
true),
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
false)));
TEST_P(ColumnFamilyTest, AddDrop) {
Open();
@@ -774,6 +810,8 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
constexpr int kNumCF = 1000;
ColumnFamilyOptions cf_options;
WriteOptions write_options;
db_options_.write_buffer_manager = std::make_shared<WriteBufferManager>(
1 << 30, nullptr, false, WriteBufferFlushPolicy::kFlushLargestAcrossDBs);
Open();
std::vector<std::string> cf_names;
std::vector<ColumnFamilyHandle*> cf_handles;
@@ -784,7 +822,22 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
for (int i = 1; i <= kNumCF; i++) {
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
}
std::atomic<int> per_removal_refreshes{0};
std::atomic<int> bulk_refreshes{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"ColumnFamilySet::RemoveColumnFamily:Refresh",
[&](void*) { ++per_removal_refreshes; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"ColumnFamilySet::EndBulkFlushableMemAccountingUpdate:Refresh",
[&](void*) { ++bulk_refreshes; });
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Defer cleanup_sync_points([] {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
});
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
EXPECT_EQ(0, per_removal_refreshes.load());
EXPECT_EQ(1, bulk_refreshes.load());
std::vector<ColumnFamilyDescriptor> cf_descriptors;
for (auto* handle : cf_handles) {
delete handle;
@@ -798,7 +851,10 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
for (int i = 1; i <= kNumCF; i++) {
ASSERT_OK(db_->Put(write_options, cf_handles[i - 1], "foo", "bar"));
}
bulk_refreshes.store(0);
ASSERT_OK(db_->DropColumnFamilies(cf_handles));
EXPECT_EQ(0, per_removal_refreshes.load());
EXPECT_EQ(1, bulk_refreshes.load());
for (auto* handle : cf_handles) {
delete handle;
}
@@ -897,7 +953,9 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
CreateColumnFamilies({"cf1", "cf2"});
// fill up the DB
std::string one, two, three;
std::string one;
std::string two;
std::string three;
PutFixed64(&one, 1);
PutFixed64(&two, 2);
PutFixed64(&three, 3);
@@ -1105,7 +1163,10 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
Open();
CreateColumnFamilies({"one", "two", "three"});
ColumnFamilyOptions default_cf, one, two, three;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
ColumnFamilyOptions three;
// setup options. all column families have max_write_buffer_number setup to 10
// "default" -> 100KB memtable, start flushing immediately
// "one" -> 200KB memtable, start flushing with two immutable memtables
@@ -1257,7 +1318,9 @@ TEST_P(ColumnFamilyTest, GetComparator) {
// Add three column families - one with no comparator and two
// with comparators specified
ColumnFamilyOptions second, third, fourth;
ColumnFamilyOptions second;
ColumnFamilyOptions third;
ColumnFamilyOptions fourth;
second.comparator = &third_comparator;
third.comparator = &fourth_comparator;
CreateColumnFamilies({"second", "third", "fourth"}, {second, third, fourth});
@@ -1270,12 +1333,16 @@ TEST_P(ColumnFamilyTest, GetComparator) {
TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
Open();
CreateColumnFamilies({"first", "second"});
ColumnFamilyOptions default_cf, first, second;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions first;
ColumnFamilyOptions second;
first.merge_operator = MergeOperators::CreateUInt64AddOperator();
second.merge_operator = MergeOperators::CreateStringAppendOperator();
Reopen({default_cf, first, second});
std::string one, two, three;
std::string one;
std::string two;
std::string three;
PutFixed64(&one, 1);
PutFixed64(&two, 2);
PutFixed64(&three, 3);
@@ -1300,7 +1367,9 @@ TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf, one, two;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
db_options_.max_open_files = 20; // only 10 files in file cache
default_cf.level_compaction_dynamic_level_bytes = false;
@@ -1372,7 +1441,9 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf, one, two;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1472,7 +1543,9 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf, one, two;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1569,7 +1642,9 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf, one, two;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1668,7 +1743,8 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf, one;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1768,7 +1844,8 @@ TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf, one;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1859,7 +1936,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf, one;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -1956,7 +2034,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
Open();
CreateColumnFamilies({"one"});
ColumnFamilyOptions default_cf, one;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
db_options_.max_open_files = 20; // only 10 files in file cache
db_options_.max_background_compactions = 3;
@@ -2169,7 +2248,9 @@ TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
Open();
CreateColumnFamilies({"one", "two"});
ColumnFamilyOptions default_cf, one, two;
ColumnFamilyOptions default_cf;
ColumnFamilyOptions one;
ColumnFamilyOptions two;
default_cf.write_buffer_size = 100000; // small write buffer size
default_cf.arena_block_size = 4096;
default_cf.disable_auto_compactions = true;
@@ -2230,8 +2311,378 @@ struct CountOptionsFilesFs : public FileSystemWrapper {
std::atomic<int> options_files_created{};
};
struct FailOptionsFilesFs : public FileSystemWrapper {
explicit FailOptionsFilesFs(const std::shared_ptr<FileSystem>& t)
: FileSystemWrapper(t) {}
const char* Name() const override { return "FailOptionsFilesFs"; }
IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
std::unique_ptr<FSWritableFile>* r,
IODebugContext* dbg) override {
if (fail_options_writes.load(std::memory_order_relaxed) &&
f.find("OPTIONS-") != std::string::npos) {
return IOStatus::IOError("injected OPTIONS-file creation failure");
}
return FileSystemWrapper::NewWritableFile(f, file_opts, r, dbg);
}
std::atomic<bool> fail_options_writes{false};
};
} // namespace
TEST_P(ColumnFamilyTest, TrackedOptionsWriteFailureDoesNotChangeManifest) {
db_options_.track_options_file_number_in_manifest = true;
db_options_.max_manifest_file_size = 1;
db_options_.max_manifest_space_amp_pct = 0;
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
OptionsFileManifestState before_failure;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &before_failure));
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
ColumnFamilyHandle* handle = nullptr;
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_EQ(nullptr, handle);
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
OptionsFileManifestState after_failure;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &after_failure));
ASSERT_EQ(before_failure.has_committed_options_file_number,
after_failure.has_committed_options_file_number);
ASSERT_EQ(before_failure.committed_options_file_number,
after_failure.committed_options_file_number);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
const uint64_t old_manifest_number =
dbfull()->GetVersionSet()->manifest_file_number();
ASSERT_OK(db_->Put(WriteOptions(), "key", "value"));
ASSERT_OK(db_->Flush(FlushOptions()));
ASSERT_NE(old_manifest_number,
dbfull()->GetVersionSet()->manifest_file_number());
OptionsFileManifestState after_rotation;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &after_rotation));
ASSERT_EQ(before_failure.has_committed_options_file_number,
after_rotation.has_committed_options_file_number);
ASSERT_EQ(before_failure.committed_options_file_number,
after_rotation.committed_options_file_number);
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, TrackedDropFailsBeforeManifestOnOptionsError) {
db_options_.track_options_file_number_in_manifest = true;
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_NE(nullptr, handle);
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
ASSERT_NOK(db_->DropColumnFamily(handle));
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName, "one"});
}
TEST_P(ColumnFamilyTest, TrackedManifestFailureKeepsPreviousOptions) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::string selected_options_before;
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
&selected_options_before));
std::atomic<bool> inject_error{true};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (inject_error.exchange(false)) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ColumnFamilyHandle* handle = nullptr;
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
ASSERT_EQ(nullptr, handle);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::string selected_options_after;
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
&selected_options_after));
ASSERT_EQ(selected_options_before, selected_options_after);
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, FirstTrackedCreateFailureIgnoresUncommittedOptions) {
// Create a legacy DB without a committed number, then enable tracking while
// Open also
// needs to create a missing CF. The current topology must be bound before
// the proposed topology OPTIONS file can become a durable orphan.
Open();
Close();
db_options_.track_options_file_number_in_manifest = true;
db_options_.create_missing_column_families = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
bool injected = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::CreateColumnFamilyImpl:AfterPersistOptions", [&](void*) {
if (!injected) {
injected = true;
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(TryOpen({kDefaultColumnFamilyName, "one"}));
ASSERT_TRUE(injected);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
OptionsFileManifestState protocol_state;
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
dbname_, db_options_.env->GetFileSystem().get(), &protocol_state));
ASSERT_TRUE(protocol_state.has_committed_options_file_number);
std::string selected_options;
ASSERT_OK(
GetLatestOptionsFileName(dbname_, db_options_.env, &selected_options));
ASSERT_EQ(OptionsFileName(protocol_state.committed_options_file_number),
selected_options);
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, TrackedCreatePersistsCommittedOptions) {
db_options_.track_options_file_number_in_manifest = true;
Open();
ColumnFamilyOptions requested;
requested.arena_block_size = 0;
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(requested, "one", &handle));
const ColumnFamilyOptions effective = db_->GetOptions(handle);
ASSERT_GT(effective.arena_block_size, 0U);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ("one", loaded_cfs[1].name);
ASSERT_EQ(effective.arena_block_size, loaded_cfs[1].options.arena_block_size);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
TEST_P(ColumnFamilyTest, TrackedBatchTopologyWritesOneOptionsSnapshot) {
db_options_.track_options_file_number_in_manifest = true;
auto count_fs =
std::make_shared<CountOptionsFilesFs>(db_options_.env->GetFileSystem());
auto count_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, count_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, count_env.get());
Open();
count_fs->options_files_created.store(0, std::memory_order_relaxed);
std::vector<ColumnFamilyHandle*> handles;
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(),
{"one", "two", "three"}, &handles));
ASSERT_EQ(3U, handles.size());
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
count_fs->options_files_created.store(0, std::memory_order_relaxed);
ASSERT_OK(db_->DropColumnFamilies(handles));
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
for (auto* handle : handles) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(1U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
Close();
}
TEST_P(ColumnFamilyTest, TrackedPartialBatchFiltersOptionsSuperset) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::atomic<int> manifest_writes{0};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<ColumnFamilyHandle*> handles;
ASSERT_NOK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
&handles));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
ASSERT_EQ(1U, handles.size());
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
ASSERT_EQ("one", loaded_cfs[1].name);
ConfigOptions config_options;
config_options.env = db_options_.env;
DBOptions loaded_db_options;
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
&loaded_cfs));
ASSERT_OK(CheckOptionsCompatibility(config_options, dbname_,
loaded_db_options, loaded_cfs));
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
Close();
}
TEST_P(ColumnFamilyTest, TrackedPartialBatchDropFiltersOptionsSuperset) {
db_options_.track_options_file_number_in_manifest = true;
auto fault_fs =
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
Open();
std::vector<ColumnFamilyHandle*> handles;
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
&handles));
ASSERT_EQ(2U, handles.size());
std::atomic<int> manifest_writes{0};
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
fault_fs->SetThreadLocalErrorContext(
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
/*retryable=*/false, /*has_data_loss=*/false);
fault_fs->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kWrite);
}
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_NOK(db_->DropColumnFamilies(handles));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
std::vector<ColumnFamilyDescriptor> loaded_cfs;
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
ASSERT_EQ(2U, loaded_cfs.size());
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
ASSERT_EQ("two", loaded_cfs[1].name);
for (auto* handle : handles) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
Close();
}
TEST_P(ColumnFamilyTest, InvalidTrackedTopologyChangeCreatesNoOptionsOrphan) {
db_options_.track_options_file_number_in_manifest = true;
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
const size_t before_duplicate_create = CountOptionsFiles();
ColumnFamilyHandle* duplicate = nullptr;
ASSERT_TRUE(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &duplicate)
.IsInvalidArgument());
ASSERT_EQ(nullptr, duplicate);
ASSERT_EQ(before_duplicate_create, CountOptionsFiles());
ASSERT_OK(db_->DropColumnFamily(handle));
const size_t before_duplicate_drop = CountOptionsFiles();
ASSERT_TRUE(db_->DropColumnFamily(handle).IsInvalidArgument());
ASSERT_EQ(before_duplicate_drop, CountOptionsFiles());
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
}
TEST_P(ColumnFamilyTest,
PartialMultiDropConsumesLegacyOptionsPersistenceFailure) {
auto fail_fs =
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
auto fail_env =
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
Open();
ColumnFamilyHandle* handle = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
Status s = db_->DropColumnFamilies({handle, db_->DefaultColumnFamily()});
ASSERT_TRUE(s.IsInvalidArgument());
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
Close();
AssertColumnFamilyNames({kDefaultColumnFamilyName});
}
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
// Can't accidentally add CFs to an existing DB
Open();
@@ -2264,6 +2715,16 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
Close();
ASSERT_EQ(my_fs->options_files_created.load(), 2);
Destroy();
db_options_.track_options_file_number_in_manifest = true;
my_fs->options_files_created.store(0, std::memory_order_relaxed);
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
Close();
// A new tracked DB writes its initial snapshot, one proposed batch snapshot,
// and the normal final DB::Open snapshot, independent of the number of
// missing CFs.
ASSERT_EQ(my_fs->options_files_created.load(), 3);
}
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
@@ -3167,6 +3628,32 @@ TEST_P(ColumnFamilyTest, CreateDropAndDestroy) {
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
}
TEST_P(ColumnFamilyTest, DropWaitsForUnorderedMemtableWrite) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL",
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL"},
{"DBImpl::WaitForPendingWrites:BeforeBlock",
"DBImpl::WriteImpl:BeforeUnorderedWriteMemtable"}});
db_options_.unordered_write = true;
Open();
ColumnFamilyHandle* cfh;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
port::Thread writer(
[&] { ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar")); });
TEST_SYNC_POINT(
"ColumnFamilyTest::DropWaitsForUnorderedMemtableWrite:AfterWAL");
ASSERT_OK(db_->DropColumnFamily(cfh));
writer.join();
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
ColumnFamilyHandle* cfh;
Open();
@@ -3225,6 +3712,12 @@ TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
SpecialEnv env(Env::Default());
db_options_.env = &env;
// The DB keeps its Env pointer until Close(). Close it before the local Env
// is destroyed, including when an ASSERT below exits the test early.
Defer close_db_on_exit([this] {
Close();
db_options_.env = env_;
});
db_options_.max_background_flushes = 1;
// When this option is removed, the test will need re-engineering
db_options_.background_close_inactive_wals = true;
@@ -3234,25 +3727,38 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
CreateColumnFamilies({"one"});
ASSERT_OK(Put(1, "fodor", "mirko"));
// Create an iterator holding the current super version.
Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]);
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions(), handles_[1]));
ASSERT_OK(it->status());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"DBImpl::BGWorkFlush:done",
"ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone"},
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Defer disable_sync_points(
[] { ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); });
// A flush will make `it` hold the last reference of its super version.
ASSERT_OK(Flush(1));
// Flush() can return before the background function releases all temporary
// references to the old super version.
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone");
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
{"ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush",
"DBImpl::BackgroundCallFlush:start"},
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(Put(1, "fodor", "mirko"));
ASSERT_OK(Put(0, "fodor", "mirko"));
ASSERT_OK(Put(1, "fodor", "mirko"));
// Flush jobs will close previous WAL files after finishing. By
// block flush jobs from running, we trigger a condition where
// the iterator destructor should close the WAL files.
test::SleepingBackgroundTask sleeping_task;
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
Env::Priority::HIGH);
// Make sure the task is sleeping. Otherwise, it might start to execute
// after sleeping_task.WaitUntilDone() and cause TSAN warning.
sleeping_task.WaitUntilSleeping();
// Flush jobs close previous WAL files after finishing. Keep the next flush
// from starting so the iterator destructor is responsible for closing the
// WAL. Occupying one Env worker is insufficient because the shared Env can
// have multiple HIGH-priority workers.
WriteOptions wo;
wo.sync = true;
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
@@ -3260,18 +3766,15 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
ASSERT_EQ(2, env.num_open_wal_file_.load());
// Deleting the iterator will clear its super version, triggering
// closing all files
delete it;
it.reset();
ASSERT_EQ(1, env.num_open_wal_file_.load());
sleeping_task.WakeUp();
sleeping_task.WaitUntilDone();
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush");
WaitForFlush(1);
Reopen();
ASSERT_EQ("mirko", Get(0, "fodor"));
ASSERT_EQ("mirko", Get(1, "fodor"));
db_options_.env = env_;
Close();
}
TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
@@ -3528,7 +4031,8 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
// Leave cf_paths for one column families to be empty.
// Files should be generated according to db_paths for that
// column family.
ColumnFamilyOptions cf_opt1, cf_opt2;
ColumnFamilyOptions cf_opt1;
ColumnFamilyOptions cf_opt2;
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
std::numeric_limits<uint64_t>::max());
CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2});
@@ -3553,7 +4057,8 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
Open();
// Configure Column family specific paths.
ColumnFamilyOptions cf_opt1, cf_opt2;
ColumnFamilyOptions cf_opt1;
ColumnFamilyOptions cf_opt2;
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
std::numeric_limits<uint64_t>::max());
cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1",
@@ -3669,7 +4174,9 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
// the behavior of manual flush is that it skips retaining UDTs.
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
public:
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestBbtFormatVersion) {}
ColumnFamilyRetainUDTTest()
: ColumnFamilyTestBase(
BlockBasedTableOptions::kLatestSupportedFormatVersion) {}
void SetUp() override {
db_options_.allow_concurrent_memtable_write = false;
+9 -8
View File
@@ -214,7 +214,8 @@ bool Compaction::IsBottommostLevel(
} else {
output_l0_idx = -1;
}
Slice smallest_key, largest_key;
Slice smallest_key;
Slice largest_key;
GetBoundaryKeys(vstorage, inputs, &smallest_key, &largest_key);
return !vstorage->RangeMightExistAfterSortedRun(smallest_key, largest_key,
output_level, output_l0_idx);
@@ -986,9 +987,9 @@ uint64_t Compaction::MaxInputFileNewestKeyTime(const InternalKey* start,
return newest_key_time;
}
uint64_t Compaction::MinInputFileOldestAncesterTime(
uint64_t Compaction::MinInputFileOldestAncestorTime(
const InternalKey* start, const InternalKey* end) const {
uint64_t min_oldest_ancester_time = std::numeric_limits<uint64_t>::max();
uint64_t min_oldest_ancestor_time = std::numeric_limits<uint64_t>::max();
const InternalKeyComparator& icmp =
column_family_data()->internal_comparator();
for (const auto& level_files : inputs_) {
@@ -999,14 +1000,14 @@ uint64_t Compaction::MinInputFileOldestAncesterTime(
if (end != nullptr && icmp.Compare(file->smallest, *end) > 0) {
continue;
}
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
if (oldest_ancester_time != 0) {
min_oldest_ancester_time =
std::min(min_oldest_ancester_time, oldest_ancester_time);
uint64_t oldest_ancestor_time = file->TryGetOldestAncestorTime();
if (oldest_ancestor_time != 0) {
min_oldest_ancestor_time =
std::min(min_oldest_ancestor_time, oldest_ancestor_time);
}
}
}
return min_oldest_ancester_time;
return min_oldest_ancestor_time;
}
uint64_t Compaction::MinInputFileEpochNumber() const {
+4 -2
View File
@@ -114,6 +114,8 @@ class Compaction {
// No copying allowed
Compaction(const Compaction&) = delete;
void operator=(const Compaction&) = delete;
Compaction(Compaction&&) = delete;
void operator=(Compaction&&) = delete;
~Compaction();
@@ -438,8 +440,8 @@ class Compaction {
const InternalKey* end) const;
// start and end are sub compact range. Null if no boundary.
// This is used to filter out some input files' ancester's time range.
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
// This is used to filter out some input files' ancestor's time range.
uint64_t MinInputFileOldestAncestorTime(const InternalKey* start,
const InternalKey* end) const;
// Return the minimum epoch number among
// input files' associated with this compaction
+2 -2
View File
@@ -1593,8 +1593,8 @@ TEST_P(CompactionIteratorTsGcTest, DropTombstones) {
std::string full_history_ts_low;
PutFixed64(&full_history_ts_low, 102);
RunTest(input_keys, input_values, expected_keys, expected_values,
/*last_committed_sequence=*/kMaxSequenceNumber,
/*merge_op=*/nullptr, /*compaction_filter=*/nullptr,
/*last_committed_seq=*/kMaxSequenceNumber,
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
/*bottommost_level=*/false,
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
+18 -16
View File
@@ -2160,20 +2160,20 @@ Status CompactionJob::FinishCompactionOutputFile(
if (s.ok()) {
// With accurate smallest and largest key, we can get a slightly more
// accurate oldest ancester time.
// This makes oldest ancester time in manifest more accurate than in
// accurate oldest ancestor time.
// This makes oldest ancestor time in manifest more accurate than in
// table properties. Not sure how to resolve it.
if (meta->smallest.size() > 0 && meta->largest.size() > 0) {
uint64_t refined_oldest_ancester_time;
uint64_t refined_oldest_ancestor_time;
Slice new_smallest = meta->smallest.user_key();
Slice new_largest = meta->largest.user_key();
if (!new_largest.empty() && !new_smallest.empty()) {
refined_oldest_ancester_time =
sub_compact->compaction->MinInputFileOldestAncesterTime(
refined_oldest_ancestor_time =
sub_compact->compaction->MinInputFileOldestAncestorTime(
&(meta->smallest), &(meta->largest));
if (refined_oldest_ancester_time !=
if (refined_oldest_ancestor_time !=
std::numeric_limits<uint64_t>::max()) {
meta->oldest_ancester_time = refined_oldest_ancester_time;
meta->oldest_ancestor_time = refined_oldest_ancestor_time;
}
}
}
@@ -2555,7 +2555,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
return s;
}
// Try to figure out the output file's oldest ancester time.
// Try to figure out the output file's oldest ancestor time.
int64_t temp_current_time = 0;
auto get_time_status = db_options_.clock->GetCurrentTime(&temp_current_time);
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
@@ -2565,21 +2565,22 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
get_time_status.ToString().c_str());
}
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
InternalKey tmp_start, tmp_end;
InternalKey tmp_start;
InternalKey tmp_end;
if (sub_compact->start.has_value()) {
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
}
if (sub_compact->end.has_value()) {
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
}
uint64_t oldest_ancester_time =
sub_compact->compaction->MinInputFileOldestAncesterTime(
uint64_t oldest_ancestor_time =
sub_compact->compaction->MinInputFileOldestAncestorTime(
sub_compact->start.has_value() ? &tmp_start : nullptr,
sub_compact->end.has_value() ? &tmp_end : nullptr);
if (oldest_ancester_time == std::numeric_limits<uint64_t>::max()) {
if (oldest_ancestor_time == std::numeric_limits<uint64_t>::max()) {
// TODO: fix DBSSTTest.GetTotalSstFilesSize and use
// kUnknownOldestAncesterTime
oldest_ancester_time = current_time;
// kUnknownOldestAncestorTime
oldest_ancestor_time = current_time;
}
uint64_t newest_key_time = sub_compact->compaction->MaxInputFileNewestKeyTime(
@@ -2592,7 +2593,7 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
FileMetaData meta;
meta.fd = FileDescriptor(file_number,
sub_compact->compaction->output_path_id(), 0);
meta.oldest_ancester_time = oldest_ancester_time;
meta.oldest_ancestor_time = oldest_ancestor_time;
meta.file_creation_time = current_time;
meta.epoch_number = epoch_number;
meta.temperature = temperature;
@@ -3268,7 +3269,8 @@ Status CompactionJob::PersistSubcompactionProgress(
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
}
if (s.ok()) {
s = compaction_progress_writer_->file()->Sync(opts, db_options_.use_fsync);
s = db_options_.use_fsync ? compaction_progress_writer_->file()->Fsync(opts)
: compaction_progress_writer_->file()->Sync(opts);
}
if (!s.ok()) {
+5 -4
View File
@@ -175,6 +175,7 @@ class CompactionJob {
CompactionJob(CompactionJob&& job) = delete;
CompactionJob(const CompactionJob& job) = delete;
CompactionJob& operator=(const CompactionJob& job) = delete;
CompactionJob& operator=(CompactionJob&& job) = delete;
// REQUIRED: mutex held
// Prepare for the compaction by setting up boundaries for each subcompaction
@@ -508,7 +509,7 @@ class CompactionJob {
std::atomic<int>* num_running_remote_compactions_;
// Stores the sequence number to time mapping gathered from all input files
// it also collects the smallest_seqno -> oldest_ancester_time from the SST.
// it also collects the smallest_seqno -> oldest_ancestor_time from the SST.
SeqnoToTimeMapping seqno_to_time_mapping_;
// Max seqno that can be zeroed out in last level, including for preserving
@@ -641,7 +642,7 @@ struct CompactionServiceOutputFile {
SequenceNumber largest_seqno{};
std::string smallest_internal_key;
std::string largest_internal_key;
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime;
uint64_t file_creation_time = kUnknownFileCreationTime;
uint64_t epoch_number = kUnknownEpochNumber;
std::string file_checksum = kUnknownFileChecksum;
@@ -657,7 +658,7 @@ struct CompactionServiceOutputFile {
CompactionServiceOutputFile(
const std::string& name, uint64_t size, SequenceNumber smallest,
SequenceNumber largest, std::string _smallest_internal_key,
std::string _largest_internal_key, uint64_t _oldest_ancester_time,
std::string _largest_internal_key, uint64_t _oldest_ancestor_time,
uint64_t _file_creation_time, uint64_t _epoch_number,
const std::string& _file_checksum,
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
@@ -670,7 +671,7 @@ struct CompactionServiceOutputFile {
largest_seqno(largest),
smallest_internal_key(std::move(_smallest_internal_key)),
largest_internal_key(std::move(_largest_internal_key)),
oldest_ancester_time(_oldest_ancester_time),
oldest_ancestor_time(_oldest_ancestor_time),
file_creation_time(_file_creation_time),
epoch_number(_epoch_number),
file_checksum(_file_checksum),
+6 -4
View File
@@ -322,8 +322,10 @@ class CompactionJobTestBase : public testing::Test {
assert(contents.size() > 0);
bool first_key = true;
std::string smallest, largest;
InternalKey smallest_key, largest_key;
std::string smallest;
std::string largest;
InternalKey smallest_key;
InternalKey largest_key;
SequenceNumber smallest_seqno = kMaxSequenceNumber;
SequenceNumber largest_seqno = 0;
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
@@ -387,7 +389,7 @@ class CompactionJobTestBase : public testing::Test {
edit.AddFile(
level, file_number, 0, file_size, smallest_key, largest_key,
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
oldest_blob_file_number, kUnknownOldestAncesterTime,
oldest_blob_file_number, kUnknownOldestAncestorTime,
kUnknownFileCreationTime,
versions_->GetColumnFamilySet()->GetDefault()->NewEpochNumber(),
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
@@ -1702,7 +1704,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
rnd.Uniform(kStrMaxLen)) /* smallest_internal_key */,
rnd.RandomBinaryString(
rnd.Uniform(kStrMaxLen)) /* largest_internal_key */,
rnd64.Uniform(UINT64_MAX) /* oldest_ancester_time */,
rnd64.Uniform(UINT64_MAX) /* oldest_ancestor_time */,
rnd64.Uniform(UINT64_MAX) /* file_creation_time */,
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
file_checksum /* file_checksum */,
+133 -34
View File
@@ -10,7 +10,10 @@
#include "db/compaction/compaction_outputs.h"
#include <inttypes.h>
#include "db/builder.h"
#include "logging/logging.h"
namespace ROCKSDB_NAMESPACE {
@@ -32,7 +35,7 @@ Status CompactionOutputs::Finish(
meta->fd.largest_seqno);
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
builder_->SetSeqnoTimeTableProperties(relevant_mapping,
meta->oldest_ancester_time);
meta->oldest_ancestor_time);
s = builder_->Finish();
} else {
@@ -73,7 +76,7 @@ IOStatus CompactionOutputs::WriterSyncClose(const Status& input_status,
WriteOptions(Env::IOActivity::kCompaction), opts);
if (input_status.ok() && io_s.ok()) {
StopWatch sw(clock, statistics, COMPACTION_OUTFILE_SYNC_MICROS);
io_s = file_writer_->Sync(opts, use_fsync);
io_s = use_fsync ? file_writer_->Fsync(opts) : file_writer_->Sync(opts);
}
if (input_status.ok() && io_s.ok()) {
io_s = file_writer_->Close(opts);
@@ -373,6 +376,24 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
return false;
}
void CompactionOutputs::PrepareNewOutputForFirstKey(
const CompactionIterator& c_iter) {
// A new output file starts a fresh partition-tracking window. Establish the
// partitioner fast path from this file's first key: if the partitioner can
// express its next boundary as "first key not sharing this prefix", cache
// that prefix; otherwise fall back to per-key ShouldPartition(). This is the
// only place the prefix needs to be (re)derived, since a partition boundary
// always forces a file cut, and re-deriving on non-partition cuts just
// recomputes the same still-valid prefix.
if (partitioner_) {
OptSlice prefix = partitioner_->ShouldPartitionByPrefix(c_iter.user_key());
partitioner_prefix_valid_ = prefix.has_value();
if (partitioner_prefix_valid_) {
partitioner_prefix_.assign(prefix->data(), prefix->size());
}
}
}
Status CompactionOutputs::AddToOutput(
const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
@@ -412,42 +433,117 @@ Status CompactionOutputs::AddToOutput(
if (!s.ok()) {
return s;
}
// A new output file starts a fresh partition-tracking window. Establish the
// partitioner fast path from this file's first key: if the partitioner can
// express its next boundary as "first key not sharing this prefix", cache
// that prefix; otherwise fall back to per-key ShouldPartition(). This is
// the only place the prefix needs to be (re)derived, since a partition
// boundary always forces a file cut, and re-deriving on non-partition cuts
// just recomputes the same still-valid prefix.
if (partitioner_) {
OptSlice prefix =
partitioner_->ShouldPartitionByPrefix(c_iter.user_key());
partitioner_prefix_valid_ = prefix.has_value();
if (partitioner_prefix_valid_) {
partitioner_prefix_.assign(prefix->data(), prefix->size());
}
}
}
// c_iter may emit range deletion keys, so update `last_key_for_partitioner_`
// here before returning below when `is_range_del` is true. Only needed in the
// fallback mode; the prefix fast path does not use it.
if (partitioner_ && !partitioner_prefix_valid_) {
last_key_for_partitioner_.assign(c_iter.user_key().data_,
c_iter.user_key().size_);
PrepareNewOutputForFirstKey(c_iter);
}
if (UNLIKELY(is_range_del)) {
// c_iter may emit range deletion keys, so update
// `last_key_for_partitioner_` before returning. Only needed in the fallback
// mode; the prefix fast path does not use it.
if (partitioner_ && !partitioner_prefix_valid_) {
last_key_for_partitioner_.assign(c_iter.user_key().data_,
c_iter.user_key().size_);
}
return s;
}
assert(builder_ != nullptr);
const Slice& value = c_iter.value();
s = current_output().validator.Add(key, value);
if (!s.ok()) {
return s;
bool retried_after_new_table = false;
while (true) {
s = current_output().validator.CanAdd(key);
if (!s.ok()) {
return s;
}
TableBuilderAddContext add_context;
TableBuilderAddResult add_result =
builder_->TryAdd(key, value, &add_context);
if (add_result == TableBuilderAddResult::kError) {
s = builder_->status();
if (s.ok()) {
return Status::Corruption(
"TableBuilder returned kError without a non-OK status");
}
return s;
}
if (add_result == TableBuilderAddResult::kAdded) {
current_output().validator.AddValidated(key, value);
break;
}
assert(add_result == TableBuilderAddResult::kRequiresNewTable);
s = builder_->status();
if (!s.ok()) {
return s;
}
const Compaction* compaction = compaction_;
if (compaction == nullptr) {
return Status::Corruption("Compaction output is missing compaction");
}
ColumnFamilyData* cfd = compaction->column_family_data();
if (cfd == nullptr) {
return Status::Corruption(
"Compaction output is missing column family data");
}
const bool allow_data_in_errors =
compaction->immutable_options().allow_data_in_errors;
const std::string key_for_diagnostics =
allow_data_in_errors ? key.ToString(/* hex */ true) : "hidden";
if (compaction->output_level() == 0) {
const std::string detail =
"cf: " + cfd->GetName() + ", file_number: " +
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
", key: " + key_for_diagnostics;
return Status::Corruption(
"TableBuilder requested a new table while producing level 0 output",
detail);
}
if (retried_after_new_table || builder_->IsEmpty()) {
const std::string detail =
"cf: " + cfd->GetName() + ", file_number: " +
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
", key: " + key_for_diagnostics;
return Status::Corruption(
"TableBuilder requested a new table without accepting any rows",
detail);
}
ROCKS_LOG_INFO(
compaction->immutable_options().info_log,
"[%s] Compaction output table builder requested a new table before "
"key %s for file #%" PRIu64 ". reason: %s",
cfd->GetName().c_str(), key_for_diagnostics.c_str(),
current_output().meta.fd.GetNumber(),
TableBuilderNewTableReasonToString(add_context.new_table_reason));
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
key, &c_iter, *this);
if (!s.ok()) {
return s;
}
grandparent_boundary_switched_num_ = 0;
grandparent_overlapped_bytes_ =
GetCurrentKeyGrandparentOverlappedBytes(key);
range_tombstone_lower_bound_.Clear();
s = open_file_func(*this);
if (!s.ok()) {
return s;
}
PrepareNewOutputForFirstKey(c_iter);
retried_after_new_table = true;
}
if (partitioner_ && !partitioner_prefix_valid_) {
last_key_for_partitioner_.assign(c_iter.user_key().data_,
c_iter.user_key().size_);
}
builder_->Add(key, value);
stats_.num_output_records++;
current_output_file_size_ = builder_->EstimatedFileSize();
@@ -512,10 +608,13 @@ Status CompactionOutputs::AddRangeDels(
assert(!range_del_agg.IsEmpty());
FileMetaData& meta = current_output().meta;
const Comparator* ucmp = icmp.user_comparator();
InternalKey lower_bound_buf, upper_bound_buf;
Slice lower_bound_guard, upper_bound_guard;
InternalKey lower_bound_buf;
InternalKey upper_bound_buf;
Slice lower_bound_guard;
Slice upper_bound_guard;
std::string smallest_user_key;
const Slice *lower_bound, *upper_bound;
const Slice* lower_bound;
const Slice* upper_bound;
// We first determine the internal key lower_bound and upper_bound for
// this output file. All and only range tombstones that overlap with
@@ -817,10 +916,10 @@ void CompactionOutputs::FillFilesToCutForTtl() {
*(compaction_->inputs(compaction_->num_input_levels() - 1));
for (FileMetaData* file : olevel) {
// Worth filtering out by start and end?
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
uint64_t oldest_ancestor_time = file->TryGetOldestAncestorTime();
// We put old files if they are not too small to prevent a flood
// of small files.
if (oldest_ancester_time < old_age_thres &&
if (oldest_ancestor_time < old_age_thres &&
file->fd.GetFileSize() >
compaction_->mutable_cf_options().target_file_size_base / 2) {
files_to_cut_for_ttl_.push_back(file);
+6 -1
View File
@@ -245,6 +245,11 @@ class CompactionOutputs {
// before processing the current key in compaction iterator.
bool ShouldStopBefore(const CompactionIterator& c_iter);
// Re-establishes per-file state that depends on the first key accepted by a
// freshly opened builder. This keeps caller-requested file cuts equivalent to
// RocksDB's built-in file-boundary decisions for partition tracking.
void PrepareNewOutputForFirstKey(const CompactionIterator& c_iter);
void Cleanup() {
if (builder_ != nullptr) {
// May happen if we get a shutdown call in the middle of compaction
@@ -371,7 +376,7 @@ class CompactionOutputs {
// repetitive comparison in ShouldStopBefore()
const InternalKey* local_output_split_key_ = nullptr;
// Some identified files with old oldest ancester time and the range should be
// Some identified files with old oldest ancestor time and the range should be
// isolated out so that the output file(s) in that range can be merged down
// for TTL and clear the timestamps for the range.
std::vector<FileMetaData*> files_to_cut_for_ttl_;
+20 -9
View File
@@ -236,7 +236,10 @@ void CompactionPicker::GetRange(const CompactionInputFiles& inputs1,
} else if (inputs2.empty()) {
GetRange(inputs1, smallest, largest);
} else {
InternalKey smallest1, smallest2, largest1, largest2;
InternalKey smallest1;
InternalKey smallest2;
InternalKey largest1;
InternalKey largest2;
GetRange(inputs1, &smallest1, &largest1);
GetRange(inputs2, &smallest2, &largest2);
*smallest =
@@ -286,7 +289,8 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
return true;
}
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
// Keep expanding inputs until we are sure that there is a "clean cut"
// boundary between the files in input and the surrounding files.
@@ -360,7 +364,8 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
// TODO: Intra L0 compactions can have the ranges overlapped, but the input
// files cannot be overlapped in the order of L0 files.
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
GetRange(inputs, &smallest, &largest, Compaction::kInvalidLevel);
if (proximal_level != Compaction::kInvalidLevel) {
if (ioptions_.compaction_style == kCompactionStyleUniversal) {
@@ -370,7 +375,8 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
return true;
}
} else {
InternalKey proximal_smallest, proximal_largest;
InternalKey proximal_smallest;
InternalKey proximal_largest;
GetRange(inputs, &proximal_smallest, &proximal_largest, level);
if (RangeOverlapWithCompaction(proximal_smallest.user_key(),
proximal_largest.user_key(),
@@ -546,7 +552,8 @@ bool CompactionPicker::SetupOtherInputs(
assert(vstorage->NumLevelFiles(l) == 0);
}
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
// Get the range one last time.
GetRange(*inputs, &smallest, &largest);
@@ -579,7 +586,8 @@ bool CompactionPicker::SetupOtherInputs(
CompactionInputFiles expanded_inputs;
expanded_inputs.level = input_level;
// Get closed interval of output level
InternalKey all_start, all_limit;
InternalKey all_start;
InternalKey all_limit;
GetRange(*inputs, *output_level_inputs, &all_start, &all_limit);
bool try_overlapping_inputs = true;
if (only_expand_towards_right) {
@@ -605,7 +613,8 @@ bool CompactionPicker::SetupOtherInputs(
if (try_overlapping_inputs && expanded_inputs.size() > inputs->size() &&
!AreFilesInCompaction(expanded_inputs.files) &&
output_level_inputs_size + expanded_inputs_size < limit) {
InternalKey new_start, new_limit;
InternalKey new_start;
InternalKey new_limit;
GetRange(expanded_inputs, &new_start, &new_limit);
CompactionInputFiles expanded_output_level_inputs;
expanded_output_level_inputs.level = output_level;
@@ -655,7 +664,8 @@ void CompactionPicker::GetGrandparents(
VersionStorageInfo* vstorage, const CompactionInputFiles& inputs,
const CompactionInputFiles& output_level_inputs,
std::vector<FileMetaData*>* grandparents) {
InternalKey start, limit;
InternalKey start;
InternalKey limit;
GetRange(inputs, output_level_inputs, &start, &limit);
// Compute the set of grandparent files that overlap this compaction
// (parent == level+1; grandparent == level+2 or the first
@@ -1303,7 +1313,8 @@ bool CompactionPicker::GetOverlappingL0Files(
// Two level 0 compaction won't run at the same time, so don't need to worry
// about files on level 0 being compacted.
assert(level0_compactions_in_progress()->empty());
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
GetRange(*start_level_inputs, &smallest, &largest);
// Note that the next call will discard the file we placed in
// c->inputs_[0] earlier and replace it with an overlapping set
+1 -1
View File
@@ -276,7 +276,7 @@ class NullCompactionPicker : public CompactionPicker {
NullCompactionPicker(const ImmutableOptions& ioptions,
const InternalKeyComparator* icmp)
: CompactionPicker(ioptions, icmp) {}
virtual ~NullCompactionPicker() {}
~NullCompactionPicker() override = default;
// Always return "nullptr"
Compaction* PickCompaction(
+8 -6
View File
@@ -105,9 +105,10 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
TableReader* reader = f->fd.pinned_reader.Get();
if (reader != nullptr && reader->GetTableProperties() != nullptr) {
uint64_t newest_key_time = f->TryGetNewestKeyTime();
uint64_t creation_time = reader->GetTableProperties()->creation_time;
uint64_t oldest_ancestor_time =
reader->GetTableProperties()->oldest_ancestor_time;
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
? creation_time
? oldest_ancestor_time
: newest_key_time;
if (est_newest_key_time == kUnknownNewestKeyTime ||
est_newest_key_time >= (current_time - mutable_cf_options.ttl)) {
@@ -165,13 +166,13 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
for (const auto& f : inputs[0].files) {
assert(f);
uint64_t newest_key_time = f->TryGetNewestKeyTime();
uint64_t creation_time = 0;
uint64_t oldest_ancestor_time = 0;
TableReader* reader = f->fd.pinned_reader.Get();
if (reader != nullptr && reader->GetTableProperties() != nullptr) {
creation_time = reader->GetTableProperties()->creation_time;
oldest_ancestor_time = reader->GetTableProperties()->oldest_ancestor_time;
}
uint64_t est_newest_key_time = newest_key_time == kUnknownNewestKeyTime
? creation_time
? oldest_ancestor_time
: newest_key_time;
ROCKS_LOG_BUFFER(log_buffer,
"[%s] FIFO compaction: picking file %" PRIu64
@@ -225,7 +226,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
vstorage->LevelFiles(last_level);
// Compute effective size and limit for comparison.
uint64_t effective_size, effective_max;
uint64_t effective_size;
uint64_t effective_max;
GetEffectiveSizeAndLimit(fifo_opts, total_size,
vstorage->GetBlobStats().total_file_size,
&effective_size, &effective_max);
+6 -3
View File
@@ -393,7 +393,8 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
}
size_t start_index = vstorage_->FilesByCompactionPri(start_level_)[0];
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
// Constraint 4 (No need to check again later)
compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest);
CompactionInputFiles output_level_inputs;
@@ -675,7 +676,8 @@ bool LevelCompactionBuilder::TryPickL0TrivialMove() {
const std::vector<FileMetaData*>& level_files =
vstorage_->LevelFiles(start_level_);
InternalKey my_smallest, my_largest;
InternalKey my_smallest;
InternalKey my_largest;
for (auto it = level_files.rbegin(); it != level_files.rend(); ++it) {
CompactionInputFiles output_level_inputs;
output_level_inputs.level = output_level_;
@@ -886,7 +888,8 @@ bool LevelCompactionBuilder::PickFileToCompact() {
// Note we rely on ExpandInputsToCleanCut() to tell us whether any output-
// level files are locked, not just the extra ones pulled in for user-key
// overlap.
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest);
CompactionInputFiles output_level_inputs;
output_level_inputs.level = output_level_;
+93 -85
View File
@@ -120,7 +120,7 @@ class CompactionPickerTestBase : public testing::Test {
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
size_t compensated_file_size = 0, bool marked_for_compact = false,
Temperature temperature = Temperature::kUnknown,
uint64_t oldest_ancestor_time = kUnknownOldestAncesterTime,
uint64_t oldest_ancestor_time = kUnknownOldestAncestorTime,
uint64_t newest_key_time = kUnknownNewestKeyTime,
Slice ts_of_smallest = Slice(), Slice ts_of_largest = Slice(),
uint64_t epoch_number = kUnknownEpochNumber) {
@@ -162,15 +162,15 @@ class CompactionPickerTestBase : public testing::Test {
FileMetaData* f = new FileMetaData(
file_number, path_id, file_size, smallest_ikey, largest_ikey,
smallest_seq, largest_seq, marked_for_compact, temperature,
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
kInvalidBlobFileNumber, kUnknownOldestAncestorTime,
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
true /* user_defined_timestamps_persisted */, "" /* min timestamp */,
"" /* max timestamp */);
f->compensated_file_size =
(compensated_file_size != 0) ? compensated_file_size : file_size;
// oldest_ancester_time is only used if newest_key_time is not available
f->oldest_ancester_time = oldest_ancestor_time;
// oldest_ancestor_time is only used if newest_key_time is not available
f->oldest_ancestor_time = oldest_ancestor_time;
// Set min/max timestamps for UDT support
if (!ts_of_smallest.empty()) {
f->min_timestamp = ts_of_smallest.ToString();
@@ -325,7 +325,7 @@ class CompactionPickerU64TsTest : public CompactionPickerTestBase {
/*smallest_seq=*/10, /*largest_seq=*/40,
/*compensated_file_size=*/1000,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
std::string full_history_ts_low = MakeU64Timestamp(full_history_ts_low_val);
@@ -350,12 +350,12 @@ class CompactionPickerU64TsTest : public CompactionPickerTestBase {
/*smallest_seq=*/100, /*largest_seq=*/100,
/*compensated_file_size=*/file_size,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts1, ts2);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts1, ts2);
Add(0, 2U, "150", "250", file_size, /*path_id=*/0,
/*smallest_seq=*/200, /*largest_seq=*/200,
/*compensated_file_size=*/file_size,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts1, ts2);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts1, ts2);
}
};
@@ -1452,14 +1452,14 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
static_cast<uint64_t>(current_time) - kColdThreshold;
Add(0 /* level */, 4U /* file_number */, "260", "300", 1 * kFileSize, 0,
2500, 2600, 0, true, Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: threshold_time - 2000 /* oldest_ancestor_time */,
newestKeyTimeKnown ? threshold_time - 2000
: kUnknownNewestKeyTime /* newest_key_time */);
// Qualifies for compaction to kCold.
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
newestKeyTimeKnown ? threshold_time - 3000
: kUnknownNewestKeyTime /* newest_key_time */);
UpdateVersionStorageInfo();
@@ -1504,32 +1504,32 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
static_cast<uint64_t>(current_time) - kColdThreshold;
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: static_cast<uint64_t>(current_time) - 100,
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
: kUnknownNewestKeyTime);
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
// The following two files qualify for compaction to kCold.
// But only the last two should be included to respect
// `max_compaction_bytes`.
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
UpdateVersionStorageInfo();
@@ -1574,30 +1574,30 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
static_cast<uint64_t>(current_time) - kColdThreshold;
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: static_cast<uint64_t>(current_time) - 100,
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
: kUnknownNewestKeyTime);
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
// The following two files qualify for compaction to kCold.
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
Temperature::kCold,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
UpdateVersionStorageInfo();
@@ -1642,30 +1642,30 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
static_cast<uint64_t>(current_time) - kColdThreshold;
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: static_cast<uint64_t>(current_time) - 100,
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
: kUnknownNewestKeyTime);
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time + 100,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time + 100,
newestKeyTimeKnown ? threshold_time + 100 : kUnknownNewestKeyTime);
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 2000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 2000,
newestKeyTimeKnown ? threshold_time - 2000 : kUnknownNewestKeyTime);
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
Temperature::kCold,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 3000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 3000,
newestKeyTimeKnown ? threshold_time - 3000 : kUnknownNewestKeyTime);
// Qualifies for compaction to kCold.
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 4000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 4000,
newestKeyTimeKnown ? threshold_time - 4000 : kUnknownNewestKeyTime);
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
Temperature::kCold,
newestKeyTimeKnown ? kUnknownOldestAncesterTime : threshold_time - 5000,
newestKeyTimeKnown ? kUnknownOldestAncestorTime : threshold_time - 5000,
newestKeyTimeKnown ? threshold_time - 5000 : kUnknownNewestKeyTime);
UpdateVersionStorageInfo();
@@ -1714,36 +1714,36 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
static_cast<uint64_t>(current_time) - kWarmThreshold;
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: static_cast<uint64_t>(current_time) - 100,
newestKeyTimeKnown ? static_cast<uint64_t>(current_time) - 100
: kUnknownNewestKeyTime);
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: hot_threshold_time + 100,
newestKeyTimeKnown ? hot_threshold_time + 100 : kUnknownNewestKeyTime);
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: hot_threshold_time - 200,
newestKeyTimeKnown ? hot_threshold_time - 200 : kUnknownNewestKeyTime);
// Qualifies for Hot
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: warm_threshold_time - 100,
newestKeyTimeKnown ? warm_threshold_time - 100 : kUnknownNewestKeyTime);
// Qualifies for Warm
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: warm_threshold_time - 4000,
newestKeyTimeKnown ? warm_threshold_time - 4000
: kUnknownNewestKeyTime);
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
Temperature::kUnknown,
newestKeyTimeKnown ? kUnknownOldestAncesterTime
newestKeyTimeKnown ? kUnknownOldestAncestorTime
: warm_threshold_time - 5000,
newestKeyTimeKnown ? warm_threshold_time - 5000
: kUnknownNewestKeyTime);
@@ -2490,7 +2490,7 @@ TEST_F(CompactionPickerTest, FileTtlBoosterLargeNumLevels) {
FileTtlBooster booster(kCurrentTime, /*ttl=*/2048,
/*num_non_empty_levels=*/100, /*level=*/1);
FileMetaData meta;
meta.oldest_ancester_time = kCurrentTime - 1023;
meta.oldest_ancestor_time = kCurrentTime - 1023;
ASSERT_EQ(1, booster.GetBoostScore(&meta));
}
@@ -2509,47 +2509,47 @@ TEST_F(CompactionPickerTest, FileTtlBooster) {
FileTtlBooster booster(kCurrentTime, 2048, 7, 3);
// Not triggering if the file is younger than ttl/2
meta.oldest_ancester_time = kCurrentTime - 1023;
meta.oldest_ancestor_time = kCurrentTime - 1023;
ASSERT_EQ(1, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime - 1024;
meta.oldest_ancestor_time = kCurrentTime - 1024;
ASSERT_EQ(1, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime + 10;
meta.oldest_ancestor_time = kCurrentTime + 10;
ASSERT_EQ(1, booster.GetBoostScore(&meta));
// Within one boosting step
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 6);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 6);
ASSERT_EQ(1, booster.GetBoostScore(&meta));
// One boosting step
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 7);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 7);
ASSERT_EQ(2, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 8);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 8);
ASSERT_EQ(2, booster.GetBoostScore(&meta));
// Multiple boosting steps
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 30);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 30);
ASSERT_EQ(5, booster.GetBoostScore(&meta));
// Very high boosting steps
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 700);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 120 + 700);
ASSERT_EQ(101, booster.GetBoostScore(&meta));
}
{
// Test second last level
FileTtlBooster booster(kCurrentTime, 2048, 7, 5);
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480);
ASSERT_EQ(1, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480 + 60);
ASSERT_EQ(3, booster.GetBoostScore(&meta));
}
{
// Test last level
FileTtlBooster booster(kCurrentTime, 2048, 7, 6);
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480);
ASSERT_EQ(1, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
meta.oldest_ancestor_time = kCurrentTime - (1024 + 480 + 60);
ASSERT_EQ(1, booster.GetBoostScore(&meta));
meta.oldest_ancester_time = kCurrentTime - 3000;
meta.oldest_ancestor_time = kCurrentTime - 3000;
ASSERT_EQ(1, booster.GetBoostScore(&meta));
}
}
@@ -3577,21 +3577,21 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap) {
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
/*marked_for_compact*/ false, /* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 3);
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 2);
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 1);
@@ -3619,7 +3619,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap) {
// Simulate a flush and mark the file for compaction
Add(0, 7U, "150", "200", kFileSize, 0, 551, 600, 0, true,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 4);
@@ -3647,7 +3647,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap2) {
// Mark file number 4 for compaction
Add(0, 4U, "260", "300", 4 * kFileSize, 0, 260, 300, 0, true,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 1);
@@ -3674,14 +3674,14 @@ TEST_F(CompactionPickerTest, UniversalMarkedCompactionFullOverlap2) {
AddVersionStorage();
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
/*marked_for_compact*/ false, /* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 3);
Add(0, 2U, "201", "250", 2 * kFileSize, 0, 401, 450,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 2);
@@ -3859,27 +3859,27 @@ TEST_F(CompactionPickerTest, UniversalMarkedL0Overlap2) {
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 260, 300,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 4);
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 201, 250, 0, true,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 3);
Add(0, 3U, "301", "350", 4 * kFileSize, 0, 101, 150,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 2);
Add(0, 6U, "501", "750", 8 * kFileSize, 0, 50, 100,
/*compensated_file_size*/ 0, /*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 1);
@@ -3906,14 +3906,14 @@ TEST_F(CompactionPickerTest, UniversalMarkedL0Overlap2) {
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550, /*compensated_file_size*/ 0,
/*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 6);
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450, /*compensated_file_size*/ 0,
/*marked_for_compact*/ false,
/* temperature*/ Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime,
/*ts_of_smallest*/ Slice(), /*ts_of_largest*/ Slice(),
/*epoch_number*/ 5);
@@ -4149,7 +4149,7 @@ TEST_F(CompactionPickerU64TsTest, Overlap) {
/*file_size=*/1U, /*path_id=*/0,
/*smallest_seq=*/100, /*largest_seq=*/100, /*compensated_file_size=*/0,
/*marked_for_compact=*/false, /*temperature=*/Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime, ts1, ts2);
UpdateVersionStorageInfo();
}
@@ -4231,12 +4231,12 @@ TEST_F(CompactionPickerU64TsTest, CannotTrivialMoveUniversal) {
Add(1, 1U, "150", "150", kFileSize, /*path_id=*/0, /*smallest_seq=*/100,
/*largest_seq=*/100, /*compensated_file_size=*/kFileSize,
/*marked_for_compact=*/false, Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime, ts1, ts2);
Add(2, 2U, "150", "150", kFileSize, /*path_id=*/0, /*smallest_seq=*/100,
/*largest_seq=*/100, /*compensated_file_size=*/kFileSize,
/*marked_for_compact=*/false, Temperature::kUnknown,
/*oldest_ancestor_time*/ kUnknownOldestAncesterTime,
/*oldest_ancestor_time*/ kUnknownOldestAncestorTime,
/*newest_key_time*/ kUnknownNewestKeyTime, ts3, ts4);
UpdateVersionStorageInfo();
@@ -5033,12 +5033,12 @@ TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
// Create L0 files with overlapping ranges
// File 1: newest regular file (epoch 5), keys [100, 200]
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
// File 2: standalone range deletion (epoch 4), keys [150, 250]
// This file should be marked as having only range deletions
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
// Manually set file 2 as standalone range deletion
FileMetaData* range_del_file = file_map_[2U].first;
@@ -5169,7 +5169,7 @@ TEST_F(CompactionPickerU64TsTest,
/*smallest_seq=*/10, /*largest_seq=*/40,
/*compensated_file_size=*/1000,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
// Update version storage with empty full_history_ts_low
UpdateVersionStorageInfo();
@@ -5209,7 +5209,7 @@ TEST_F(CompactionPickerU64TsTest,
/*smallest_seq=*/10, /*largest_seq=*/40,
/*compensated_file_size=*/1000,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
// Simulate a file whose min/max timestamp was never recorded.
ASSERT_EQ(1U, vstorage_->LevelFiles(5).size());
@@ -5249,7 +5249,7 @@ TEST_F(CompactionPickerU64TsTest,
/*smallest_seq=*/10, /*largest_seq=*/40,
/*compensated_file_size=*/1000,
/*marked_for_compact=*/false, Temperature::kUnknown,
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, ts_small, ts_large);
kUnknownOldestAncestorTime, kUnknownNewestKeyTime, ts_small, ts_large);
// Simulate a file whose min/max timestamp was never recorded.
ASSERT_EQ(1U, vstorage_->LevelFiles(5).size());
@@ -5715,13 +5715,13 @@ TEST_F(CompactionPickerTest, FIFOTTLBlobEstimationSingleLevel) {
uint64_t recent_time = static_cast<uint64_t>(time(nullptr));
Add(0, 1U, "100", "200", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
Add(0, 2U, "200", "300", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
Add(0, 3U, "300", "400", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
Add(0, 4U, "400", "500", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
if (blob_total > 0) {
AddBlobFile(100, blob_total / 2);
AddBlobFile(101, blob_total / 2);
@@ -5794,13 +5794,13 @@ TEST_F(CompactionPickerTest, FIFOTTLBlobEstimationMultiLevel) {
uint64_t recent_time = static_cast<uint64_t>(time(nullptr));
// L0 files: 2 recent, 2 expired
Add(0, 1U, "100", "200", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
Add(0, 2U, "200", "300", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, recent_time);
Temperature::kUnknown, kUnknownOldestAncestorTime, recent_time);
Add(0, 3U, "300", "400", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
Add(0, 4U, "400", "500", 50 * 1024, 0, 100, 100, 0, false,
Temperature::kUnknown, kUnknownOldestAncesterTime, 1);
Temperature::kUnknown, kUnknownOldestAncestorTime, 1);
// L2 legacy migration file
Add(2, 10U, "100", "600", 200 * 1024);
// Blob files (associated with ALL levels)
@@ -5825,8 +5825,8 @@ TEST_F(CompactionPickerTest, FIFOBlobAwareSizeDropping) {
// Sub-case 1: Single-level. SST = 200KB, blob = 500MB, limit = 200MB.
// effective_size ~ 500MB >> 200MB -> drops from L0.
{
SetupFIFORatioBased(/*max_table=*/200ULL * 1024 * 1024,
/*max_data=*/200ULL * 1024 * 1024,
SetupFIFORatioBased(/*max_table_files_size=*/200ULL * 1024 * 1024,
/*max_data_files_size=*/200ULL * 1024 * 1024,
/*trigger=*/4,
/*allow_compaction=*/true,
/*use_kv_ratio=*/false);
@@ -5880,8 +5880,8 @@ TEST_F(CompactionPickerTest, FIFOBlobAwareSizeDropping) {
// Sub-case 3: Under limit. SST = 256KB, blob = 200MB, limit = 1GB.
// effective_size ~ 200MB < 1GB -> no dropping.
{
SetupFIFORatioBased(/*max_table=*/1ULL * 1024 * 1024 * 1024,
/*max_data=*/1ULL * 1024 * 1024 * 1024,
SetupFIFORatioBased(/*max_table_files_size=*/1ULL * 1024 * 1024 * 1024,
/*max_data_files_size=*/1ULL * 1024 * 1024 * 1024,
/*trigger=*/4,
/*allow_compaction=*/true,
/*use_kv_ratio=*/true);
@@ -6034,7 +6034,9 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
/*existing_snapshots=*/{}, /*snapshot_checker=*/nullptr,
vstorage_.get(), &log_buffer_, /*full_history_ts_low=*/""));
if (!compaction) return {};
if (!compaction) {
return {};
}
if (out_reason) {
*out_reason = compaction->compaction_reason();
@@ -6080,7 +6082,9 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
size_t insert_pos = oldest_input_pos;
for (size_t idx : sorted_indices) {
if (idx < oldest_input_pos) insert_pos--;
if (idx < oldest_input_pos) {
insert_pos--;
}
}
insert_pos = std::min(insert_pos, files.size());
files.insert(files.begin() + insert_pos,
@@ -6104,10 +6108,13 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
sizes.push_back(f.size);
}
}
if (sizes.empty()) return {0, 0, 0, 0.0, 0.0};
if (sizes.empty()) {
return {0, 0, 0, 0.0, 0.0};
}
uint64_t sum = 0;
uint64_t min_s = UINT64_MAX, max_s = 0;
uint64_t min_s = UINT64_MAX;
uint64_t max_s = 0;
for (uint64_t s : sizes) {
sum += s;
min_s = std::min(min_s, s);
@@ -6244,7 +6251,8 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
if (!inputs.empty() && reason == CompactionReason::kFIFOReduceNumFiles) {
// Compute target from the picker's perspective: we need to estimate
// it the same way the picker does.
uint64_t total_sst = 0, total_blob = 0;
uint64_t total_sst = 0;
uint64_t total_blob = 0;
for (const auto& f : files) {
total_sst += f.size;
total_blob += f.blob_size;
+4 -2
View File
@@ -573,7 +573,8 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
auto comparator = icmp_->user_comparator();
int first_iter = 1;
InputFileInfo prev, curr;
InputFileInfo prev;
InputFileInfo curr;
SmallestKeyHeap smallest_key_priority_q =
create_level_heap(c, icmp_->user_comparator());
@@ -1421,7 +1422,8 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
// Since we need to go from lower level up and this is in the reverse
// order, compared to level order, we first write to an reversed
// data structure and finally copy them to compaction inputs.
InternalKey smallest, largest;
InternalKey smallest;
InternalKey largest;
picker_->GetRange(second_last_level_inputs, &smallest, &largest);
std::vector<CompactionInputFiles> inputs_reverse;
for (auto it = ++(++sorted_runs_.rbegin()); it != sorted_runs_.rend(); it++) {
+9 -21
View File
@@ -271,7 +271,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
file.smallest_seqno, file.largest_seqno);
meta.smallest.DecodeFrom(file.smallest_internal_key);
meta.largest.DecodeFrom(file.largest_internal_key);
meta.oldest_ancester_time = file.oldest_ancester_time;
meta.oldest_ancestor_time = file.oldest_ancestor_time;
meta.file_creation_time = file.file_creation_time;
meta.epoch_number = file.epoch_number;
meta.file_checksum = file.file_checksum;
@@ -469,7 +469,7 @@ Status CompactionServiceCompactionJob::Run() {
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
meta.fd.smallest_seqno, meta.fd.largest_seqno,
meta.smallest.Encode().ToString(), meta.largest.Encode().ToString(),
meta.oldest_ancester_time, meta.file_creation_time, meta.epoch_number,
meta.oldest_ancestor_time, meta.file_creation_time, meta.epoch_number,
meta.file_checksum, meta.file_checksum_func_name,
output_file.validator.GetHash(), meta.marked_for_compaction,
meta.unique_id, *output_file.table_properties,
@@ -601,8 +601,9 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct CompactionServiceOutputFile, largest_internal_key),
OptionType::kEncodedString, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
// Misspelled key kept for compatibility with other versions
{"oldest_ancester_time",
{offsetof(struct CompactionServiceOutputFile, oldest_ancester_time),
{offsetof(struct CompactionServiceOutputFile, oldest_ancestor_time),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"file_creation_time",
@@ -920,24 +921,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct InternalStats::CompactionStats, count),
OptionType::kUInt64T, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
// The remote worker may serialize a different number of
// CompactionReason counts than this primary expects (RocksDB versions
// can disagree on kNumOfReasons). Deserialization tolerates the
// mismatch: extra trailing elements are dropped, missing ones are
// zero-filled.
//
// The serialized width is intentionally kept at kNumOfReasons - 1 (the
// reduced width) so this change does NOT alter what is written -- only
// the read path becomes tolerant. Widening the serialized width to the
// full kNumOfReasons is a separate, later change that must not land
// until this tolerant read has been deployed to every primary,
// including rollback targets.
{"counts",
SizeTolerantDeserializeArray<
int, static_cast<int>(CompactionReason::kNumOfReasons) - 1>(
offsetof(struct InternalStats::CompactionStats, counts),
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
{0, OptionType::kInt})},
{"counts", SizeTolerantDeserializeArray<
int, static_cast<int>(CompactionReason::kNumOfReasons)>(
offsetof(struct InternalStats::CompactionStats, counts),
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
{0, OptionType::kInt})},
};
static std::unordered_map<std::string, OptionTypeInfo>
+12 -2
View File
@@ -1900,7 +1900,7 @@ TEST_F(CompactionServiceTest, InvalidResultFallsBackToLocal) {
TEST_F(CompactionServiceTest, CompatCheckCountsWidthTolerance) {
// Number of per-CompactionReason counts this binary serializes and expects.
constexpr size_t kExpected =
static_cast<size_t>(CompactionReason::kNumOfReasons) - 1;
static_cast<size_t>(CompactionReason::kNumOfReasons);
constexpr size_t kLastIndex = kExpected - 1;
constexpr int kFirstCount = 3;
constexpr int kLastCount = 5;
@@ -2452,6 +2452,8 @@ TEST_F(CompactionServiceTest, RemoteCompactionManifestFloorKillSwitchOff) {
Options options = CurrentOptions();
options.num_levels = 7;
options.max_open_files = -1;
options.open_files_async = true;
options.skip_stats_update_on_db_open = true;
options.level0_file_num_compaction_trigger = 100; // no auto compaction
options.remote_compaction_manifest_floor = false; // kill switch OFF
ReopenWithCompactionService(&options);
@@ -2464,8 +2466,15 @@ TEST_F(CompactionServiceTest, RemoteCompactionManifestFloorKillSwitchOff) {
ASSERT_OK(Flush()); // L0: Delete(k)
// Reopen so the MANIFEST is rewritten as current state before we perturb it.
SyncPoint::GetInstance()->LoadDependency(
{{"DBImpl::BGWorkAsyncFileOpen:Done",
"CompactionServiceTest::ManifestFloorKillSwitchOff:OpenDone"}});
SyncPoint::GetInstance()->EnableProcessing();
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "cf_1", "cf_2", "cf_3"},
options);
TEST_SYNC_POINT("CompactionServiceTest::ManifestFloorKillSwitchOff:OpenDone");
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
GetCompactionService()->SetCanceled(false);
bool floor_provided_seen = true;
@@ -2774,7 +2783,8 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
ASSERT_EQ(dbname_, info.db_name);
std::string db_id, db_session_id;
std::string db_id;
std::string db_session_id;
ASSERT_OK(db_->GetDbIdentity(db_id));
ASSERT_EQ(db_id, info.db_id);
ASSERT_OK(db_->GetDbSessionId(db_session_id));
+3 -3
View File
@@ -68,11 +68,11 @@ class FileTtlBooster {
if (!enabled_) {
return 1;
}
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
if (oldest_ancester_time >= current_time_) {
uint64_t oldest_ancestor_time = f->TryGetOldestAncestorTime();
if (oldest_ancestor_time >= current_time_) {
return 1;
}
uint64_t age = current_time_ - oldest_ancester_time;
uint64_t age = current_time_ - oldest_ancestor_time;
if (age > boost_age_start_) {
// Use integer just for convenience.
// We could make all file_to_order double if we want.
+3
View File
@@ -168,6 +168,9 @@ class SubcompactionState {
? &proximal_level_outputs_
: &compaction_outputs_;
}
SubcompactionState& operator=(SubcompactionState&&) = delete;
~SubcompactionState() = default;
// Add all the new files from this compaction to version_edit
void AddOutputsEdit(VersionEdit* out_edit) const {
+10 -6
View File
@@ -389,7 +389,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
// the range dels in both cold and hot SSTs
auto snap = db_->GetSnapshot();
latest_cold_seq = seq_history[2];
std::string start = Key(25), end = Key(35);
std::string start = Key(25);
std::string end = Key(35);
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
ASSERT_OK(Flush());
@@ -620,7 +621,8 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
hot_start = Key(50);
hot_end = Key(100);
}
std::string start = Key(1), end = Key(70);
std::string start = Key(1);
std::string end = Key(70);
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
ASSERT_OK(Flush());
@@ -1268,7 +1270,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
// the range dels in bottommost
auto snap = db_->GetSnapshot();
std::string start = Key(25), end = Key(35);
std::string start = Key(25);
std::string end = Key(35);
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
// add one small key and large key in the input level, to make sure it's able
@@ -3045,9 +3048,10 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
verify_db();
// Rewrite the middle file only. File endpoints should not change.
std::string begin_key_buf = Key(kNumKeysPerFile + 1),
end_key_buf = Key(kNumKeysPerFile + 2);
Slice begin_key(begin_key_buf), end_key(end_key_buf);
std::string begin_key_buf = Key(kNumKeysPerFile + 1);
std::string end_key_buf = Key(kNumKeysPerFile + 2);
Slice begin_key(begin_key_buf);
Slice end_key(end_key_buf);
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
&end_key));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
+3 -2
View File
@@ -315,8 +315,9 @@ class ComparatorDBTest
INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest,
testing::Values(test::kDefaultFormatVersion));
INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest,
testing::Values(kLatestBbtFormatVersion));
INSTANTIATE_TEST_CASE_P(
FormatLatest, ComparatorDBTest,
testing::Values(BlockBasedTableOptions::kLatestSupportedFormatVersion));
TEST_P(ComparatorDBTest, Bytewise) {
for (int rand_seed = 301; rand_seed < 306; rand_seed++) {
+12 -6
View File
@@ -143,7 +143,8 @@ class CorruptionTest : public testing::Test {
}
void Build(int n, int start, int flush_every) {
std::string key_space, value_space;
std::string key_space;
std::string value_space;
WriteBatch batch;
for (int i = 0; i < n; i++) {
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
@@ -773,7 +774,8 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
// Write must eventually fail because of corrupted table
Status s;
std::string tmp1, tmp2;
std::string tmp1;
std::string tmp2;
bool failed = false;
for (int i = 0; i < 10000; i++) {
s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2));
@@ -792,7 +794,8 @@ TEST_F(CorruptionTest, UnrelatedKeys) {
Corrupt(kTableFile, 100, 1);
ASSERT_NOK(db_->VerifyChecksum());
std::string tmp1, tmp2;
std::string tmp1;
std::string tmp2;
ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2)));
std::string v;
ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
@@ -940,7 +943,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
db_.reset();
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_OK(DB::Open(options, dbname_, &db_));
std::string start, end;
std::string start;
std::string end;
assert(db_ != nullptr); // suppress false clang-analyze report
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
Key(3, &start), Key(7, &end)));
@@ -976,7 +980,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
Build(10, 0, 0);
std::string start, end;
std::string start;
std::string end;
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
Key(5, &start), Key(15, &end)));
auto snap = db_->GetSnapshot();
@@ -1012,7 +1017,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
ASSERT_OK(DestroyDB(dbname_, options));
ASSERT_OK(DB::Open(options, dbname_, &db_));
assert(db_ != nullptr); // suppress false clang-analyze report
std::string start, end;
std::string start;
std::string end;
Build(10);
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
Key(3, &start), Key(7, &end)));
+188 -12
View File
@@ -13,6 +13,9 @@
#include "db/log_writer.h"
#include "db/manifest_ops.h"
#include "db/version_edit.h"
#ifdef ROCKSDB_IOURING_PRESENT
#include "env/io_posix.h"
#endif
#include "file/writable_file_writer.h"
#include "options/options_helper.h"
#include "port/stack_trace.h"
@@ -23,6 +26,7 @@
#include "rocksdb/table.h"
#include "rocksdb/utilities/checkpoint.h"
#include "rocksdb/utilities/debug.h"
#include "rocksdb/write_buffer_manager.h"
#include "table/block_based/block_based_table_reader.h"
#include "table/block_based/block_builder.h"
#include "table/format.h"
@@ -85,11 +89,9 @@ class MyFlushBlockPolicyFactory : public FlushBlockPolicyFactory {
explicit MyFlushBlockPolicyFactory(const int num_keys_in_block)
: num_keys_in_block_(num_keys_in_block) {}
virtual const char* Name() const override {
return "MyFlushBlockPolicyFactory";
}
const char* Name() const override { return "MyFlushBlockPolicyFactory"; }
virtual FlushBlockPolicy* NewFlushBlockPolicy(
FlushBlockPolicy* NewFlushBlockPolicy(
const BlockBasedTableOptions& /*table_options*/,
const BlockBuilder& data_block_builder) const override {
return new MyFlushBlockPolicy(num_keys_in_block_, data_block_builder);
@@ -1233,7 +1235,10 @@ TEST_F(DBBasicTest, EnableDirectIOWithZeroBuf) {
TEST_F(DBBasicTest, UniqueSession) {
Options options = CurrentOptions();
std::string sid1, sid2, sid3, sid4;
std::string sid1;
std::string sid2;
std::string sid3;
std::string sid4;
ASSERT_OK(db_->GetDbSessionId(sid1));
Reopen(options);
@@ -1684,7 +1689,8 @@ TEST_P(DBBasicGetWithParam, TimedPutBasic) {
if (option_config_ != kRowCache) {
std::string range_del_begin = "b";
std::string range_del_end = "baz";
Slice begin_rdel = range_del_begin, end_rdel = range_del_end;
Slice begin_rdel = range_del_begin;
Slice end_rdel = range_del_end;
ASSERT_OK(
db_->DeleteRange(WriteOptions(), handles_[1], begin_rdel, end_rdel));
ASSERT_EQ("NOT_FOUND", Get(1, "bar", nullptr, use_coroutine_));
@@ -2307,7 +2313,8 @@ TEST_F(DBBasicTest, IdentityAcrossRestarts) {
for (bool with_manifest : {false, true}) {
for (bool write_file : {false, true}) {
std::string idfilename = IdentityFileName(dbname_);
std::string id1, tmp;
std::string id1;
std::string tmp;
ASSERT_OK(db_->GetDbIdentity(id1));
ASSERT_GE(id1.size(), kMinIdSize);
@@ -2409,7 +2416,8 @@ TEST_F(DBBasicTest, LockFileRecovery) {
for (bool ber : {false, true}) {
options.best_efforts_recovery = ber;
DestroyAndReopen(options);
std::string id1, id2;
std::string id1;
std::string id2;
ASSERT_OK(db_->GetDbIdentity(id1));
Close();
@@ -4972,6 +4980,10 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2WithRangeDelInL1) {
TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) {
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
const bool previous_enable_io_uring = enable_io_uring;
Defer restore_enable_io_uring([previous_enable_io_uring]() {
enable_io_uring = previous_enable_io_uring;
});
enable_io_uring = false;
ReopenDB();
@@ -4998,6 +5010,164 @@ TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) {
}
}
TEST_P(DBMultiGetAsyncIOTest, AsyncIOSupportCheckedOnReadThread) {
#ifndef ROCKSDB_IOURING_PRESENT
ROCKSDB_GTEST_SKIP("This test requires io_uring support");
#else
if (use_coroutine_) {
ROCKSDB_GTEST_BYPASS(
"This test targets synchronous MultiGet with async IO");
return;
}
if (!CheckFSFeatureSupport(db_->GetFileSystem(), FSSupportedOps::kAsyncIO)) {
ROCKSDB_GTEST_SKIP("io_uring is unavailable on the test thread");
return;
}
PrepareDBForTest();
std::atomic<uint64_t> read_thread_id{0};
int read_async_count = 0;
SyncPoint::GetInstance()->SetCallBack(
"PosixFileSystem::SupportedOps:CreateIOUring", [&](void* arg) {
if (read_thread_id.load(std::memory_order_acquire) !=
Env::Default()->GetThreadID()) {
return;
}
auto** iu = static_cast<struct io_uring**>(arg);
if (*iu != nullptr) {
DeleteIOUring(*iu);
*iu = nullptr;
}
});
SyncPoint::GetInstance()->SetCallBack(
"RandomAccessFileReader::ReadAsync:InjectStatus",
[&](void*) { ++read_async_count; });
Defer cleanup([]() {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> values;
std::thread read_thread([&]() {
read_thread_id.store(Env::Default()->GetThreadID(),
std::memory_order_release);
values =
MultiGet({Key(33), Key(54), Key(102)}, /*snapshot=*/nullptr,
/*async=*/true, optimize_multiget_for_io_, use_coroutine_);
});
read_thread.join();
ASSERT_EQ(values.size(), 3);
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
ASSERT_EQ(values[1], "val_l1_" + std::to_string(54));
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
ASSERT_EQ(read_async_count, 0);
#endif // ROCKSDB_IOURING_PRESENT
}
// SupportedOps is advisory, so ReadAsync can still report NotSupported when a
// request is submitted. MultiGet must then read synchronously rather than
// returning that error to the caller.
TEST_P(DBMultiGetAsyncIOTest, ReadAsyncNotSupportedFallsBackToSync) {
const bool previous_enable_io_uring = enable_io_uring;
Defer cleanup([previous_enable_io_uring]() {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
enable_io_uring = previous_enable_io_uring;
});
enable_io_uring = true;
PrepareDBForTest();
int sync_fallback_count = 0;
SyncPoint::GetInstance()->SetCallBack(
"RandomAccessFileReader::ReadAsync:InjectStatus", [](void* arg) {
*static_cast<IOStatus*>(arg) =
IOStatus::NotSupported("ReadAsync: failed to init io_uring");
});
SyncPoint::GetInstance()->SetCallBack(
"AsyncFileReader::MultiReadAsyncImpl:SyncFallback",
[&](void*) { ++sync_fallback_count; });
SyncPoint::GetInstance()->SetCallBack(
"AsyncFileReader::Wait:InjectPollStatus", [](void* arg) {
*static_cast<IOStatus*>(arg) = IOStatus::IOError("injected Poll error");
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
std::vector<std::string> values =
MultiGet(key_strs, /*snapshot=*/nullptr, /*async=*/true,
optimize_multiget_for_io_, use_coroutine_);
ASSERT_EQ(values.size(), 3);
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
ASSERT_EQ(values[1], "val_l1_" + std::to_string(54));
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
#ifdef ROCKSDB_IOURING_PRESENT
// Both synchronous async IO paths use AsyncFileReader when reads do not go
// through the coroutine read executor.
if (!UseCoroutineRead()) {
ASSERT_GT(sync_fallback_count, 0);
}
#endif // ROCKSDB_IOURING_PRESENT
}
#ifdef ROCKSDB_IOURING_PRESENT
TEST_P(DBMultiGetAsyncIOTest, MixedReadAsyncFallbackPropagatesPollError) {
if (UseCoroutineRead()) {
ROCKSDB_GTEST_BYPASS(
"This test targets the synchronous MultiGet AsyncFileReader path");
return;
}
if (!CheckFSFeatureSupport(db_->GetFileSystem(), FSSupportedOps::kAsyncIO)) {
ROCKSDB_GTEST_SKIP("io_uring is unavailable on the test thread");
return;
}
PrepareDBForTest();
std::atomic<int> read_async_count{0};
std::atomic<int> sync_fallback_count{0};
Defer cleanup([]() {
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
});
SyncPoint::GetInstance()->SetCallBack(
"RandomAccessFileReader::ReadAsync:InjectStatus", [&](void* arg) {
if (read_async_count.fetch_add(1) % 2 == 0) {
*static_cast<IOStatus*>(arg) =
IOStatus::NotSupported("injected ReadAsync fallback");
}
});
SyncPoint::GetInstance()->SetCallBack(
"AsyncFileReader::MultiReadAsyncImpl:SyncFallback",
[&](void*) { ++sync_fallback_count; });
SyncPoint::GetInstance()->SetCallBack(
"AsyncFileReader::Wait:InjectPollStatus", [](void* arg) {
*static_cast<IOStatus*>(arg) = IOStatus::IOError("injected Poll error");
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
std::vector<PinnableSlice> values;
std::vector<Status> statuses;
MultiGet(key_strs, /*snapshot=*/nullptr, /*async=*/true,
optimize_multiget_for_io_, use_coroutine_, values, statuses);
ASSERT_EQ(statuses.size(), 3);
ASSERT_OK(statuses[0]);
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
ASSERT_TRUE(statuses[1].IsIOError());
ASSERT_OK(statuses[2]);
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
ASSERT_EQ(read_async_count, 3);
ASSERT_EQ(sync_fallback_count, 2);
}
#endif // ROCKSDB_IOURING_PRESENT
INSTANTIATE_TEST_CASE_P(DBMultiGetAsyncIOTest, DBMultiGetAsyncIOTest,
testing::Combine(testing::Bool(), testing::Bool()));
#endif // USE_COROUTINES
@@ -5767,6 +5937,9 @@ INSTANTIATE_TEST_CASE_P(BestEffortsRecoverIncompleteVersionTest,
TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) {
Options options = CurrentOptions();
options.env = env_;
options.write_buffer_manager = std::make_shared<WriteBufferManager>(
64 << 20, nullptr /* cache */, false /* allow_stall */,
WriteBufferFlushPolicy::kFlushLargestAcrossDBs);
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "value0"));
ASSERT_OK(Flush());
@@ -5788,6 +5961,9 @@ TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) {
Reopen(options);
ASSERT_OK(Put("bar", "value"));
options.write_buffer_manager->TEST_RefreshFlushInitiatorCandidate();
ASSERT_TRUE(
options.write_buffer_manager->TEST_ScheduleFlushOnLargestDB(nullptr));
}
TEST_F(DBBasicTest, RecoverWithNoCurrentFile) {
@@ -6369,9 +6545,9 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
ASSERT_TRUE(CheckUncompressableValue(key_uncmp[i], values[i].ToString()));
}
if (compression_enabled() && !has_compressed_cache()) {
expected_reads += (read_from_cache ? 3 : 3);
expected_reads += 3;
} else {
expected_reads += (read_from_cache ? 4 : 4);
expected_reads += 4;
}
ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads);
@@ -6438,7 +6614,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
public:
FakeDirectIOSequentialFile(std::unique_ptr<SequentialFile>&& file)
: SequentialFileWrapper(file.get()), file_(std::move(file)) {}
~FakeDirectIOSequentialFile() {}
~FakeDirectIOSequentialFile() override {}
bool use_direct_io() const override { return true; }
size_t GetRequiredBufferAlignment() const override { return 1; }
@@ -6451,7 +6627,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
public:
FakeDirectIORandomAccessFile(std::unique_ptr<RandomAccessFile>&& file)
: RandomAccessFileWrapper(file.get()), file_(std::move(file)) {}
~FakeDirectIORandomAccessFile() {}
~FakeDirectIORandomAccessFile() override {}
bool use_direct_io() const override { return true; }
size_t GetRequiredBufferAlignment() const override { return 1; }
+6 -2
View File
@@ -1921,7 +1921,10 @@ TEST_F(CacheKeyTest, DBImplSessionIdStructure) {
// counter."
std::string session_id1 = DBImpl::GenerateDbSessionId(/*env*/ nullptr);
std::string session_id2 = DBImpl::GenerateDbSessionId(/*env*/ nullptr);
uint64_t upper1, upper2, lower1, lower2;
uint64_t upper1;
uint64_t upper2;
uint64_t lower1;
uint64_t lower2;
ASSERT_OK(DecodeSessionId(session_id1, &upper1, &lower1));
ASSERT_OK(DecodeSessionId(session_id2, &upper2, &lower2));
// Because generated in same process
@@ -1981,7 +1984,8 @@ struct CacheKeyDecoder {
}
void Decode(const CacheKey& key) {
uint64_t file_num_etc64, offset_etc64;
uint64_t file_num_etc64;
uint64_t offset_etc64;
DeconstructNonemptyCacheKey(key, &file_num_etc64, &offset_etc64);
// First decode session counter
+6 -5
View File
@@ -917,14 +917,15 @@ INSTANTIATE_TEST_CASE_P(
::testing::Values(
std::make_tuple(kAutoBloom,
FilterPartitioning::kCoupledPartitionedFilter,
kLatestBbtFormatVersion),
BlockBasedTableOptions::kLatestSupportedFormatVersion),
std::make_tuple(kAutoBloom,
FilterPartitioning::kDecoupledPartitionedFilter,
kLatestBbtFormatVersion),
BlockBasedTableOptions::kLatestSupportedFormatVersion),
std::make_tuple(kAutoBloom, FilterPartitioning::kUnpartitionedFilter,
kLatestBbtFormatVersion),
std::make_tuple(kAutoRibbon, FilterPartitioning::kUnpartitionedFilter,
kLatestBbtFormatVersion)));
BlockBasedTableOptions::kLatestSupportedFormatVersion),
std::make_tuple(
kAutoRibbon, FilterPartitioning::kUnpartitionedFilter,
BlockBasedTableOptions::kLatestSupportedFormatVersion)));
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
TEST_F(DBBloomFilterTest, BloomFilterRate) {
+85 -2
View File
@@ -3,8 +3,11 @@
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include <atomic>
#include "db/db_test_util.h"
#include "port/port.h"
#include "test_util/sync_point.h"
#include "util/random.h"
namespace ROCKSDB_NAMESPACE {
@@ -14,6 +17,62 @@ class DBClipTest : public DBTestBase {
DBClipTest() : DBTestBase("db_clip_test", /*env_do_fsync=*/true) {}
};
// ClipColumnFamily reads the CF's boundary keys under the DB mutex, then drops
// it and issues several writes with those keys. Those keys must not point into
// Version-owned FileMetaData, which a background job can replace and free in
// that window. Here a full compaction runs inside the window and obsoletes the
// Version the keys were read from.
TEST_F(DBClipTest, BoundaryKeysSurviveConcurrentVersionChange) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.num_levels = 3;
DestroyAndReopen(options);
Random rnd(301);
std::map<int32_t, std::string> values;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 100; j++) {
int k = i * 100 + j;
values[k] = rnd.RandomString(1024);
ASSERT_OK(Put(Key(k), values[k]));
}
ASSERT_OK(Flush());
}
std::atomic<bool> fired{false};
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::ClipColumnFamily:PostBoundaryKeys", [&](void*) {
if (fired.exchange(true)) {
return;
}
// Replace and free the Version the boundary keys were read from, on
// another thread so this does not re-enter Clip's own call stack.
port::Thread compactor([&] {
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForPurge());
});
compactor.join();
});
SyncPoint::GetInstance()->EnableProcessing();
const auto begin_key = Key(251);
const auto end_key = Key(751);
ASSERT_OK(
db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key, end_key));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_TRUE(fired.load());
for (int i = 251; i < 751; i++) {
ASSERT_EQ(Get(Key(i)), values[i]);
}
for (int i : {0, 250, 751, 999}) {
std::string result;
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &result).IsNotFound());
}
}
TEST_F(DBClipTest, TestClipRange) {
Options options = CurrentOptions();
options.write_buffer_size = 10 * 1024 * 1024;
@@ -39,7 +98,8 @@ TEST_F(DBClipTest, TestClipRange) {
ASSERT_OK(Flush());
}
ASSERT_EQ("10", FilesPerLevel(0));
auto begin_key = Key(251), end_key = Key(751);
auto begin_key = Key(251);
auto end_key = Key(751);
ASSERT_OK(
db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key, end_key));
@@ -99,7 +159,8 @@ TEST_F(DBClipTest, TestClipRange) {
}
ASSERT_EQ("5,5,3", FilesPerLevel(0));
auto begin_key_2 = Key(222), end_key_2 = Key(888);
auto begin_key_2 = Key(222);
auto end_key_2 = Key(888);
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key_2,
end_key_2));
@@ -133,6 +194,28 @@ TEST_F(DBClipTest, TestClipRange) {
ASSERT_TRUE(in_range);
}
}
TEST_F(DBClipTest, KeepFileEndingAtBeginKey) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Put("b", "vb"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
ASSERT_OK(Put("c", "vc"));
ASSERT_OK(Put("d", "vd"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
ASSERT_EQ("0,2", FilesPerLevel(0));
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(), "b", "d"));
ASSERT_EQ("NOT_FOUND", Get("a"));
ASSERT_EQ("vb", Get("b"));
ASSERT_EQ("vc", Get("c"));
ASSERT_EQ("NOT_FOUND", Get("d"));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+4 -1
View File
@@ -601,7 +601,10 @@ TEST_P(DBTestCompactionFilterWithCompactParam,
}
TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
std::string one, two, three, four;
std::string one;
std::string two;
std::string three;
std::string four;
PutFixed64(&one, 1);
PutFixed64(&two, 2);
PutFixed64(&three, 3);
+319 -31
View File
@@ -7,6 +7,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <atomic>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
@@ -26,6 +29,8 @@
#include "rocksdb/file_checksum.h"
#include "rocksdb/iostats_context.h"
#include "rocksdb/sst_file_writer.h"
#include "table/block_based/block_based_table_factory.h"
#include "table/table_builder.h"
#include "test_util/mock_time_env.h"
#include "test_util/sync_point.h"
#include "test_util/testutil.h"
@@ -173,6 +178,264 @@ class DBCompactionTest : public DBTestBase {
}
};
struct FileCuttingTableFactoryState {
std::atomic<int> cut_requests{0};
int max_cut_requests = 1;
bool allow_empty_builder_cut = false;
};
class FileCuttingTableBuilder : public TableBuilder {
public:
FileCuttingTableBuilder(std::unique_ptr<TableBuilder> base_builder,
std::string cut_before_user_key,
std::shared_ptr<FileCuttingTableFactoryState> state)
: base_builder_(std::move(base_builder)),
cut_before_user_key_(std::move(cut_before_user_key)),
state_(std::move(state)) {}
void Add(const Slice& key, const Slice& value) override {
base_builder_->Add(key, value);
}
TableBuilderAddResult TryAdd(const Slice& key, const Slice& value,
TableBuilderAddContext* context) override {
const Slice user_key = ExtractUserKey(key);
const bool cut_request_limit_not_reached =
state_->cut_requests.load() < state_->max_cut_requests;
const bool should_cut =
cut_request_limit_not_reached &&
(state_->allow_empty_builder_cut || !base_builder_->IsEmpty()) &&
user_key.compare(Slice(cut_before_user_key_)) == 0;
if (should_cut) {
state_->cut_requests++;
context->new_table_reason = TableBuilderNewTableReason::kBuilderPolicy;
return TableBuilderAddResult::kRequiresNewTable;
}
return base_builder_->TryAdd(key, value, context);
}
Status status() const override { return base_builder_->status(); }
IOStatus io_status() const override { return base_builder_->io_status(); }
Status Finish() override { return base_builder_->Finish(); }
void Abandon() override { base_builder_->Abandon(); }
uint64_t NumEntries() const override { return base_builder_->NumEntries(); }
bool IsEmpty() const override { return base_builder_->IsEmpty(); }
uint64_t PreCompressionSize() const override {
return base_builder_->PreCompressionSize();
}
uint64_t FileSize() const override { return base_builder_->FileSize(); }
uint64_t EstimatedFileSize() const override {
return base_builder_->EstimatedFileSize();
}
uint64_t EstimatedTailSize() const override {
return base_builder_->EstimatedTailSize();
}
uint64_t GetTailSize() const override { return base_builder_->GetTailSize(); }
bool NeedCompact() const override { return base_builder_->NeedCompact(); }
TableProperties GetTableProperties() const override {
return base_builder_->GetTableProperties();
}
std::string GetFileChecksum() const override {
return base_builder_->GetFileChecksum();
}
const char* GetFileChecksumFuncName() const override {
return base_builder_->GetFileChecksumFuncName();
}
void SetSeqnoTimeTableProperties(const SeqnoToTimeMapping& relevant_mapping,
uint64_t oldest_ancestor_time) override {
base_builder_->SetSeqnoTimeTableProperties(relevant_mapping,
oldest_ancestor_time);
}
uint64_t GetWorkerCPUMicros() const override {
return base_builder_->GetWorkerCPUMicros();
}
private:
std::unique_ptr<TableBuilder> base_builder_;
std::string cut_before_user_key_;
std::shared_ptr<FileCuttingTableFactoryState> state_;
};
class FileCuttingTableFactory : public TableFactory {
public:
FileCuttingTableFactory(std::string cut_before_user_key,
std::shared_ptr<FileCuttingTableFactoryState> state)
: cut_before_user_key_(std::move(cut_before_user_key)),
state_(std::move(state)) {}
const char* Name() const override { return "FileCuttingTableFactory"; }
using TableFactory::NewTableReader;
Status NewTableReader(
const ReadOptions& read_options,
const TableReaderOptions& table_reader_options,
std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
std::unique_ptr<TableReader>* table_reader,
bool prefetch_index_and_filter_in_cache) const override {
return base_factory_.NewTableReader(
read_options, table_reader_options, std::move(file), file_size,
table_reader, prefetch_index_and_filter_in_cache);
}
TableBuilder* NewTableBuilder(
const TableBuilderOptions& table_builder_options,
WritableFileWriter* file) const override {
return new FileCuttingTableBuilder(
std::unique_ptr<TableBuilder>(
base_factory_.NewTableBuilder(table_builder_options, file)),
cut_before_user_key_, state_);
}
std::unique_ptr<TableFactory> Clone() const override {
return std::make_unique<FileCuttingTableFactory>(cut_before_user_key_,
state_);
}
bool IsDeleteRangeSupported() const override {
return base_factory_.IsDeleteRangeSupported();
}
std::string GetPrintableOptions() const override {
return base_factory_.GetPrintableOptions();
}
private:
std::string cut_before_user_key_;
std::shared_ptr<FileCuttingTableFactoryState> state_;
BlockBasedTableFactory base_factory_;
};
TEST_F(DBCompactionTest, TableBuilderCanRequestNewTableDuringCompaction) {
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.level0_file_num_compaction_trigger = 10;
options.target_file_size_base = 1 << 30;
options.table_factory =
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
Reopen(options);
for (int key : {0, 2, 4, 6, 8}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
for (int key : {1, 3, 5, 7, 9}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
ASSERT_EQ(2, NumTableFilesAtLevel(0));
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ(1, factory_state->cut_requests.load());
ASSERT_EQ(0, NumTableFilesAtLevel(0));
ASSERT_EQ(2, NumTableFilesAtLevel(1));
for (int key = 0; key < 10; ++key) {
ASSERT_EQ("value" + std::to_string(key), Get(Key(key)));
}
}
TEST_F(DBCompactionTest, TableBuilderCutRequestForLevelZeroFailsCompaction) {
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleUniversal;
options.disable_auto_compactions = true;
options.num_levels = 1;
options.table_factory =
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
Reopen(options);
for (int key : {0, 2, 4, 6, 8}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
for (int key : {1, 3, 5, 7, 9}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
ASSERT_NE(std::string::npos, status.ToString().find("level 0 output"))
<< status.ToString();
ASSERT_EQ(1, factory_state->cut_requests.load());
}
TEST_F(DBCompactionTest, TableBuilderCutOnEmptyBuilderFailsCompaction) {
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
factory_state->allow_empty_builder_cut = true;
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.allow_data_in_errors = false;
options.level0_file_num_compaction_trigger = 10;
options.target_file_size_base = 1 << 30;
options.table_factory =
std::make_shared<FileCuttingTableFactory>(Key(0), factory_state);
Reopen(options);
for (int key : {0, 2, 4, 6, 8}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
for (int key : {1, 3, 5, 7, 9}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
ASSERT_NE(std::string::npos, status.ToString().find("key: hidden"))
<< status.ToString();
ASSERT_EQ(1, factory_state->cut_requests.load());
}
TEST_F(DBCompactionTest, TableBuilderRepeatedCutRequestFailsCompaction) {
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
factory_state->max_cut_requests = 2;
factory_state->allow_empty_builder_cut = true;
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.level0_file_num_compaction_trigger = 10;
options.target_file_size_base = 1 << 30;
options.table_factory =
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
Reopen(options);
for (int key : {0, 2, 4, 6, 8}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
for (int key : {1, 3, 5, 7, 9}) {
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
}
ASSERT_OK(Flush());
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
ASSERT_EQ(2, factory_state->cut_requests.load());
}
class DBCompactionTestWithParam
: public DBTestBase,
public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
@@ -2458,9 +2721,12 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
// Delete files in range [0, 299] (inclusive)
{
auto begin_str1 = Key(0), end_str1 = Key(100);
auto begin_str2 = Key(100), end_str2 = Key(200);
auto begin_str3 = Key(200), end_str3 = Key(299);
auto begin_str1 = Key(0);
auto end_str1 = Key(100);
auto begin_str2 = Key(100);
auto end_str2 = Key(200);
auto begin_str3 = Key(200);
auto end_str3 = Key(299);
std::vector<RangeOpt> ranges;
ranges.emplace_back(begin_str1, end_str1);
ranges.emplace_back(begin_str2, end_str2);
@@ -2482,12 +2748,18 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
// Delete files in range [600, 999) (exclusive)
{
auto begin_str1 = Key(600), end_str1 = Key(800);
auto begin_str2 = Key(700), end_str2 = Key(900);
auto begin_str3 = Key(800), end_str3 = Key(999);
Slice begin1(begin_str1), end1(end_str1);
Slice begin2(begin_str2), end2(end_str2);
Slice begin3(begin_str3), end3(end_str3);
auto begin_str1 = Key(600);
auto end_str1 = Key(800);
auto begin_str2 = Key(700);
auto end_str2 = Key(900);
auto begin_str3 = Key(800);
auto end_str3 = Key(999);
Slice begin1(begin_str1);
Slice end1(end_str1);
Slice begin2(begin_str2);
Slice end2(end_str2);
Slice begin3(begin_str3);
Slice end3(end_str3);
std::vector<RangePtr> ranges;
ranges.emplace_back(&begin1, &end1);
ranges.emplace_back(&begin2, &end2);
@@ -2574,8 +2846,10 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRangeFileEndpointsOverlapBug) {
// Verify `DeleteFilesInRange` can't drop only file 0 which would cause
// "1 -> vals[0]" to reappear.
std::string begin_str = Key(0), end_str = Key(1);
Slice begin = begin_str, end = end_str;
std::string begin_str = Key(0);
std::string end_str = Key(1);
Slice begin = begin_str;
Slice end = end_str;
ASSERT_OK(
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
ASSERT_EQ(vals[1], GetValue(Key(1)));
@@ -2883,7 +3157,8 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionCFPathUse) {
std::vector<Options> option_vector;
option_vector.emplace_back(options);
ColumnFamilyOptions cf_opt1(options), cf_opt2(options);
ColumnFamilyOptions cf_opt1(options);
ColumnFamilyOptions cf_opt2(options);
// Configure CF1 specific paths.
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 500 * 1024);
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 4 * 1024 * 1024);
@@ -3659,7 +3934,10 @@ TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) {
// move to level 3 will not be allowed
options.compression_per_level = {kNoCompression, kNoCompression,
kZlibCompression};
int matches = 0, didnt_match = 0, trivial_move = 0, non_trivial = 0;
int matches = 0;
int didnt_match = 0;
int trivial_move = 0;
int non_trivial = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"Compaction::InputCompressionMatchesOutput:Matches",
@@ -4735,7 +5013,8 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
std::vector<LiveFileMetaData> pre_release_metadata, post_release_metadata;
std::vector<LiveFileMetaData> pre_release_metadata;
std::vector<LiveFileMetaData> post_release_metadata;
db_->GetLiveFilesMetaData(&pre_release_metadata);
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
// files does not need to be preserved in case of a future snapshot.
@@ -4882,7 +5161,8 @@ TEST_F(DBCompactionTest, NoCompactBottomLevelFilesWithDeletions) {
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr));
ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
std::vector<LiveFileMetaData> pre_release_metadata, post_release_metadata;
std::vector<LiveFileMetaData> pre_release_metadata;
std::vector<LiveFileMetaData> post_release_metadata;
db_->GetLiveFilesMetaData(&pre_release_metadata);
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
// files does not need to be preserved in case of a future snapshot.
@@ -5350,10 +5630,10 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
*max_open_files = 2;
});
// In the case where all files are opened and doing DB restart
// forcing the oldest ancester time in manifest file to be 0 to
// forcing the oldest ancestor time in manifest file to be 0 to
// simulate the case of reading from an old version.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionEdit::EncodeTo:VarintOldestAncesterTime", [&](void* arg) {
"VersionEdit::EncodeTo:VarintOldestAncestorTime", [&](void* arg) {
if (if_restart && if_open_all_files) {
std::string* encoded_field = static_cast<std::string*>(arg);
*encoded_field = "";
@@ -5390,7 +5670,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
std::vector<std::vector<FileMetaData>> level_to_files;
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
&level_to_files);
uint64_t oldest_time = level_to_files[0][0].oldest_ancester_time;
uint64_t oldest_time = level_to_files[0][0].oldest_ancestor_time;
// Add 1 hour and do another flush.
env_->MockSleepForSeconds(1 * 60 * 60);
for (int i = 101; i <= 200; ++i) {
@@ -5458,7 +5738,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
&level_to_files);
ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancester_time);
ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancestor_time);
env_->MockSleepForSeconds(25 * 60 * 60);
ASSERT_OK(Put(Key(2), "1"));
@@ -5881,7 +6161,7 @@ TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
props->file_creation_time = 0;
}
if (set_creation_time_to_zero) {
props->creation_time = 0;
props->oldest_ancestor_time = 0;
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
@@ -6432,7 +6712,8 @@ TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) {
CompactRangeOptions compact_range_opts;
ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr));
uint64_t get_prop_tmp, num_memtable_entries = 0;
uint64_t get_prop_tmp;
uint64_t num_memtable_entries = 0;
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables,
&get_prop_tmp));
num_memtable_entries += get_prop_tmp;
@@ -8201,7 +8482,8 @@ TEST_F(DBCompactionTest, ManualCompactionBottomLevelOptimized) {
}
TEST_F(DBCompactionTest, ManualCompactionMax) {
uint64_t l1_avg_size = 0, l2_avg_size = 0;
uint64_t l1_avg_size = 0;
uint64_t l2_avg_size = 0;
auto generate_sst_func = [&]() {
Random rnd(301);
for (auto i = 0; i < 100; i++) {
@@ -9389,7 +9671,8 @@ TEST_P(DBCompactionTestL0FilesMisorderCorruptionWithParam,
// `start` and `end` is carefully chosen so that compact range:
// (1) doesn't overlap with memtable therefore the memtable won't be flushed
// (2) should target at compacting s0 with s1 and s2
Slice start("k3"), end("k5");
Slice start("k3");
Slice end("k5");
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
// After compaction, we have LSM tree:
//
@@ -10904,8 +11187,12 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
env_->SetMockSleep();
// Track all temperature file creations
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
total_ice = 0, total_unknown = 0;
int total_hot = 0;
int total_warm = 0;
int total_cool = 0;
int total_cold = 0;
int total_ice = 0;
int total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
Temperature temperature = *(static_cast<Temperature*>(arg));
@@ -11741,7 +12028,8 @@ TEST_F(DBCompactionTest, DrainUnnecessaryLevelsAfterMultiplierChanged) {
// necessary, unnecessary levels should to be drained.
const int kBaseLevelBytes = 256 << 10; // 256KB
const int kFileBytes = 64 << 10; // 64KB
const int kInitMultiplier = 2, kChangedMultiplier = 10;
const int kInitMultiplier = 2;
const int kChangedMultiplier = 10;
const int kNumFiles = 32;
const int kNumLevels = 5;
const int kValueBytes = 1 << 10; // 1KB
@@ -12417,7 +12705,7 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
ASSERT_EQ(newest_key_time, file_metadatas[idx]
->fd.pinned_reader.Get()
->GetTableProperties()
->creation_time);
->oldest_ancestor_time);
}
// The delta between the first and last newest_key_times is 15s
uint64_t last_newest_key_time = prev_newest_key_time;
@@ -12436,14 +12724,14 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
->GetTableProperties()
->newest_key_time,
first_newest_key_time);
// Contrast newest_key_time with creation_time, which records the oldest
// ancestor time (15s older than newest_key_time)
// Contrast newest_key_time with oldest_ancestor_time (15s older than
// newest_key_time)
ASSERT_EQ(file_metadatas[0]
->fd.pinned_reader.Get()
->GetTableProperties()
->creation_time,
->oldest_ancestor_time,
last_newest_key_time);
ASSERT_EQ(file_metadatas[0]->oldest_ancester_time, last_newest_key_time);
ASSERT_EQ(file_metadatas[0]->oldest_ancestor_time, last_newest_key_time);
// Make sure TTL of 5s causes compaction
env_->MockSleepForSeconds(6);
+223 -2
View File
@@ -1357,7 +1357,8 @@ TEST_F(DBTest2, DuplicateSnapshot) {
options = CurrentOptions(options);
std::vector<const Snapshot*> snapshots;
DBImpl* dbi = dbfull();
SequenceNumber oldest_ww_snap, first_ww_snap;
SequenceNumber oldest_ww_snap;
SequenceNumber first_ww_snap;
ASSERT_OK(Put("k", "v")); // inc seq
snapshots.push_back(db_->GetSnapshot());
@@ -2380,10 +2381,12 @@ TEST_F(DBTest2, PausingManualCompaction1) {
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> files_before_compact, files_after_compact;
std::vector<std::string> files_before_compact;
std::vector<std::string> files_after_compact;
// Remember file name before compaction is triggered
std::vector<LiveFileMetaData> files_meta;
dbfull()->GetLiveFilesMetaData(&files_meta);
files_before_compact.reserve(files_meta.size());
for (const auto& file : files_meta) {
files_before_compact.push_back(file.name);
}
@@ -2399,6 +2402,7 @@ TEST_F(DBTest2, PausingManualCompaction1) {
// Get file names after compaction is stopped
files_meta.clear();
dbfull()->GetLiveFilesMetaData(&files_meta);
files_after_compact.reserve(files_meta.size());
for (const auto& file : files_meta) {
files_after_compact.push_back(file.name);
}
@@ -3513,6 +3517,40 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(DBTest2, LiveFilesStorageInfoWithConcurrentSetOptions) {
// SetOptions() writes a new OPTIONS file and deletes all but the newest
// two. Without file deletions disabled by the caller, the OPTIONS file
// chosen by GetLiveFilesStorageInfo must still survive until its size is
// read.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::GetLiveFilesStorageInfo:BeforeOptionsFileSize",
[&](void* /*arg*/) {
ASSERT_OK(db_->SetOptions({{"disable_auto_compactions", "true"}}));
ASSERT_OK(db_->SetOptions({{"disable_auto_compactions", "false"}}));
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
std::vector<LiveFileStorageInfo> infos;
ASSERT_OK(
db_->GetLiveFilesStorageInfo(LiveFilesStorageInfoOptions(), &infos));
ASSERT_TRUE(dbfull()->IsFileDeletionsEnabled());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBTest2, LiveFilesStorageInfoReadOnlyKeepsDeletionsEnabled) {
ASSERT_OK(Put("key", "value"));
ASSERT_OK(Flush());
ASSERT_OK(ReadOnlyReopen(CurrentOptions()));
LiveFilesStorageInfoOptions opts;
opts.wal_size_for_flush = std::numeric_limits<uint64_t>::max();
std::vector<LiveFileStorageInfo> infos;
ASSERT_OK(db_->GetLiveFilesStorageInfo(opts, &infos));
ASSERT_TRUE(dbfull()->IsFileDeletionsEnabled());
}
TEST_F(DBTest2, TestNumPread) {
Options options = CurrentOptions();
bool prefetch_supported =
@@ -4271,6 +4309,147 @@ TEST_F(DBTest2, TraceWithLimit) {
ASSERT_OK(DestroyDB(dbname2, options));
}
namespace {
Status ReplayTraceFile(Env* env, const std::string& trace_filename, DB* db,
const std::vector<ColumnFamilyHandle*>& handles,
uint32_t num_threads,
TraceExecutionResultHandler* res_handler = nullptr) {
std::unique_ptr<TraceReader> trace_reader;
Status s =
NewFileTraceReader(env, EnvOptions(), trace_filename, &trace_reader);
if (!s.ok()) {
return s;
}
std::unique_ptr<Replayer> replayer;
s = db->NewDefaultReplayer(handles, std::move(trace_reader), &replayer);
if (!s.ok()) {
return s;
}
s = replayer->Prepare();
if (!s.ok()) {
return s;
}
// Count the replayed records by type in `res_handler`, if given.
std::function<void(Status, std::unique_ptr<TraceRecordResult>&&)> res_cb;
if (res_handler != nullptr) {
res_cb = [res_handler](const Status& exec_s,
std::unique_ptr<TraceRecordResult>&& res) {
exec_s.PermitUncheckedError();
if (res != nullptr) {
EXPECT_OK(res->Accept(res_handler));
}
};
}
return replayer->Replay(ReplayOptions(num_threads, 1.0), res_cb);
}
} // anonymous namespace
TEST_F(DBTest2, TraceReplayDefaultColumnFamilyWithoutHandles) {
Options options = CurrentOptions();
std::string trace_filename = dbname_ + "/rocksdb.trace";
std::unique_ptr<TraceWriter> trace_writer;
ASSERT_OK(
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
ASSERT_OK(Put("a", "1"));
ASSERT_OK(Put("b", "2"));
ASSERT_EQ("1", Get("a"));
std::vector<std::string> values;
for (const Status& s :
db_->MultiGet(ReadOptions(), {Slice("a"), Slice("b")}, &values)) {
ASSERT_OK(s);
}
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
iter->Seek("a");
iter->SeekForPrev("b");
ASSERT_OK(iter->status());
iter.reset();
ASSERT_OK(db_->EndTrace());
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
options.create_if_missing = true;
for (uint32_t num_threads : {1U, 2U}) {
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
ASSERT_OK(DB::Open(options, dbname2, &db2));
// No handles: every read record on the default column family must still
// replay.
TraceExecutionResultHandler res_handler;
ASSERT_OK(ReplayTraceFile(env_, trace_filename, db2.get(),
/*handles=*/{}, num_threads, &res_handler));
// Each record replayed and produced a result.
ASSERT_EQ(2, res_handler.GetNumWrites());
ASSERT_EQ(1, res_handler.GetNumGets());
ASSERT_EQ(1, res_handler.GetNumMultiGets());
ASSERT_EQ(2, res_handler.GetNumIterSeeks());
std::string value;
ASSERT_OK(db2->Get(ReadOptions(), "a", &value));
ASSERT_EQ("1", value);
ASSERT_OK(db2->Get(ReadOptions(), "b", &value));
ASSERT_EQ("2", value);
}
ASSERT_OK(DestroyDB(dbname2, options));
}
TEST_F(DBTest2, TraceReplayReturnsExecutionError) {
Options options = CurrentOptions();
CreateAndReopenWithCF({"pikachu"}, options);
std::string trace_filename = dbname_ + "/rocksdb.trace";
std::unique_ptr<TraceWriter> trace_writer;
ASSERT_OK(
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
ASSERT_EQ("NOT_FOUND", Get(1, "a"));
ASSERT_EQ("NOT_FOUND", Get(1, "b"));
ASSERT_OK(db_->EndTrace());
// The replay DB has no "pikachu" column family, so both Get records fail.
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
options.create_if_missing = true;
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
ASSERT_OK(DB::Open(options, dbname2, &db2));
for (uint32_t num_threads : {1U, 2U}) {
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
Status s = ReplayTraceFile(env_, trace_filename, db2.get(),
{db2->DefaultColumnFamily()}, num_threads);
ASSERT_TRUE(s.IsCorruption()) << s.ToString();
}
db2.reset();
ASSERT_OK(DestroyDB(dbname2, options));
}
TEST_F(DBTest2, TraceReplayReturnsNotSupportedExecutionError) {
Options options = CurrentOptions();
std::string trace_filename = dbname_ + "/rocksdb.trace";
std::unique_ptr<TraceWriter> trace_writer;
ASSERT_OK(
NewFileTraceWriter(env_, EnvOptions(), trace_filename, &trace_writer));
ASSERT_OK(db_->StartTrace(TraceOptions(), std::move(trace_writer)));
ASSERT_OK(Put("a", "1"));
ASSERT_OK(
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
ASSERT_OK(db_->EndTrace());
// DeleteRange is not compatible with row_cache, so replaying the DeleteRange
// write fails with NotSupported. Unlike an unsupported trace type, this must
// not be skipped.
std::string dbname2 = test::PerThreadDBPath(env_, "/db_replay2");
options.create_if_missing = true;
options.row_cache = NewLRUCache(1 << 20);
for (uint32_t num_threads : {1U, 2U}) {
SCOPED_TRACE("num_threads=" + std::to_string(num_threads));
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
ASSERT_OK(DB::Open(options, dbname2, &db2));
Status s = ReplayTraceFile(env_, trace_filename, db2.get(),
/*handles=*/{}, num_threads);
ASSERT_TRUE(s.IsNotSupported()) << s.ToString();
}
ASSERT_OK(DestroyDB(dbname2, options));
}
TEST_F(DBTest2, TraceWithSampling) {
Options options = CurrentOptions();
ReadOptions ro;
@@ -7651,6 +7830,48 @@ TEST_F(DBTest2, TableCacheMissDuringReadFromBlockCacheTier) {
ASSERT_EQ(orig_num_file_opens, TestGetTickerCount(options, NO_FILE_OPENS));
}
TEST_F(DBTest2, MultiGetBlockCacheTierSkipsColdSstData) {
Options options = CurrentOptions();
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
// Use buffered reads to reach the non-mmap RetrieveMultipleBlocks path.
options.allow_mmap_reads = false;
BlockBasedTableOptions table_options;
// Data blocks use the block cache. Index and filter blocks stay in the table
// reader (cache_index_and_filter_blocks is false by default), so a
// kBlockCacheTier read gets to the data block without metadata I/O.
table_options.block_cache = NewLRUCache(1 << 20);
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
ASSERT_OK(Put("k1", "v1"));
ASSERT_OK(Put("k2", "v2"));
ASSERT_OK(Flush());
// Open the table reader so that kBlockCacheTier does not fail at table
// open. Do not add the data block to the block cache.
ReadOptions warm_ro;
warm_ro.fill_cache = false;
std::string value;
ASSERT_OK(db_->Get(warm_ro, "k1", &value));
ASSERT_EQ(value, "v1");
// Make sure the data block is not in the block cache.
table_options.block_cache->EraseUnRefEntries();
ReadOptions no_io_ro;
no_io_ro.read_tier = kBlockCacheTier;
ASSERT_TRUE(db_->Get(no_io_ro, "k1", &value).IsIncomplete());
ASSERT_TRUE(db_->Get(no_io_ro, "k2", &value).IsIncomplete());
Slice keys[2] = {Slice("k1"), Slice("k2")};
PinnableSlice values[2];
Status statuses[2];
db_->MultiGet(no_io_ro, db_->DefaultColumnFamily(), 2, keys, values,
statuses);
ASSERT_TRUE(statuses[0].IsIncomplete());
ASSERT_TRUE(statuses[1].IsIncomplete());
}
TEST_F(DBTest2, GetFileChecksumsFromCurrentManifest_CRC32) {
Options opts = CurrentOptions();
opts.create_if_missing = true;
+2 -1
View File
@@ -61,7 +61,8 @@ TEST_F(DBEtc3Test, AutoTuneManifestSize) {
// auto-tuning phases continue to test the unrelaxed background threshold
// even though CF manipulation itself is foreground. Phase-specific foreground
// checks disable that piggy-backed background write.
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
uint64_t prev_manifest_num = 0;
uint64_t cur_manifest_num = 0;
std::deque<ColumnFamilyHandle*> handles;
int counter = 5;
auto UpdateManifestNumsFrom = [&](uint64_t before_manifest_num) {
+82 -7
View File
@@ -17,6 +17,7 @@
#include "file/file_util.h"
#include "file/filename.h"
#include "logging/logging.h"
#include "options/options_parser.h"
#include "port/port.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
@@ -198,7 +199,8 @@ Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) {
Status DBImpl::AppendColumnFamilyDropsToManifest(
const std::string& manifest_path, uint64_t manifest_size,
const std::vector<uint32_t>& cf_ids) {
const std::vector<uint32_t>& cf_ids,
uint64_t committed_options_file_number) {
const WriteOptions write_options;
// Snapshot manifest_preallocation_size_ under the DB mutex per VersionSet's
// contract (the field is mutated by SetDBOptions under this mutex).
@@ -208,21 +210,44 @@ Status DBImpl::AppendColumnFamilyDropsToManifest(
preallocation_size = versions_->manifest_preallocation_size_;
}
return versions_->AppendColumnFamilyDropsToManifest(
manifest_path, manifest_size, cf_ids, write_options, preallocation_size);
manifest_path, manifest_size, cf_ids, write_options, preallocation_size,
committed_options_file_number);
}
Status DBImpl::CreateOptionsFileForSubsetCheckpoint(
const std::string& checkpoint_dir,
const SubsetCheckpointOptionsSnapshot& options_snapshot) {
if (options_snapshot.options_file_number == 0) {
return Status::OK();
}
// The snapshot's CF options are shallow copies that share live pointer
// sub-options (e.g. RibbonFilterPolicy, whose bloom_before_level is mutated
// in place by SetOptions). Hold options_mutex_, as WriteOptionsFile does, so
// SetOptions cannot change them between writing the file and verifying it.
InstrumentedMutexLock ol(&options_mutex_);
const std::string options_path =
OptionsFileName(checkpoint_dir, options_snapshot.options_file_number);
return PersistRocksDBOptions(
WriteOptions(), options_snapshot.db_options, options_snapshot.cf_names,
options_snapshot.cf_options, options_path, fs_.get());
}
Status DBImpl::GetLiveFilesStorageInfo(
const LiveFilesStorageInfoOptions& opts,
std::vector<LiveFileStorageInfo>* files) {
return GetLiveFilesStorageInfoImpl(opts, /*include_cf_ids=*/{}, files,
/*excluded_cf_ids=*/nullptr);
/*excluded_cf_ids=*/nullptr,
/*options_snapshot=*/nullptr);
}
Status DBImpl::GetLiveFilesStorageInfoForSubsetCheckpoint(
const LiveFilesStorageInfoOptions& opts,
const std::vector<uint32_t>& include_cf_ids,
std::vector<LiveFileStorageInfo>* files,
std::vector<uint32_t>* excluded_cf_ids) {
std::vector<uint32_t>* excluded_cf_ids,
SubsetCheckpointOptionsSnapshot* options_snapshot) {
// Internal contract: caller must coalesce ids and always include the default
// CF (it cannot be dropped, so a subset checkpoint without it would produce
// an unopenable DB). CheckpointImpl enforces this before calling here.
@@ -230,15 +255,17 @@ Status DBImpl::GetLiveFilesStorageInfoForSubsetCheckpoint(
assert(std::find(include_cf_ids.begin(), include_cf_ids.end(),
default_cf_handle_->GetID()) != include_cf_ids.end());
assert(excluded_cf_ids != nullptr);
assert(options_snapshot != nullptr);
return GetLiveFilesStorageInfoImpl(opts, include_cf_ids, files,
excluded_cf_ids);
excluded_cf_ids, options_snapshot);
}
Status DBImpl::GetLiveFilesStorageInfoImpl(
const LiveFilesStorageInfoOptions& opts,
const std::vector<uint32_t>& include_cf_ids,
std::vector<LiveFileStorageInfo>* files,
std::vector<uint32_t>* excluded_cf_ids) {
std::vector<uint32_t>* excluded_cf_ids,
SubsetCheckpointOptionsSnapshot* options_snapshot) {
// To avoid returning partial results, only move results to files on success.
assert(files);
files->clear();
@@ -246,6 +273,7 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
const bool cf_subset = !include_cf_ids.empty();
assert(!cf_subset || excluded_cf_ids != nullptr);
assert(!cf_subset || options_snapshot != nullptr);
const std::unordered_set<uint32_t> include_cf_id_set(include_cf_ids.begin(),
include_cf_ids.end());
// Tracks which requested ids remain to be observed as live under the DB
@@ -413,6 +441,29 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
"Requested column family id was not live at checkpoint time");
}
if (cf_subset) {
assert(options_snapshot != nullptr);
assert(excluded_cf_ids != nullptr);
*options_snapshot = SubsetCheckpointOptionsSnapshot();
if (!excluded_cf_ids->empty()) {
options_snapshot->options_file_number = versions_->NewFileNumber();
options_snapshot->commit_options_file_number =
versions_->has_committed_options_file_number();
options_snapshot->db_options =
BuildDBOptions(immutable_db_options_, mutable_db_options_);
ColumnFamilySet* column_family_set = versions_->GetColumnFamilySet();
assert(column_family_set != nullptr);
for (auto* cfd : *column_family_set) {
assert(cfd != nullptr);
if (!cfd->IsDropped() &&
include_cf_id_set.find(cfd->GetID()) != include_cf_id_set.end()) {
options_snapshot->cf_names.push_back(cfd->GetName());
options_snapshot->cf_options.push_back(cfd->GetLatestCFOptions());
}
}
}
}
// Capture some final info before releasing mutex
const uint64_t manifest_number = versions_->manifest_file_number();
uint64_t manifest_size = versions_->manifest_file_size();
@@ -424,13 +475,37 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
}
}
const uint64_t options_number = versions_->options_file_number();
const uint64_t options_size = versions_->options_file_size_;
const uint64_t min_log_num = MinLogNumberToKeep();
// Ensure consistency with manifest for track_and_verify_wals_in_manifest
const uint64_t max_log_num = cur_wal_number_;
if (options_number != 0) {
// A concurrent SetOptions() may otherwise delete this OPTIONS file before
// its size is read below, even when the caller has not disabled deletions.
DisableFileDeletionsWithLock().PermitUncheckedError();
}
mutex_.Unlock();
uint64_t options_size = 0;
if (options_number != 0) {
// File deletions must be disabled by callers while they copy this snapshot.
// The number belongs to the MANIFEST/CF snapshot captured above, while the
// immutable file's size can be read without holding the DB mutex.
TEST_SYNC_POINT("DBImpl::GetLiveFilesStorageInfo:BeforeOptionsFileSize");
Status options_s = GetEnv()->GetFileSize(
OptionsFileName(GetName(), options_number), &options_size);
{
// Not EnableFileDeletions(): read-only DB implementations override it to
// return NotSupported without undoing DisableFileDeletionsWithLock().
InstrumentedMutexLock l(&mutex_);
assert(disable_delete_obsolete_files_ > 0);
--disable_delete_obsolete_files_;
}
if (!options_s.ok()) {
return options_s;
}
}
std::string manifest_fname = DescriptorFileName(manifest_number);
{ // MANIFEST
results.emplace_back();
+80 -14
View File
@@ -157,7 +157,8 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
env_->SetBackgroundThreads(0, Env::HIGH);
std::thread::id tid;
int num_flushes = 0, num_compactions = 0;
int num_flushes = 0;
int num_compactions = 0;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BGWorkFlush", [&](void* /*arg*/) {
if (tid == std::thread::id()) {
@@ -1017,7 +1018,9 @@ TEST_F(DBFlushTest, MemPurgeBasic) {
std::string KEY7 = "IamKey7";
std::string KEY8 = "IamKey8";
std::string KEY9 = "IamKey9";
std::string RNDKEY1, RNDKEY2, RNDKEY3;
std::string RNDKEY1;
std::string RNDKEY2;
std::string RNDKEY3;
const std::string NOT_FOUND = "NOT_FOUND";
// Heavy overwrite workload,
@@ -1026,8 +1029,18 @@ TEST_F(DBFlushTest, MemPurgeBasic) {
const size_t NUM_REPEAT = 100;
const size_t RAND_KEYS_LENGTH = 57;
const size_t RAND_VALUES_LENGTH = 10240;
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9, p_rv1,
p_rv2, p_rv3;
std::string p_v1;
std::string p_v2;
std::string p_v3;
std::string p_v4;
std::string p_v5;
std::string p_v6;
std::string p_v7;
std::string p_v8;
std::string p_v9;
std::string p_rv1;
std::string p_rv2;
std::string p_rv3;
// Insert a very first set of keys that will be
// mempurged at least once.
@@ -1394,7 +1407,14 @@ TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
const size_t NUM_REPEAT = 100;
const size_t RAND_VALUES_LENGTH = 10240;
std::string key, value, p_v1, p_v2, p_v3, p_v3b, p_v4, p_v5;
std::string key;
std::string value;
std::string p_v1;
std::string p_v2;
std::string p_v3;
std::string p_v3b;
std::string p_v4;
std::string p_v5;
int count = 0;
const int EXPECTED_COUNT_FORLOOP = 3;
const int EXPECTED_COUNT_END = 4;
@@ -1597,7 +1617,15 @@ TEST_F(DBFlushTest, MemPurgeAndCompactionFilter) {
Random rnd(53);
const size_t NUM_REPEAT = 1000;
const size_t RAND_VALUES_LENGTH = 10240;
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9;
std::string p_v1;
std::string p_v2;
std::string p_v3;
std::string p_v4;
std::string p_v5;
std::string p_v6;
std::string p_v7;
std::string p_v8;
std::string p_v9;
p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
@@ -1691,7 +1719,8 @@ TEST_F(DBFlushTest, DISABLED_MemPurgeWALSupport) {
keys.push_back("IamKey" + std::to_string(k));
}
std::string RNDKEY, RNDVALUE;
std::string RNDKEY;
std::string RNDVALUE;
const std::string NOT_FOUND = "NOT_FOUND";
// Heavy overwrite workload,
@@ -1700,7 +1729,8 @@ TEST_F(DBFlushTest, DISABLED_MemPurgeWALSupport) {
const size_t NUM_REPEAT = 100;
const size_t RAND_KEY_LENGTH = 4096;
const size_t RAND_VALUES_LENGTH = 1024;
std::vector<std::string> values_default(KVSIZE), values_pikachu(KVSIZE);
std::vector<std::string> values_default(KVSIZE);
std::vector<std::string> values_pikachu(KVSIZE);
// Insert a very first set of keys that will be
// mempurged at least once.
@@ -1888,7 +1918,8 @@ TEST_F(DBFlushTest, MemPurgeCorrectLogNumberAndSSTFileCreation) {
const uint64_t NUM_RAND_INSERTS = 500;
const uint64_t RAND_VALUES_LENGTH = 10240;
std::string key, value;
std::string key;
std::string value;
std::vector<std::string> values(9, "");
// Keys used to check that no SST file disappeared.
@@ -2982,6 +3013,34 @@ TEST_P(DBAtomicFlushTest, ManualAtomicFlush) {
}
}
// Two handles naming the same column family make the selected-cfd list longer
// than the per-CF memtable-id list, which is keyed by CF. The wait loop must
// not read the id list past its own length.
TEST_P(DBAtomicFlushTest, ManualAtomicFlushDuplicateColumnFamilyHandles) {
if (!GetParam()) {
// Only the atomic path reaches the shared wait list; without atomic_flush
// each handle is flushed independently.
return;
}
Options options = CurrentOptions();
options.create_if_missing = true;
options.atomic_flush = true;
options.write_buffer_size = (static_cast<size_t>(64) << 20);
DestroyAndReopen(options);
WriteOptions wopts;
wopts.disableWAL = true;
ASSERT_OK(Put("key", "value", wopts));
FlushOptions flush_options; // wait == true
ASSERT_OK(db_->Flush(
flush_options, {db_->DefaultColumnFamily(), db_->DefaultColumnFamily()}));
auto cfh = static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily());
ASSERT_EQ(0, cfh->cfd()->imm()->NumNotFlushed());
ASSERT_TRUE(cfh->cfd()->mem()->IsEmpty());
}
TEST_P(DBAtomicFlushTest, PrecomputeMinLogNumberToKeepNon2PC) {
Options options = CurrentOptions();
options.create_if_missing = true;
@@ -3424,7 +3483,8 @@ TEST_P(DBAtomicFlushTest, BgThreadNoWaitAfterManifestError) {
{"BgFlushThr2:WaitToCommit", "BgFlushThr1:BeforeWriteManifest"},
});
std::thread::id bg_flush_thr1, bg_flush_thr2;
std::thread::id bg_flush_thr1;
std::thread::id bg_flush_thr2;
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::BackgroundCallFlush:start", [&](void*) {
if (bg_flush_thr1 == std::thread::id()) {
@@ -4199,9 +4259,14 @@ TEST_F(DBFlushTest, SuperBlockAlignmentValueDeltaEscape) {
for (bool separate_kv : {false, true}) {
SCOPED_TRACE("separate_kv=" + std::to_string(separate_kv));
std::vector<std::string> get7, iter7, get8, iter8;
int pad7 = 0, pad8 = 0;
bool delta7 = false, delta8 = false;
std::vector<std::string> get7;
std::vector<std::string> iter7;
std::vector<std::string> get8;
std::vector<std::string> iter8;
int pad7 = 0;
int pad8 = 0;
bool delta7 = false;
bool delta8 = false;
build_and_read(7, separate_kv, &get7, &iter7, &pad7, &delta7);
build_and_read(/*format_version=*/8, separate_kv, &get8, &iter8, &pad8,
&delta8);
@@ -4368,7 +4433,8 @@ TEST_F(DBFlushTest, FlushAfterReadPathRangeTombstoneInsertion) {
// already immutable. Without the fix, this succeeds and creates
// an entry count mismatch.
cfh->cfd()->mem()->AddLogicallyRedundantRangeTombstone(
1 /* seq */, "b", "f", cfh->cfd()->GetIngestSstLock());
1 /* seq */, "b", "f", 0 /* version_number */,
cfh->cfd()->GetIngestSstLock());
});
SyncPoint::GetInstance()->EnableProcessing();
+40 -1
View File
@@ -25,7 +25,7 @@ class DBFollowerTest : public DBTestBase {
Reopen(CurrentOptions());
}
~DBFollowerTest() {
~DBFollowerTest() override {
follower_.reset();
EXPECT_EQ(DestroyDB(follower_name_, CurrentOptions()), Status::OK());
Destroy(CurrentOptions());
@@ -204,6 +204,19 @@ class DBFollowerTest : public DBTestBase {
return result;
}
void VerifyFollowerBatchValue(const std::string& key,
const std::string& expected) {
std::vector<Iterator*> iterators;
ASSERT_OK(follower()->NewIterators(
ReadOptions(), {follower()->DefaultColumnFamily()}, &iterators));
ASSERT_EQ(1U, iterators.size());
std::unique_ptr<Iterator> iterator(iterators[0]);
iterator->Seek(key);
ASSERT_OK(iterator->status());
ASSERT_TRUE(iterator->Valid());
ASSERT_EQ(expected, iterator->value());
}
DB* follower() { return follower_.get(); }
DBFollowerTestFS* follower_fs() {
return static_cast<DBFollowerTestFS*>(follower_env_->GetFileSystem().get());
@@ -253,6 +266,32 @@ TEST_F(DBFollowerTest, Basic) {
CheckDirs();
}
TEST_F(DBFollowerTest, NewIteratorsRefreshesAfterCatchUp) {
ASSERT_OK(Put("key", "v1"));
ASSERT_OK(Flush());
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
{"Leader::Done", "DBImplFollower::TryCatchupWithLeader:Begin2"},
{"DBImplFollower::TryCatchupWithLeader:End", "Follower::WaitForCatchup"},
});
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(OpenAsFollower());
VerifyFollowerBatchValue("key", "v1");
TEST_SYNC_POINT("Leader::Start");
ASSERT_OK(Put("key", "v2"));
ASSERT_OK(Flush());
TEST_SYNC_POINT("Leader::Done");
TEST_SYNC_POINT("Follower::WaitForCatchup");
VerifyFollowerBatchValue("key", "v2");
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearTrace();
}
TEST_F(DBFollowerTest, Flush) {
SyncPoint::GetInstance()->LoadDependency({
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
+2
View File
@@ -18,6 +18,8 @@ class CompactedDBImpl : public DBImpl {
// No copying allowed
CompactedDBImpl(const CompactedDBImpl&) = delete;
void operator=(const CompactedDBImpl&) = delete;
CompactedDBImpl(CompactedDBImpl&&) = delete;
void operator=(CompactedDBImpl&&) = delete;
~CompactedDBImpl() override;
+902 -218
View File
File diff suppressed because it is too large. Load diff
+218 -12
View File
@@ -9,6 +9,7 @@
#pragma once
#include <atomic>
#include <chrono>
#include <cstdint>
#include <deque>
#include <functional>
@@ -51,6 +52,7 @@
#include "db/write_controller.h"
#include "db/write_thread.h"
#include "logging/event_logger.h"
#include "memtable/flush_initiator.h"
#include "memtable/wbwi_memtable.h"
#include "monitoring/instrumented_mutex.h"
#include "options/db_options.h"
@@ -99,6 +101,17 @@ struct JobContext;
struct ExternalSstFileInfo;
struct MemTableInfo;
// Captured under the same DB mutex hold as a subset checkpoint's MANIFEST
// boundary and live CF set. This prevents the generated OPTIONS membership
// from describing a different point in time than the copied MANIFEST.
struct SubsetCheckpointOptionsSnapshot {
uint64_t options_file_number = 0;
bool commit_options_file_number = false;
DBOptions db_options;
std::vector<std::string> cf_names;
std::vector<ColumnFamilyOptions> cf_options;
};
// Class to maintain directories for all database paths other than main one.
class Directories {
public:
@@ -221,7 +234,7 @@ class DBImpl : public DB
DBImpl(DBImpl&&) = delete;
void operator=(DBImpl&&) = delete;
virtual ~DBImpl();
~DBImpl() override;
// ---- Implementations of the DB interface ----
@@ -640,7 +653,8 @@ class DBImpl : public DB
const LiveFilesStorageInfoOptions& opts,
const std::vector<uint32_t>& include_cf_ids,
std::vector<LiveFileStorageInfo>* files,
std::vector<uint32_t>* excluded_cf_ids);
std::vector<uint32_t>* excluded_cf_ids,
SubsetCheckpointOptionsSnapshot* options_snapshot);
// Shared body of GetLiveFilesStorageInfo and its subset-checkpoint variant.
// include_cf_ids empty -> no filter; otherwise records skipped CF ids in
@@ -649,15 +663,23 @@ class DBImpl : public DB
const LiveFilesStorageInfoOptions& opts,
const std::vector<uint32_t>& include_cf_ids,
std::vector<LiveFileStorageInfo>* files,
std::vector<uint32_t>* excluded_cf_ids);
std::vector<uint32_t>* excluded_cf_ids,
SubsetCheckpointOptionsSnapshot* options_snapshot);
// Appends kColumnFamilyDrop records to the MANIFEST file at manifest_path for
// the given column family ids. Used by Checkpoint to make a subset-CF
// checkpoint's copied MANIFEST consistent with the reduced file set. Operates
// only on the given file; does not mutate this DB's live state.
Status AppendColumnFamilyDropsToManifest(const std::string& manifest_path,
uint64_t manifest_size,
const std::vector<uint32_t>& cf_ids);
Status AppendColumnFamilyDropsToManifest(
const std::string& manifest_path, uint64_t manifest_size,
const std::vector<uint32_t>& cf_ids,
uint64_t committed_options_file_number);
// Writes a previously captured subset-specific OPTIONS snapshot into the
// checkpoint. A zero file number is a no-op for a legacy source DB.
Status CreateOptionsFileForSubsetCheckpoint(
const std::string& checkpoint_dir,
const SubsetCheckpointOptionsSnapshot& options_snapshot);
Status GetPreparedFileInfoForExternalSstIngestion(
const std::string& file_path,
@@ -1191,6 +1213,12 @@ class DBImpl : public DB
batches_[seq] = {log, batch, batch_cnt};
}
// No copying allowed
RecoveredTransaction(const RecoveredTransaction&) = delete;
RecoveredTransaction& operator=(const RecoveredTransaction&) = delete;
RecoveredTransaction(RecoveredTransaction&&) = delete;
RecoveredTransaction& operator=(RecoveredTransaction&&) = delete;
~RecoveredTransaction() {
for (auto& it : batches_) {
delete it.second.batch_;
@@ -1342,6 +1370,9 @@ class DBImpl : public DB
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
size_t TEST_GetWBMFlushableMemUsage();
bool TEST_IsWBMFlushInitiatorFlushable() const;
// Force current memtable contents to be flushed.
Status TEST_FlushMemTable(bool wait = true, bool allow_write_stall = false,
ColumnFamilyHandle* cfh = nullptr);
@@ -1362,7 +1393,10 @@ class DBImpl : public DB
// finishes. For example in CompactRange.
Status TEST_AtomicFlushMemTables(
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
const FlushOptions& flush_opts);
const FlushOptions& flush_opts, bool non_blocking_write_thread = false);
void TEST_BeginWriteStall();
void TEST_EndWriteStall();
// Wait for background threads to complete scheduled work.
Status TEST_WaitForBackgroundWork();
@@ -1555,6 +1589,21 @@ class DBImpl : public DB
}
}
// Returns false when the wait times out while the DB is still blocked.
bool BlockFor(uint64_t timeout_micros) {
MutexLock lock(&state_mutex_);
const std::chrono::microseconds deadline(
SystemClock::Default()->NowMicros() + timeout_micros);
while (state_ == State::BLOCKED) {
TEST_SYNC_POINT("WBMStallInterface::BlockDB");
if (SystemClock::Default()->TimedWait(&state_cv_, deadline) &&
state_ == State::BLOCKED) {
return false;
}
}
return true;
}
// Called from WriteBufferManager. This function changes the state_
// to State::RUNNING indicating the stall is cleared and DB can proceed.
void Signal() override {
@@ -1575,6 +1624,24 @@ class DBImpl : public DB
State state_;
};
// Adapts DBImpl to the shared WriteBufferManager flush registry.
class WBMFlushInitiator : public FlushInitiator {
public:
WBMFlushInitiator(DBImpl* db, bool atomic_flush)
: FlushInitiator(atomic_flush), db_(db) {}
bool ScheduleFlush() override {
return db_->ScheduleWriteBufferManagerFlush();
}
bool TryRefreshMemoryAccounting() override {
return db_->TryRefreshFlushableMemAccounting();
}
private:
DBImpl* const db_;
};
static void TEST_ResetDbSessionIdGen();
static std::string GenerateDbSessionId(Env* env);
@@ -1592,6 +1659,9 @@ class DBImpl : public DB
// db_session_id_ is an identifier that gets reset
// every time the DB is opened
std::string db_session_id_;
// Declared before versions_ so memtables cannot outlive their allocation
// accounting target during destruction.
std::unique_ptr<FlushInitiator> wbm_flush_initiator_;
std::unique_ptr<VersionSet> versions_;
// Flag to check whether we allocated and own the info log file
bool own_info_log_;
@@ -1684,6 +1754,13 @@ class DBImpl : public DB
// with corresponding column_family_data and column_family_options.
class RecoveryContext {
public:
RecoveryContext() = default;
// No copying allowed
RecoveryContext(const RecoveryContext&) = delete;
RecoveryContext& operator=(const RecoveryContext&) = delete;
RecoveryContext(RecoveryContext&&) = delete;
RecoveryContext& operator=(RecoveryContext&&) = delete;
~RecoveryContext() {
for (auto& edit_list : edit_lists_) {
for (auto* edit : edit_list) {
@@ -1716,6 +1793,10 @@ class DBImpl : public DB
std::unordered_map<uint32_t, uint32_t> map_; // cf_id to index;
autovector<ColumnFamilyData*> cfds_;
autovector<autovector<VersionEdit*>> edit_lists_;
// Files found in the primary DB directory by MaybeUpdateNextFileNumber().
// Reused for OPTIONS selection so writable open performs one directory
// scan instead of two.
std::vector<std::string> existing_db_files_;
// All existing data files (SST files and Blob files) found during DB::Open.
std::vector<std::string> existing_data_files_;
bool is_new_db_ = false;
@@ -1726,6 +1807,24 @@ class DBImpl : public DB
Status WriteOptionsFile(const WriteOptions& write_options,
bool db_mutex_already_held);
// Writes, renames, and directory-syncs an OPTIONS snapshot, but does not
// publish it in VersionSet. The caller must either commit its file number in
// MANIFEST or leave it as an unreferenced orphan.
Status PersistOptionsFile(const WriteOptions& write_options,
const DBOptions& db_options,
const std::vector<std::string>& cf_names,
const std::vector<ColumnFamilyOptions>& cf_opts,
uint64_t* options_file_number,
uint64_t* options_file_size);
// For tracked DBs, writes the OPTIONS snapshot that is committed with a
// column-family add/drop. Returns file number zero in legacy mode.
Status PersistOptionsFileForColumnFamilyManipulation(
const WriteOptions& write_options,
const std::vector<ColumnFamilyDescriptor>& added_column_families,
const std::vector<uint32_t>& excluded_cf_ids,
uint64_t* options_file_number, uint64_t* options_file_size);
Status CompactRangeInternal(const CompactRangeOptions& options,
ColumnFamilyHandle* column_family,
const Slice* begin, const Slice* end,
@@ -1735,7 +1834,9 @@ class DBImpl : public DB
// 1. WriteThread::Writer::EnterUnbatched() is used.
// 2. db_mutex is NOT held
Status RenameTempFileToOptionsFile(const std::string& file_name,
bool is_remote_compaction_enabled);
uint64_t options_file_number,
uint64_t* options_file_size);
void MaybeDeleteObsoleteOptionsFiles();
Status DeleteObsoleteOptionsFiles(bool schedule_only);
void NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
@@ -2176,6 +2277,12 @@ class DBImpl : public DB
explicit WriteContext(bool create_superversion = false)
: superversion_context(create_superversion) {}
// No copying allowed
WriteContext(const WriteContext&) = delete;
WriteContext& operator=(const WriteContext&) = delete;
WriteContext(WriteContext&&) = delete;
WriteContext& operator=(WriteContext&&) = delete;
~WriteContext() {
superversion_context.Clean();
for (auto& m : memtables_to_free_) {
@@ -2429,6 +2536,15 @@ class DBImpl : public DB
const ColumnFamilyOptions& cf_options,
const std::string& cf_name,
ColumnFamilyHandle** handle);
Status CreateColumnFamiliesImpl(
const ReadOptions& read_options, const WriteOptions& write_options,
const std::vector<ColumnFamilyDescriptor>& column_families,
std::vector<ColumnFamilyHandle*>* handles);
Status CreateColumnFamilyWithPreparedOptions(
const ReadOptions& read_options, const WriteOptions& write_options,
const ColumnFamilyOptions& sanitized_cf_options,
const std::string& cf_name, uint64_t options_file_number,
uint64_t options_file_size, ColumnFamilyHandle** handle);
// Follow-up work to user creating a column family or (families)
Status WrapUpCreateColumnFamilies(
@@ -2436,6 +2552,9 @@ class DBImpl : public DB
const std::vector<const ColumnFamilyOptions*>& cf_options);
Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
Status DropColumnFamilyWithOptionsFile(ColumnFamilyHandle* column_family,
uint64_t options_file_number,
uint64_t options_file_size);
// Delete any unneeded files and stale in-memory entries.
void DeleteObsoleteFiles();
@@ -2631,7 +2750,7 @@ class DBImpl : public DB
// Begin stalling of writes when memory usage increases beyond a certain
// threshold.
void WriteBufferManagerStallWrites();
Status WriteBufferManagerStallWrites(WriteContext* write_context);
Status ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
WriteBatch* my_batch);
@@ -2659,6 +2778,16 @@ class DBImpl : public DB
ReadOnlyMemTable* new_imm = nullptr,
SequenceNumber last_seqno = 0);
// `refresh_wbm_accounting` can be false when a caller switches a batch of
// memtables and refreshes the DB-wide accounting once after the batch.
Status SwitchMemtableImpl(ColumnFamilyData* cfd, WriteContext* context,
ReadOnlyMemTable* new_imm,
SequenceNumber last_seqno,
bool refresh_wbm_accounting);
// REQUIRES: mutex_ is held
void UpdateWBMAccountingAfterMemtableSwitch(bool refresh_now);
// Select and output column families qualified for atomic flush in
// `selected_cfds`. If `provided_candidate_cfds` is non-empty, it will be used
// as candidate CFs to select qualified ones from. Otherwise, all column
@@ -2683,6 +2812,18 @@ class DBImpl : public DB
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
bool entered_write_thread = false);
enum class WriteThreadJoinMode { kBlocking, kNonBlocking };
Status FlushMemTableImpl(ColumnFamilyData* cfd, const FlushOptions& options,
FlushReason flush_reason, bool entered_write_thread,
WriteThreadJoinMode join_mode, bool* made_progress);
Status AtomicFlushMemTablesImpl(
const FlushOptions& options, FlushReason flush_reason,
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
bool entered_write_thread, WriteThreadJoinMode join_mode,
bool* made_progress);
// REQUIRES: mutex locked and write queues drained up to the recovery flush
// fence that is about to switch memtables.
void MaybeSyncLastSequenceWithAllocatedForRecovery(FlushReason flush_reason);
@@ -2769,6 +2910,18 @@ class DBImpl : public DB
return static_cast<uint8_t*>(static_cast<void*>(this)) + type;
}
// Checks DB-local write queues; manager-wide stall state would reject healthy
// DBs. REQUIRES: mutex_ held.
bool WouldBlockJoiningWriteThread() {
mutex_.AssertHeld();
return write_thread_.GetBegunCountOfOutstandingStall() != 0 ||
(two_write_queues_ &&
nonmem_write_thread_.GetBegunCountOfOutstandingStall() != 0);
}
Status EnterWriteThreadForNonBlockingFlush(WriteThread::Writer* w,
WriteThread::Writer* nonmem_w);
// REQUIRES: mutex locked and in write thread.
void AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds);
@@ -2778,6 +2931,31 @@ class DBImpl : public DB
// REQUIRES: mutex locked and in write thread.
Status HandleWriteBufferManagerFlush(WriteContext* write_context);
// Column families and memory eligible for a WriteBufferManager flush.
struct FlushableCFs {
ColumnFamilyData* largest = nullptr; // most reclaimable memtable memory
ColumnFamilyData* oldest = nullptr; // smallest memtable creation seq
size_t largest_mem = 0;
size_t total_mem = 0;
};
// Shared selection for bidding and flushing. REQUIRES: mutex_ held.
FlushableCFs CollectFlushableCFs(bool include_waiting_immutable);
// Registers an opened read-write DB without holding mutex_.
void MaybeRegisterFlushInitiator();
// Refreshes the published mutable-memory counters after memtables change.
// REQUIRES: mutex_ held.
void RefreshFlushableMemAccounting();
bool TryRefreshFlushableMemAccounting();
// Queues one WBM flush; false lets the coordinator try another DB.
bool ScheduleWriteBufferManagerFlush();
void BackgroundCallWBMFlush();
// REQUIRES: mutex locked
Status PreprocessWrite(const WriteOptions& write_options,
WalContext* log_context, WriteContext* write_context);
@@ -2816,7 +2994,13 @@ class DBImpl : public DB
// Used by WriteImpl to update bg_error_ when IO error happens, e.g., write
// WAL, sync WAL fails, if paranoid check is enabled.
void WALIOStatusCheck(const IOStatus& status);
void WALIOStatusCheck(const IOStatus& status, uint64_t failed_wal_number = 0,
SequenceNumber failed_wal_sequence = 0,
SequenceNumber failed_wal_sequence_start = 0);
// Extends the inclusive WAL-number cutoff that must not receive more I/O.
// This is lock-free because a WAL error can race with public SyncWAL().
void ExtendWALRecoveryCutoff(uint64_t failed_wal_number);
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
void HandleMemTableInsertFailure(const Status& nonok_memtable_insert_status);
@@ -3123,6 +3307,8 @@ class DBImpl : public DB
return *this;
}
~UnpublishedWAL() = default;
void Reset() {
log_number = 0;
writer.reset();
@@ -3402,6 +3588,16 @@ class DBImpl : public DB
// * whenever a column family is dropped.
InstrumentedCondVar bg_cv_;
// Set only while ResumeImpl performs a file-scoped WAL recovery. Protected
// by mutex_; SwitchMemtable uses it to avoid touching the failed writer.
bool recovering_from_wal_write_error_ = false;
uint64_t recover_wal_through_number_ = 0;
// Once a file-scoped WAL error is reported, all writers through this
// inclusive cutoff are quarantined until recovery detaches them. A
// concurrent SyncWAL() reads this without taking the DB mutex.
std::atomic<uint64_t> wal_recovery_cutoff_{0};
ColumnFamilyHandleImpl* persist_stats_cf_handle_ = nullptr;
bool persistent_stats_cfd_exists_ = true;
@@ -3665,6 +3861,12 @@ class DBImpl : public DB
// number of background memtable flush jobs, submitted to the HIGH pool
int bg_flush_scheduled_ = 0;
// LOW avoids consuming a flush worker while joining the target write thread.
static constexpr Env::Priority kWBMFlushPriority = Env::Priority::LOW;
// Outstanding cross-DB WBM flush jobs, drained during shutdown.
int bg_wbm_flush_scheduled_ = 0;
// stores the number of flushes are currently running
int num_running_flushes_ = 0;
@@ -3975,8 +4177,12 @@ uint64_t FindMinPrepLogReferencedByMemTable(
// Fix user-supplied options to be reasonable
template <class T, class V>
static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
if (static_cast<V>(*ptr) > maxvalue) *ptr = maxvalue;
if (static_cast<V>(*ptr) < minvalue) *ptr = minvalue;
if (static_cast<V>(*ptr) > maxvalue) {
*ptr = maxvalue;
}
if (static_cast<V>(*ptr) < minvalue) {
*ptr = minvalue;
}
}
inline Status DBImpl::FailIfCfHasTs(
+469 -86
View File
@@ -9,6 +9,7 @@
#include <algorithm>
#include <cinttypes>
#include <deque>
#include <memory>
#include <unordered_map>
#include "db/blob/blob_file_partition_manager.h"
@@ -61,6 +62,15 @@ void RecordAtomicFlushRequestReason(Statistics* stats,
}
}
std::vector<uint64_t> MemTableIds(const autovector<ReadOnlyMemTable*>& mems) {
std::vector<uint64_t> ids;
ids.reserve(mems.size());
for (const ReadOnlyMemTable* mem : mems) {
ids.push_back(mem->GetID());
}
return ids;
}
} // namespace
bool DBImpl::EnoughRoomForCompaction(
@@ -310,8 +320,7 @@ Status DBImpl::FlushMemTableToOutputFile(
flush_reason);
bool switched_to_mempurge = false;
size_t prepared_blob_generations = 0;
std::vector<uint64_t> sealed_blob_numbers;
std::vector<uint64_t> prepared_blob_memtable_ids;
// Within flush_job.Run, rocksdb may call event listener to notify
// file creation and deletion.
//
@@ -322,17 +331,17 @@ Status DBImpl::FlushMemTableToOutputFile(
auto mgr_handle = cfd->blob_partition_manager_handle();
auto* mgr = mgr_handle.get();
if (mgr != nullptr) {
prepared_blob_generations = flush_job.GetMemTables().size();
prepared_blob_memtable_ids = MemTableIds(flush_job.GetMemTables());
std::vector<BlobFileAddition> write_path_additions;
std::vector<BlobFileGarbage> write_path_garbages;
std::vector<std::vector<uint64_t>> generation_blob_file_numbers;
mutex_.Unlock();
s = mgr->PrepareFlushAdditions(
write_options, prepared_blob_generations, &write_path_additions,
write_options, prepared_blob_memtable_ids, &write_path_additions,
&write_path_garbages, &generation_blob_file_numbers);
mutex_.Lock();
if (!s.ok()) {
prepared_blob_generations = 0;
prepared_blob_memtable_ids.clear();
} else if (!write_path_additions.empty()) {
const auto& memtables = flush_job.GetMemTables();
assert(generation_blob_file_numbers.size() == memtables.size());
@@ -343,9 +352,6 @@ Status DBImpl::FlushMemTableToOutputFile(
memtables[i]->ProtectSealedBlobFiles(mgr_handle,
generation_blob_file_numbers[i]);
}
for (const auto& addition : write_path_additions) {
sealed_blob_numbers.push_back(addition.GetBlobFileNumber());
}
flush_job.AddExternalBlobFileAdditions(std::move(write_path_additions));
flush_job.AddExternalBlobFileGarbages(std::move(write_path_garbages));
} else if (!write_path_garbages.empty()) {
@@ -389,7 +395,7 @@ Status DBImpl::FlushMemTableToOutputFile(
});
if (cfd->blob_partition_manager() != nullptr &&
prepared_blob_generations > 0) {
!prepared_blob_memtable_ids.empty()) {
auto unconsumed_additions = flush_job.TakeExternalBlobFileAdditions();
auto unconsumed_garbages = flush_job.TakeExternalBlobFileGarbages();
if (!s.ok() || !unconsumed_additions.empty() ||
@@ -398,8 +404,7 @@ Status DBImpl::FlushMemTableToOutputFile(
// in pending_generations_. The same immutable memtables still reference
// these sealed blob files, so a later flush retry must publish the exact
// same additions rather than abandoning them here.
prepared_blob_generations = 0;
sealed_blob_numbers.clear();
prepared_blob_memtable_ids.clear();
}
}
@@ -407,12 +412,9 @@ Status DBImpl::FlushMemTableToOutputFile(
flush_job.Cancel();
}
if (s.ok() && prepared_blob_generations > 0) {
auto* mgr = cfd->blob_partition_manager();
mgr->CommitPreparedGenerations(prepared_blob_generations);
if (!sealed_blob_numbers.empty()) {
mgr->RemoveFilePartitionMappings(sealed_blob_numbers);
}
if (s.ok() && !prepared_blob_memtable_ids.empty()) {
cfd->blob_partition_manager()->CommitFlushedGenerations(
cfd->imm()->GetEarliestMemTableID());
}
if (s.ok()) {
@@ -655,13 +657,12 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
// <bool /* executed */, Status /* status code */>
autovector<std::pair<bool, Status>> exec_status;
std::vector<bool> pick_status;
std::vector<size_t> prepared_blob_generations(num_cfs, 0);
std::vector<std::vector<uint64_t>> prepared_blob_memtable_ids(num_cfs);
for (int i = 0; i != num_cfs; ++i) {
// Initially all jobs are not executed, with status OK.
exec_status.emplace_back(false, Status::OK());
pick_status.push_back(false);
}
std::unordered_map<int, std::vector<uint64_t>> sealed_blob_numbers_by_cf;
bool flush_for_recovery =
bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecovery ||
@@ -693,17 +694,17 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
continue;
}
prepared_blob_generations[i] = jobs[i]->GetMemTables().size();
prepared_blob_memtable_ids[i] = MemTableIds(jobs[i]->GetMemTables());
std::vector<BlobFileAddition> write_path_additions;
std::vector<BlobFileGarbage> write_path_garbages;
std::vector<std::vector<uint64_t>> generation_blob_file_numbers;
mutex_.Unlock();
s = mgr->PrepareFlushAdditions(
write_options, prepared_blob_generations[i], &write_path_additions,
write_options, prepared_blob_memtable_ids[i], &write_path_additions,
&write_path_garbages, &generation_blob_file_numbers);
mutex_.Lock();
if (!s.ok()) {
prepared_blob_generations[i] = 0;
prepared_blob_memtable_ids[i].clear();
break;
}
if (!write_path_additions.empty()) {
@@ -713,10 +714,6 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
memtables[j]->ProtectSealedBlobFiles(mgr_handle,
generation_blob_file_numbers[j]);
}
auto& sealed_blob_numbers = sealed_blob_numbers_by_cf[i];
for (const auto& addition : write_path_additions) {
sealed_blob_numbers.push_back(addition.GetBlobFileNumber());
}
jobs[i]->AddExternalBlobFileAdditions(std::move(write_path_additions));
}
if (!write_path_garbages.empty()) {
@@ -986,7 +983,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
for (int i = 0; i != num_cfs; ++i) {
auto* mgr = cfds[i]->blob_partition_manager();
if (mgr == nullptr || prepared_blob_generations[i] == 0) {
if (mgr == nullptr || prepared_blob_memtable_ids[i].empty()) {
continue;
}
@@ -997,15 +994,11 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
// Same retry semantics as the single-CF flush path above: do not
// consume these prepared generations yet, because the rolled-back
// memtables still need to reuse the same sealed blob files on retry.
prepared_blob_generations[i] = 0;
prepared_blob_memtable_ids[i].clear();
continue;
}
mgr->CommitPreparedGenerations(prepared_blob_generations[i]);
auto it = sealed_blob_numbers_by_cf.find(i);
if (it != sealed_blob_numbers_by_cf.end() && !it->second.empty()) {
mgr->RemoveFilePartitionMappings(it->second);
}
mgr->CommitFlushedGenerations(cfds[i]->imm()->GetEarliestMemTableID());
}
if (s.ok()) {
@@ -1220,7 +1213,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
end_without_ts, "" /*trim_ts*/);
}
std::string begin_str, end_str;
std::string begin_str;
std::string end_str;
auto [begin, end] =
MaybeAddTimestampsToRange(OptSlice::CopyFromPtr(begin_without_ts),
OptSlice::CopyFromPtr(end_without_ts), ts_sz,
@@ -1483,7 +1477,8 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
// at L1 (or LBase), if applicable.
int level = first_overlapped_level;
final_output_level = level;
int output_level = 0, base_level = 0;
int output_level = 0;
int base_level = 0;
for (;;) {
// Always allow L0 -> L1 compaction
if (level > 0) {
@@ -1712,7 +1707,7 @@ void DBImpl::PrepareTrivialMoveEdit(Compaction& c, LogBuffer* log_buffer,
c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->oldest_blob_file_number, f->oldest_ancestor_time,
f->file_creation_time, f->epoch_number, f->file_checksum,
f->file_checksum_func_name, f->unique_id,
f->compensated_range_deletion_size, f->tail_size,
@@ -2033,7 +2028,7 @@ Status DBImpl::PauseBackgroundWork() {
InstrumentedMutexLock guard_lock(&mutex_);
bg_compaction_paused_++;
while (bg_bottom_compaction_scheduled_ > 0 || bg_compaction_scheduled_ > 0 ||
bg_flush_scheduled_ > 0) {
bg_flush_scheduled_ > 0 || bg_wbm_flush_scheduled_ > 0) {
bg_cv_.Wait();
}
bg_work_paused_++;
@@ -2289,7 +2284,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
to_level, f->fd.GetNumber(), f->fd.GetPathId(), f->fd.GetFileSize(),
f->smallest, f->largest, f->fd.smallest_seqno, f->fd.largest_seqno,
f->marked_for_compaction, f->temperature, f->oldest_blob_file_number,
f->oldest_ancester_time, f->file_creation_time, f->epoch_number,
f->oldest_ancestor_time, f->file_creation_time, f->epoch_number,
f->file_checksum, f->file_checksum_func_name, f->unique_id,
f->compensated_range_deletion_size, f->tail_size,
f->user_defined_timestamps_persisted, f->min_timestamp,
@@ -2445,7 +2440,8 @@ Status DBImpl::RunManualCompaction(
assert(input_level == ColumnFamilyData::kCompactAllLevels ||
input_level >= 0);
InternalKey begin_storage, end_storage;
InternalKey begin_storage;
InternalKey end_storage;
CompactionArg* ca = nullptr;
bool scheduled = false;
@@ -2724,12 +2720,54 @@ void DBImpl::MaybeSyncLastSequenceWithAllocatedForRecovery(
}
}
Status DBImpl::EnterWriteThreadForNonBlockingFlush(
WriteThread::Writer* w, WriteThread::Writer* nonmem_w) {
mutex_.AssertHeld();
if (!write_thread_.EnterUnbatchedNonBlocking(w, &mutex_)) {
return Status::Incomplete(
"Write stall in progress, unable to join the write thread to switch "
"memtables");
}
if (two_write_queues_ &&
!nonmem_write_thread_.EnterUnbatchedNonBlocking(nonmem_w, &mutex_)) {
write_thread_.ExitUnbatched(w);
return Status::Incomplete(
"Write stall in progress, unable to join the write thread to switch "
"memtables");
}
if (!error_handler_.IsBGWorkStopped() && !write_controller_.IsStopped() &&
!WouldBlockJoiningWriteThread()) {
return Status::OK();
}
write_thread_.ExitUnbatched(w);
if (two_write_queues_) {
nonmem_write_thread_.ExitUnbatched(nonmem_w);
}
return Status::Incomplete(
"Write stop started while joining the write thread to switch memtables");
}
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
const FlushOptions& flush_options,
FlushReason flush_reason,
bool entered_write_thread) {
return FlushMemTableImpl(cfd, flush_options, flush_reason,
entered_write_thread, WriteThreadJoinMode::kBlocking,
nullptr /* made_progress */);
}
Status DBImpl::FlushMemTableImpl(ColumnFamilyData* cfd,
const FlushOptions& flush_options,
FlushReason flush_reason,
bool entered_write_thread,
WriteThreadJoinMode join_mode,
bool* made_progress) {
if (made_progress != nullptr) {
*made_progress = false;
}
// This method should not be called if atomic_flush is true.
assert(!immutable_db_options_.atomic_flush);
assert(!IsRecoveryFlush(flush_reason));
if (!flush_options.wait && write_controller_.IsStopped()) {
std::ostringstream oss;
oss << "Writes have been stopped, thus unable to perform manual flush. "
@@ -2763,13 +2801,45 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
WriteThread::Writer w;
WriteThread::Writer nonmem_w;
if (needs_to_join_write_thread) {
write_thread_.EnterUnbatched(&w, &mutex_);
if (two_write_queues_) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
if (join_mode == WriteThreadJoinMode::kNonBlocking) {
s = EnterWriteThreadForNonBlockingFlush(&w, &nonmem_w);
if (!s.ok()) {
return s;
}
} else {
write_thread_.EnterUnbatched(&w, &mutex_);
if (two_write_queues_) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
}
}
}
if (join_mode == WriteThreadJoinMode::kNonBlocking && cfd->IsDropped()) {
TEST_SYNC_POINT("DBImpl::FlushMemTableImpl:Dropped");
if (needs_to_join_write_thread) {
write_thread_.ExitUnbatched(&w);
if (two_write_queues_) {
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
}
}
s.PermitUncheckedOk();
return Status::ColumnFamilyDropped();
}
WaitForPendingWrites();
if (join_mode == WriteThreadJoinMode::kNonBlocking &&
flush_reason == FlushReason::kWriteBufferManager &&
cfd->imm()->IsFlushPendingOrRunning()) {
TEST_SYNC_POINT("DBImpl::FlushMemTableImpl:AlreadyFlushing");
if (needs_to_join_write_thread) {
write_thread_.ExitUnbatched(&w);
if (two_write_queues_) {
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
}
}
return Status::Incomplete(
"Column family started flushing before the WBM handoff completed");
}
// Recovery may have released `mutex_` after the earlier `ResumeImpl()`
// sync. Refresh sequence state at the actual memtable-switch fence, after
// both write queues are drained and before `SwitchMemtable()` consumes
@@ -2779,6 +2849,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load() ||
IsRecoveryFlush(flush_reason)) {
s = SwitchMemtable(cfd, &context);
if (s.ok() && made_progress != nullptr) {
*made_progress = true;
}
}
const uint64_t flush_memtable_id = std::numeric_limits<uint64_t>::max();
if (s.ok()) {
@@ -2851,7 +2924,11 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
req.cfd_to_max_mem_id_to_persist.begin()->first;
bool already_queued_for_flush = loop_cfd->queued_for_flush();
bool flush_req_enqueued = EnqueuePendingFlush(req);
if (already_queued_for_flush || flush_req_enqueued) {
if (flush_req_enqueued && made_progress != nullptr) {
*made_progress = true;
}
if (flush_reason != FlushReason::kWriteBufferManager &&
(already_queued_for_flush || flush_req_enqueued)) {
loop_cfd->SetFlushSkipReschedule();
}
}
@@ -2866,7 +2943,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
}
}
NotifyOnManualFlushScheduled({cfd}, flush_reason);
if (flush_reason != FlushReason::kWriteBufferManager) {
NotifyOnManualFlushScheduled({cfd}, flush_reason);
}
TEST_SYNC_POINT("DBImpl::FlushMemTable:AfterScheduleFlush");
TEST_SYNC_POINT("DBImpl::FlushMemTable:BeforeWaitForBgFlush");
if (s.ok() && flush_options.wait) {
@@ -2895,6 +2974,20 @@ Status DBImpl::AtomicFlushMemTables(
const FlushOptions& flush_options, FlushReason flush_reason,
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
bool entered_write_thread) {
return AtomicFlushMemTablesImpl(flush_options, flush_reason,
provided_candidate_cfds, entered_write_thread,
WriteThreadJoinMode::kBlocking,
nullptr /* made_progress */);
}
Status DBImpl::AtomicFlushMemTablesImpl(
const FlushOptions& flush_options, FlushReason flush_reason,
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
bool entered_write_thread, WriteThreadJoinMode join_mode,
bool* made_progress) {
if (made_progress != nullptr) {
*made_progress = false;
}
if (!flush_options.wait && write_controller_.IsStopped()) {
std::ostringstream oss;
oss << "Writes have been stopped, thus unable to perform manual flush. "
@@ -2925,32 +3018,30 @@ Status DBImpl::AtomicFlushMemTables(
candidate_cfds = provided_candidate_cfds;
}
const auto unref_generated_candidates = [&]() {
if (!provided_candidate_cfds.empty()) {
return;
}
for (auto candidate_cfd : candidate_cfds) {
candidate_cfd->UnrefAndTryDelete();
}
candidate_cfds.clear();
};
if (!flush_options.allow_write_stall) {
int num_cfs_to_flush = 0;
for (auto cfd : candidate_cfds) {
bool flush_needed = true;
s = WaitUntilFlushWouldNotStallWrites(cfd, &flush_needed);
if (!s.ok()) {
// Unref the newly generated candidate cfds (when not provided) in
// `candidate_cfds`
if (provided_candidate_cfds.empty()) {
for (auto candidate_cfd : candidate_cfds) {
candidate_cfd->UnrefAndTryDelete();
}
}
unref_generated_candidates();
return s;
} else if (flush_needed) {
++num_cfs_to_flush;
}
}
if (0 == num_cfs_to_flush) {
// Unref the newly generated candidate cfds (when not provided) in
// `candidate_cfds`
if (provided_candidate_cfds.empty()) {
for (auto candidate_cfd : candidate_cfds) {
candidate_cfd->UnrefAndTryDelete();
}
}
unref_generated_candidates();
return s;
}
}
@@ -2965,39 +3056,54 @@ Status DBImpl::AtomicFlushMemTables(
WriteThread::Writer w;
WriteThread::Writer nonmem_w;
if (needs_to_join_write_thread) {
write_thread_.EnterUnbatched(&w, &mutex_);
if (two_write_queues_) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
if (join_mode == WriteThreadJoinMode::kNonBlocking) {
s = EnterWriteThreadForNonBlockingFlush(&w, &nonmem_w);
if (!s.ok()) {
unref_generated_candidates();
return s;
}
} else {
write_thread_.EnterUnbatched(&w, &mutex_);
if (two_write_queues_) {
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
}
}
}
WaitForPendingWrites();
// Keep atomic recovery flushes consistent with the single-CF path: the
// sequence sync must happen after both write queues are drained and before
// any recovery memtable switch reads `LastSequence()`.
// Recovery may have released `mutex_` after the earlier `ResumeImpl()`
// sync. Refresh sequence state at the atomic memtable-switch fence, after
// both write queues are drained and before `SwitchMemtable()` consumes
// `LastSequence()`.
MaybeSyncLastSequenceWithAllocatedForRecovery(flush_reason);
SelectColumnFamiliesForAtomicFlush(&cfds, candidate_cfds, flush_reason);
// Unref the newly generated candidate cfds (when not provided) in
// `candidate_cfds`
if (provided_candidate_cfds.empty()) {
for (auto candidate_cfd : candidate_cfds) {
candidate_cfd->UnrefAndTryDelete();
}
}
unref_generated_candidates();
const bool defer_wbm_accounting_refresh = cfds.size() > 1;
bool switch_attempted = false;
for (auto cfd : cfds) {
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load() &&
!IsRecoveryFlush(flush_reason)) {
continue;
}
cfd->Ref();
s = SwitchMemtable(cfd, &context);
switch_attempted = true;
s = defer_wbm_accounting_refresh
? SwitchMemtableImpl(cfd, &context, nullptr, 0,
false /* refresh_wbm_accounting */)
: SwitchMemtable(cfd, &context);
cfd->UnrefAndTryDelete();
if (!s.ok()) {
break;
}
if (made_progress != nullptr) {
*made_progress = true;
}
}
if (defer_wbm_accounting_refresh && switch_attempted) {
UpdateWBMAccountingAfterMemtableSwitch(true /* refresh_now */);
}
if (s.ok()) {
AssignAtomicFlushSeq(cfds);
@@ -3014,7 +3120,9 @@ Status DBImpl::AtomicFlushMemTables(
}
}
GenerateFlushRequest(cfds, flush_reason, &flush_req);
EnqueuePendingFlush(flush_req);
if (EnqueuePendingFlush(flush_req) && made_progress != nullptr) {
*made_progress = true;
}
MaybeScheduleFlushOrCompaction();
}
@@ -3025,14 +3133,39 @@ Status DBImpl::AtomicFlushMemTables(
}
}
}
NotifyOnManualFlushScheduled(cfds, flush_reason);
if (flush_reason != FlushReason::kWriteBufferManager) {
NotifyOnManualFlushScheduled(cfds, flush_reason);
}
TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:AfterScheduleFlush");
TEST_SYNC_POINT("DBImpl::AtomicFlushMemTables:BeforeWaitForBgFlush");
if (s.ok() && flush_options.wait) {
// Walk cfds and look each one up, so the ids line up with cfds by
// construction. Iterating the map instead pairs ids with the wrong CF
// (its order is unrelated to cfds), and a CF that dropped out of the map
// (an idle CF de-selected during the switch loop, or a duplicate handle
// collapsed by the map) would make the two vectors different lengths.
autovector<const uint64_t*> flush_memtable_ids;
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
flush_memtable_ids.push_back(&(iter.second));
flush_memtable_ids.reserve(cfds.size());
for (auto* cfd : cfds) {
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfd);
flush_memtable_ids.push_back(
iter == flush_req.cfd_to_max_mem_id_to_persist.end()
? nullptr
: &(iter->second));
}
#ifndef NDEBUG
// WaitForFlushMemTables pairs flush_memtable_ids[i] with cfds[i], so each
// entry must point at this cfd's own map id (or be null when absent). Fires
// if the ids are ever rebuilt in the request map's order.
assert(flush_memtable_ids.size() == cfds.size());
for (size_t i = 0; i < cfds.size(); ++i) {
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfds[i]);
assert(flush_memtable_ids[i] ==
(iter == flush_req.cfd_to_max_mem_id_to_persist.end()
? nullptr
: &(iter->second)));
}
#endif // NDEBUG
s = WaitForFlushMemTables(
cfds, flush_memtable_ids,
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
@@ -3072,9 +3205,29 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
flush_req.atomic_flush = true;
GenerateFlushRequest(cfds, flush_reason, &flush_req);
EnqueuePendingFlush(flush_req);
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
flush_memtable_ids.push_back(iter.second);
// Keep flush_memtable_ids aligned with cfds by walking cfds, since the
// request map's order is unrelated to cfds. A CF not in the map waits on
// its whole imm list, matching the max-bound the non-atomic branch uses.
for (auto* cfd : cfds) {
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfd);
flush_memtable_ids.push_back(
iter == flush_req.cfd_to_max_mem_id_to_persist.end()
? std::numeric_limits<uint64_t>::max()
: iter->second);
}
#ifndef NDEBUG
// WaitForFlushMemTables pairs flush_memtable_ids[i] with cfds[i], so each
// entry must be this cfd's own max id. Fires if the ids are ever rebuilt in
// the request map's order, which is unrelated to cfds order.
assert(flush_memtable_ids.size() == cfds.size());
for (size_t i = 0; i < cfds.size(); ++i) {
auto iter = flush_req.cfd_to_max_mem_id_to_persist.find(cfds[i]);
assert(flush_memtable_ids[i] ==
(iter == flush_req.cfd_to_max_mem_id_to_persist.end()
? std::numeric_limits<uint64_t>::max()
: iter->second));
}
#endif // NDEBUG
} else {
for (auto cfd : cfds) {
flush_memtable_ids.push_back(
@@ -3835,11 +3988,34 @@ bool DBImpl::EnqueuePendingFlush(const FlushRequest& flush_req) {
enqueued = true;
}
} else {
// Atomic flush requests bypass the queued_for_flush() deduplication guard.
// This means an atomic request and a non-atomic request for the same CF
// can coexist in the flush queue. This is safe because PickMemtablesToFlush
// uses flush_in_progress_ to prevent double-picking of memtables, and
// BackgroundFlush filters out CFs where !IsFlushPending().
if (flush_req.flush_reason == FlushReason::kWriteBufferManager &&
write_buffer_manager_->ShouldTrackFlushInitiator()) {
for (const FlushRequest& queued : flush_queue_) {
if (!queued.atomic_flush) {
continue;
}
bool covered = true;
for (const auto& requested : flush_req.cfd_to_max_mem_id_to_persist) {
const auto queued_entry =
queued.cfd_to_max_mem_id_to_persist.find(requested.first);
if (queued_entry == queued.cfd_to_max_mem_id_to_persist.end() ||
queued_entry->second < requested.second) {
covered = false;
break;
}
}
if (covered) {
TEST_SYNC_POINT(
"DBImpl::EnqueuePendingFlush:AtomicWBMRequestDeduplicated");
return false;
}
}
}
// Atomic flush requests normally bypass the per-CF queued_for_flush()
// guard. Cross-DB WBM requests are deduplicated above because its
// autonomous retry loop must not repeatedly enqueue the same atomic cut
// while HIGH workers are unavailable. Other atomic requests retain their
// existing behavior.
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
ColumnFamilyData* cfd = iter.first;
cfd->Ref();
@@ -3964,6 +4140,178 @@ void DBImpl::UnscheduleFlushCallback(void* arg) {
TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
}
void DBImpl::RefreshFlushableMemAccounting() {
mutex_.AssertHeld();
versions_->GetColumnFamilySet()->RefreshFlushableMemAccounting();
}
bool DBImpl::TryRefreshFlushableMemAccounting() {
if (!mutex_.TryLock()) {
return false;
}
Defer unlock_mutex([this] { mutex_.Unlock(); });
RefreshFlushableMemAccounting();
return wbm_flush_initiator_ != nullptr &&
wbm_flush_initiator_->HasAccurateFlushableMemUsage();
}
DBImpl::FlushableCFs DBImpl::CollectFlushableCFs(
bool include_waiting_immutable) {
mutex_.AssertHeld();
FlushableCFs result;
SequenceNumber oldest_seq = kMaxSequenceNumber;
for (auto cfd : *versions_->GetColumnFamilySet()) {
if (cfd->IsDropped() || !cfd->initialized()) {
continue;
}
const size_t mutable_mem =
cfd->mem()->IsEmpty() ? 0 : cfd->mem()->WBMTrackedMemoryUsage();
size_t waiting_immutable_mem = 0;
if (include_waiting_immutable) {
if (immutable_db_options_.atomic_flush) {
waiting_immutable_mem =
cfd->imm()->WBMTrackedUnstartedMemTablesMemoryUsage();
} else if (!cfd->imm()->IsFlushPendingOrRunning()) {
waiting_immutable_mem =
cfd->imm()->WBMTrackedUnflushedMemTablesMemoryUsage();
}
}
result.total_mem += mutable_mem + waiting_immutable_mem;
// Non-atomic flushes skip CFs already flushing.
if (cfd->imm()->IsFlushPendingOrRunning()) {
continue;
}
size_t mem = mutable_mem;
if (include_waiting_immutable) {
mem += waiting_immutable_mem;
}
if (mem == 0) {
continue;
}
if (result.largest == nullptr || mem > result.largest_mem) {
result.largest = cfd;
result.largest_mem = mem;
}
const SequenceNumber seq = cfd->mem()->GetCreationSeq();
if (result.oldest == nullptr || seq < oldest_seq) {
result.oldest = cfd;
oldest_seq = seq;
}
}
return result;
}
bool DBImpl::ScheduleWriteBufferManagerFlush() {
{
if (!mutex_.TryLock()) {
return false;
}
Defer unlock_mutex([this] { mutex_.Unlock(); });
// Recheck bid eligibility because DB state may have changed.
if (!write_buffer_manager_->ShouldTrackFlushInitiator() ||
shutdown_initiated_.load(std::memory_order_acquire) ||
shutting_down_.load(std::memory_order_acquire) ||
reject_new_background_jobs_ || !opened_successfully_ || read_only_ ||
error_handler_.IsBGWorkStopped() || write_controller_.IsStopped() ||
// Reject both the pause drain and fully paused states.
bg_work_paused_ > 0 || bg_compaction_paused_ > 0 ||
// Never queue work that would wait behind this DB's stall.
WouldBlockJoiningWriteThread()) {
return false;
}
// A queued or running job does not count as new progress. Returning false
// lets the coordinator try another DB instead of queuing more work behind
// a busy LOW pool.
if (bg_wbm_flush_scheduled_ > 0) {
return false;
}
const FlushableCFs flushable =
CollectFlushableCFs(true /* include_waiting_immutable */);
const bool has_flushable_cf = immutable_db_options_.atomic_flush
? flushable.total_mem > 0
: flushable.largest != nullptr;
if (!has_flushable_cf) {
wbm_flush_initiator_->SetHasFlushableCF(false);
write_buffer_manager_->NotifyFlushInitiatorChanged();
return false;
}
wbm_flush_initiator_->SetHasFlushableCF(false);
write_buffer_manager_->NotifyFlushInitiatorChanged();
++bg_wbm_flush_scheduled_;
}
// The WBM-owned executor provides bounded capacity independent of Env::LOW.
// Run outside mutex_ so DB-local work cannot block other DB mutex users.
BackgroundCallWBMFlush();
return true;
}
void DBImpl::BackgroundCallWBMFlush() {
const bool atomic_flush = immutable_db_options_.atomic_flush;
ColumnFamilyData* cfd_to_flush = nullptr;
bool has_flushable_cf = false;
{
InstrumentedMutexLock l(&mutex_);
if (!shutdown_initiated_.load(std::memory_order_acquire) &&
!shutting_down_.load(std::memory_order_acquire) &&
!reject_new_background_jobs_ && opened_successfully_ && !read_only_ &&
!error_handler_.IsBGWorkStopped() && !write_controller_.IsStopped() &&
bg_work_paused_ == 0 && bg_compaction_paused_ == 0 &&
!WouldBlockJoiningWriteThread()) {
const FlushableCFs flushable =
CollectFlushableCFs(true /* include_waiting_immutable */);
has_flushable_cf =
atomic_flush ? flushable.total_mem > 0 : flushable.largest != nullptr;
if (!atomic_flush) {
cfd_to_flush = flushable.largest;
if (cfd_to_flush != nullptr) {
cfd_to_flush->Ref();
}
}
}
}
TEST_SYNC_POINT("DBImpl::BackgroundCallWBMFlush:AfterPick");
FlushOptions flush_options;
flush_options.wait = false;
flush_options.allow_write_stall = true;
bool made_progress = false;
if (has_flushable_cf) {
Status s;
if (atomic_flush) {
s = AtomicFlushMemTablesImpl(
flush_options, FlushReason::kWriteBufferManager,
{} /* provided_candidate_cfds */, false /* entered_write_thread */,
WriteThreadJoinMode::kNonBlocking, &made_progress);
} else {
s = FlushMemTableImpl(cfd_to_flush, flush_options,
FlushReason::kWriteBufferManager,
false /* entered_write_thread */,
WriteThreadJoinMode::kNonBlocking, &made_progress);
}
if (!s.ok()) {
ROCKS_LOG_WARN(
immutable_db_options_.info_log,
"Flush on behalf of a shared WriteBufferManager failed: %s",
s.ToString().c_str());
}
s.PermitUncheckedError();
}
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCallWBMFlush:MadeProgress",
&made_progress);
InstrumentedMutexLock l(&mutex_);
if (cfd_to_flush != nullptr) {
cfd_to_flush->UnrefAndTryDelete();
}
RefreshFlushableMemAccounting();
--bg_wbm_flush_scheduled_;
write_buffer_manager_->NotifyFlushInitiatorFlushCompleted(
wbm_flush_initiator_.get(), made_progress);
TEST_SYNC_POINT_CALLBACK("DBImpl::BGWorkWBMFlush:done", this);
bg_cv_.SignalAll();
}
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
LogBuffer* log_buffer, FlushReason* reason,
bool* flush_rescheduled_to_retain_udt,
@@ -4194,6 +4542,9 @@ void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
assert(num_running_flushes_ > 0);
num_running_flushes_--;
bg_flush_scheduled_--;
if (write_buffer_manager_->ShouldTrackFlushInitiator()) {
RefreshFlushableMemAccounting();
}
// See if there's more work to be done
MaybeScheduleFlushOrCompaction();
@@ -4852,7 +5203,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
false /* marked_for_compact */,
c->GetOutputTemperature() /* temperature */,
in_file->oldest_blob_file_number,
in_file->oldest_ancester_time,
in_file->oldest_ancestor_time,
out_file_creation_time,
c->MinInputFileEpochNumber(),
dest_writer->GetFileChecksum(),
@@ -5658,15 +6009,47 @@ Status DBImpl::WaitForCompact(
const auto deadline = immutable_db_options_.clock->NowMicros() +
wait_for_compact_options.timeout.count();
for (;;) {
// The coordinator reserves this DB before invoking its non-blocking
// scheduling callback. Drain that DB-specific reservation without holding
// mutex_; successful callbacks publish bg_wbm_flush_scheduled_ before
// releasing the reservation.
if (write_buffer_manager_ != nullptr &&
write_buffer_manager_->ShouldTrackFlushInitiator() &&
wbm_flush_initiator_ != nullptr) {
// Fence all coordinator requests visible before this local quiescence
// check. Candidate retrieval either observes this generation and skips
// the DB, or atomically publishes a handoff reservation first.
write_buffer_manager_->FenceFlushInitiator(wbm_flush_initiator_.get());
}
if (wbm_flush_initiator_ != nullptr &&
wbm_flush_initiator_->HasPendingFlushHandoff()) {
std::chrono::microseconds handoff_timeout{0};
if (wait_for_compact_options.timeout.count() != 0) {
const uint64_t now = immutable_db_options_.clock->NowMicros();
if (now >= deadline) {
return Status::TimedOut();
}
handoff_timeout = std::chrono::microseconds(deadline - now);
}
mutex_.Unlock();
const bool handoff_finished =
wbm_flush_initiator_->WaitForPendingFlushHandoff(handoff_timeout);
mutex_.Lock();
if (!handoff_finished) {
return Status::TimedOut();
}
continue;
}
if (shutting_down_.load(std::memory_order_acquire)) {
return Status::ShutdownInProgress();
}
if (bg_work_paused_ && wait_for_compact_options.abort_on_pause) {
return Status::Aborted();
}
// A WBM job can enqueue a regular flush, so wait for both to quiesce.
if ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
bg_flush_scheduled_ || unscheduled_compactions_ ||
!parked_compaction_cfds_.empty() ||
bg_flush_scheduled_ || bg_wbm_flush_scheduled_ ||
unscheduled_compactions_ || !parked_compaction_cfds_.empty() ||
(wait_for_compact_options.wait_for_purge && bg_purge_scheduled_) ||
unscheduled_flushes_ || error_handler_.IsRecoveryInProgress()) &&
(error_handler_.GetBGError().ok())) {
+37 -3
View File
@@ -144,6 +144,18 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
return s;
}
size_t DBImpl::TEST_GetWBMFlushableMemUsage() {
InstrumentedMutexLock l(&mutex_);
assert(wbm_flush_initiator_ != nullptr);
RefreshFlushableMemAccounting();
return wbm_flush_initiator_->GetFlushableMemUsage();
}
bool DBImpl::TEST_IsWBMFlushInitiatorFlushable() const {
assert(wbm_flush_initiator_ != nullptr);
return wbm_flush_initiator_->IsFlushable();
}
Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
ColumnFamilyHandle* cfh) {
FlushOptions fo;
@@ -182,9 +194,31 @@ Status DBImpl::TEST_FlushMemTableWithListenerWait(bool allow_write_stall,
Status DBImpl::TEST_AtomicFlushMemTables(
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
const FlushOptions& flush_opts) {
return AtomicFlushMemTables(flush_opts, FlushReason::kTest,
provided_candidate_cfds);
const FlushOptions& flush_opts, bool non_blocking_write_thread) {
const auto join_mode = non_blocking_write_thread
? WriteThreadJoinMode::kNonBlocking
: WriteThreadJoinMode::kBlocking;
return AtomicFlushMemTablesImpl(
flush_opts, FlushReason::kTest, provided_candidate_cfds,
false /* entered_write_thread */, join_mode, nullptr /* made_progress */);
}
void DBImpl::TEST_BeginWriteStall() {
InstrumentedMutexLock l(&mutex_);
write_thread_.BeginWriteStall();
if (wbm_flush_initiator_ != nullptr &&
write_buffer_manager_->ShouldTrackFlushInitiator()) {
wbm_flush_initiator_->SetFlushable(false);
}
}
void DBImpl::TEST_EndWriteStall() {
InstrumentedMutexLock l(&mutex_);
write_thread_.EndWriteStall();
if (wbm_flush_initiator_ != nullptr &&
write_buffer_manager_->ShouldTrackFlushInitiator()) {
wbm_flush_initiator_->SetFlushable(true);
}
}
Status DBImpl::TEST_WaitForBackgroundWork() {
+3 -2
View File
@@ -24,7 +24,8 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
const Slice* begin, const Slice* end) {
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
auto cfd = cfh->cfd();
InternalKey start_key, end_key;
InternalKey start_key;
InternalKey end_key;
if (begin != nullptr) {
start_key.SetMinPossibleForUserKey(*begin);
}
@@ -140,7 +141,7 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
f->fd.GetFileSize(), f->smallest, f->largest,
f->fd.smallest_seqno, f->fd.largest_seqno,
f->marked_for_compaction, f->temperature,
f->oldest_blob_file_number, f->oldest_ancester_time,
f->oldest_blob_file_number, f->oldest_ancestor_time,
f->file_creation_time, f->epoch_number, f->file_checksum,
f->file_checksum_func_name, f->unique_id,
f->compensated_range_deletion_size, f->tail_size,
+10 -1
View File
@@ -244,9 +244,14 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
// Since job_context->min_pending_output is set, until file scan finishes,
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
// here but later find newer generated unfinalized files while scanning.
assert(job_context != nullptr);
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
job_context->min_options_file_number = MinOptionsFileNumberToKeep();
job_context->manifest_options_file_number =
versions_->has_committed_options_file_number()
? versions_->options_file_number()
: 0;
job_context->min_blob_file_number_to_keep =
versions_->current_next_file_number();
for (auto* cfd : *versions_->GetColumnFamilySet()) {
@@ -730,7 +735,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
}
break;
case kOptionsFile:
keep = (number >= optsfile_num2);
keep = (number >= optsfile_num2) ||
(number == state.manifest_options_file_number);
break;
case kCompactionProgressFile:
// Keep compaction progress files - they are managed
@@ -1202,6 +1208,9 @@ Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
if (!s.ok()) {
break;
}
if (path == NormalizePath(dbname_)) {
recovery_ctx->existing_db_files_ = files;
}
for (const auto& fname : files) {
uint64_t number = 0;
FileType type;
Loaded 100 of 639 files, more files were not shown because too many files have changed in this diff. Show more