Compare commits

..
105 Commits
Author SHA1 Message Date
Richard Barnes fa616f0e79 Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/volatile_tier_impl.cc +5 (#14296)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14296

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: anand1976

Differential Revision: D92013593

fbshipit-source-id: 0b4e00339bef3f76fc5b9ad35e2383c5e4f828f9
2026-02-25 10:30:50 -08:00
Richard Barnes 96db15c6e6 Fix string-conversion issue in internal_repo_rocksdb/repo/table/block_based/index_builder.cc +1
Summary:
This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(false && "this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Differential Revision: D92886041

fbshipit-source-id: 6adfaa102f12e293491cc579ec92b48834d1d0a8
2026-02-25 10:30:50 -08:00
Richard Barnes 912596031a Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/block_cache_tier_file.cc +2 (#14312)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14312

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: dmm-fb

Differential Revision: D92528316

fbshipit-source-id: 93fbb624e8731c4cdb559746b44c1aa71d786304
2026-02-25 10:30:50 -08:00
Andrew Chang f4fe40845a Update HISTORY and version to 10.11.2 2026-02-25 10:30:29 -08:00
Andrew Chang 40a5f27f6e Check io_uring probe result in SupportedOps (#14355)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14355

SupportedOps advertised kAsyncIO based only on the IsIOUringEnabled() weak symbol check, without verifying that the constructor's io_uring probe actually succeeded. Add a thread_local_async_read_io_urings_ null check so kAsyncIO is only reported when the probe passed. Also update the constructor to probe with the same IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN flags that ReadAsync and MultiRead use at runtime.

Reviewed By: anand1976

Differential Revision: D93780065

fbshipit-source-id: 6f51f544b267cb39d09b49949a9485f55eeae12e
2026-02-24 16:36:14 -08:00
Andrew Chang 9676ce2f84 Propagate file_checksum through FileOptions on NewRandomAccessFile (#14321)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14321

Add file_checksum and file_checksum_func_name fields to FileOptions so that downstream FileSystem implementations can access per-file checksum metadata when SST files are opened. The fields are populated from FileMetaData at all call sites where SST files are opened via NewRandomAccessFile: TableCache::GetTableReader, Version::GetTableProperties, and CompactionJob::ReadTablePropertiesDirectly. Also fixes the fallback path in TableCache::GetTableReader to use the local fopts (with temperature and checksum) instead of the original file_options.

Added a kNoFileChecksumFuncName which is distinct from  kUnknownFileChecksumFuncName:

 - kUnknownFileChecksumFuncName ("Unknown"): We have FileMetaData for this file, and the metadata says no checksum was computed (no factory was configured when the file was written). This is a property of the file itself.
- kNoFileChecksumFuncName ("Unavailable"): We don't even have FileMetaData — we're opening this file in a context where there's no checksum metadata to propagate at all (e.g., SstFileDumper, SstFileReader, checksum generation). It's a property of the call site, not the file.

So the assertion file_checksum.empty() is correct for both, but for different reasons — one says "the file has no checksum," the other says "we have no idea about this file's checksum."

Reviewed By: pdillinger

Differential Revision: D92728944

fbshipit-source-id: 8fd34ea22ca87090b26d0a55c921f354f97f1ffc
2026-02-24 16:36:06 -08:00
Peter Dillinger e506aa7154 Fix format compatibility issues, extend test (#14323)
Summary:
See https://github.com/facebook/rocksdb/issues/14240 which brought this to my attention. Here I've added range deletions and compactions to the format compatible test, and fixed or worked-around compatibility issues (likely longstanding).

The first fix was in Version::MaybeInitializeFileMetaData for an assertion failure simply from adding range deletions from some 5.x version.

The second fix is a broader work-around for older SST files with unreliable num_entries/num_range_deletions/num_deletions statistics in their table properties. We depend on them only for some paranoid checks for compaction, so in my assessment the best way to deal with those files is to exclude the paranoid checks when dealing with the files with unrelaible data. (Details in code comments.) The important part is that compacting old files is exceptionally rare, so we aren't really interefering with the paranoid checks doing thier job on an ongoing basis.

This depends on https://github.com/facebook/rocksdb/issues/14315 (just landed) because there is a remaining undiagnosed problem with some very early releases, but I'm not fixing that because its support is being dropped.

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

Test Plan: test extended (ran locally excluding some releases)

Reviewed By: xingbowang

Differential Revision: D93032653

Pulled By: pdillinger

fbshipit-source-id: f90b32f30ba4764692e68d23705f42c778e0dc1d
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 6d4c2c3297 Fix racy assertion in AbortIOPartialHandlesBug test (#14319)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14319

The test asserted h1->is_finished and h2->is_finished immediately after AbortIO({H0}), before calling Poll. This is invalid because AbortIO only guarantees that handles in its abort set are finalized. Non-aborted handles' CQEs may or may not be consumed during AbortIO depending on io_uring completion ordering. If H0's two CQEs (original read + cancel) arrive before H1/H2's CQEs, AbortIO breaks out of its wait loop without processing them. Move the H1/H2 is_finished assertions to after Poll, which correctly handles either case. Also remove the racy req_count checks for non-aborted handles since Poll does not increment req_count.

Reviewed By: jaykorean

Differential Revision: D92848827

fbshipit-source-id: 0c09b44ceada99877e8311cff799fa94f1056545
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 2ceb3ef4d2 Add a cleanup target to crash_test.mk (#14286)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14286

Add the db_c leanup target which can be used by CI test scripts to delete the db on failure. The db_crashtest.py doesn't automatically delete on error.

Reviewed By: jaykorean

Differential Revision: D91912877

fbshipit-source-id: d36ec0896fba64faaafe055d8673e437e85d0c3a
2026-02-06 12:20:35 -08:00
anand76 edc76202a2 Update HISTORY and version to 10.11.1 2026-02-06 12:20:35 -08:00
Andrew Chang b44c70adcf Fix crash in GetLiveFilesStorageInfo on read-only DB (#14306)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14306

GetLiveFilesStorageInfo crashes when called on a read-only RocksDB because
it calls FlushWAL(), which accesses logs_.back() on an empty deque.

Root cause: DBImplReadOnly overrides SyncWAL() to return NotSupported, but
does NOT override FlushWAL(). Read-only DBs have an empty logs_ deque
because they don't create WAL writers during recovery - there's nothing to
write, so no WAL infrastructure is initialized.

The reason SyncWAL was originally marked NotSupported is that these WAL
operations (SyncWAL syncs buffer to disk, FlushWAL flushes to OS buffer)
require an active WAL writer at logs_.back().writer. Since read-only DBs:
1. Cannot perform writes
2. Don't create WAL files for writing
3. Have an empty logs_ deque

...there's no WAL writer to sync or flush. The operations are semantically
meaningless, not just "forbidden write operations."

The fix adds a FlushWAL override matching the SyncWAL pattern. The caller
in db_filesnapshot.cc:403-405 already handles IsNotSupported() gracefully:
  if (s.IsNotSupported()) { s = Status::OK(); }

Reviewed By: pdillinger

Differential Revision: D92419557

fbshipit-source-id: 7079071209b3c7be41a2c98c9b691e68bc031595
2026-02-06 12:20:35 -08:00
Andrew Chang 71eb5a1646 Propagate Poll errors in FilePrefetchBuffer::PollIfNeeded (#14282)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14282

Previously, `PollIfNeeded` returned `void` and silently ignored errors from
`fs_->Poll()` by calling `PermitUncheckedError()`. This could lead to silent
data corruption or unexpected behavior when Poll operations fail.

This diff changes `PollIfNeeded` to return `Status` and properly propagate
Poll errors to callers. When Poll fails:
1. The IO handle is cleaned up via `DestroyAndClearIOHandle`
2. The error status is returned to the caller
3. Callers (`HandleOverlappingAsyncData` and `PrefetchInternal`) now check
   and propagate this error

Also adds a `TEST_SYNC_POINT_CALLBACK` to allow tests to inject Poll errors.

Reviewed By: anand1976

Differential Revision: D91624185

fbshipit-source-id: 8dd0ee6588ed1ce4bf080bcf857b778c5140ccf5
2026-02-06 12:20:35 -08:00
Anand Ananthabhotla 63a256d768 Fix AbortIO consuming completions for non-aborted handles (#14301)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14301

When AbortIO was called with a subset of outstanding async read handles,
it would consume io_uring completions for handles NOT in the abort set
but fail to finalize them. This caused subsequent Poll calls on those
handles to hang forever waiting for completions that had already been
consumed.

The fix adds an `is_being_aborted` flag to Posix_IOHandle that is set
when submitting the cancel request. When processing completions in
AbortIO, handles with this flag wait for req_count==2 (original + cancel),
while handles without the flag are finalized immediately at req_count==1.

Also refactored the completion finalization logic into a shared
FinalizeAsyncRead() helper function used by both Poll and AbortIO.

Reviewed By: mszeszko-meta, archang19

Differential Revision: D92230883

fbshipit-source-id: e6d11e009a4930e5608459771990f6cf7d46d827
2026-02-06 11:47:45 -08:00
mszeszko-meta 87d4b0d579 Merge pull request #14289 from facebook/cherry_pick_14280_to_10_11
Cherry picks into 10.11.fb
2026-02-02 10:47:06 -08:00
Adam Retter 09bc20b16e Fixes the Windows VS 2022 build (#14280)
Summary:
When building a Release on Windows RTTI is not available, so asserts that use dynamic_cast need to be disabled

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

Reviewed By: nmk70

Differential Revision: D91807791

Pulled By: mszeszko-meta

fbshipit-source-id: e29c19c757bcd076a1f09ed40b306bb50ba9e882
2026-01-30 16:36:19 -08:00
Peter Dillinger dd9a806edd Update HISTORY for 10.11.0 2026-01-26 12:42:18 -08:00
Peter Dillinger ad218cacf6 Temporary disable multiscan_use_async_io in crash test (#14263)
Summary:
Seeing many errors like this

```
Iterator diverged from control iterator which has value ...
iterator is not valid with status: IO error: Req failed: Unknown error -14
VerifyIterator failed. Control CF default
```

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

Test Plan: CI

Reviewed By: archang19

Differential Revision: D91478886

Pulled By: pdillinger

fbshipit-source-id: 94b955b6ecdb7a3cab39dac8e7b0d1047d49a0bb
2026-01-26 09:06:33 -08:00
Peter Dillinger 6a79e02ebd Support pre-defined compression dictionaries (#14253)
Summary:
... in addition to those derived from samples. This could be useful when trade-offs favor an offline trained dictionary that's good for the whole work load, which can involve heavy-weight training, vs. on-the-fly training on samples for each file, which has limitations.

This involves some breaking changes to some deeper parts of the new compression API. I'm not concerned about performance because this doesn't touch the per-block parts of the API, just the per-file parts.

Bonus: change to
CompressionManagerWrapper::FindCompatibleCompressionManager to implement what is likely the preferred behavior.

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

Test Plan: unit test included

Reviewed By: hx235

Differential Revision: D91082208

Pulled By: pdillinger

fbshipit-source-id: 1442db65e15c9435437204c19787c96f7a40a207
2026-01-23 10:01:50 -08:00
Peter Dillinger a9906f0dd0 A better approach to clearing DBs for crash test (#14254)
Summary:
Clearing DB dir for crash test is currently a hodgepodge of
1. Caller of db_crashtest.py maybe tries to clear the dir
2. db_crashtest.py tries to clear the dir in get_dbname() (but ignoring failure)
3. db_crashtest.py passes --destroy_db_initially to some db_stress calls as needed
4. db_crashtest.py tries to clear the dir between some db_stress calls
5. db_crashtest.py tries to clear the dir after everything is done and successful (no artifacts to investigate or save) (but ignoring failure)
6. Try to add more uniqueness to the directory from https://github.com/facebook/rocksdb/issues/14249

This change reverts or replaces 2, 4, 5, and 6 by doubling-down on (expanding) 3 and a small variant of it:

* crash_test.mk passes --destroy_db_initially=1 so that the first run of db_stress clears the db dir.
* After each db_stress invocation, db_crashtest.py resets destroy_db_initially=0 so that the next invocation reuses the same DB, except in cases where there is an incompatibility that requires a fresh DB (from cases 3 and 4 above).
* On success, uses new `db_stress --destroy_db_and_exit` option to clean up the DB dir without needing a custom cleanup_cmd (now ignored)

Note that although case 1 is likely obsolete, it is out of control of an open source PR.

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

Test Plan: some manual runs

Reviewed By: xingbowang

Differential Revision: D91164731

Pulled By: pdillinger

fbshipit-source-id: 0a66c8c0e130c9eeacc55af411a18a09bc9debdf
2026-01-22 11:48:06 -08:00
Anand Ananthabhotla b89d290c20 Add MultiScan statistics (#14248)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14248

### Overview

This diff introduces the addition of multi-scan statistics to RocksDB, enhancing the database's ability to monitor and analyze performance during multi-scan operations.

### Key Changes

#### Implemented Multi-Scan Statistics

The following statistics were implemented to provide deeper insights into multi-scan operations:

- **MULTISCAN_PREPARE_MICROS**: Measures the time (in microseconds) spent preparing for multi-scan operations.
- **MULTISCAN_BLOCKS_PER_PREPARE**: Tracks the number of blocks processed per multi-scan prepare operation.
- **Wasted Prefetch Blocks Count**: Counts the number of prefetched blocks that were not used (i.e., wasted) if the iterator is abandoned before accessing them.
- **MULTISCAN_TOTAL_BLOCKS_SCANNED**: Tracks the total number of blocks scanned during all multi-scan operations.
- **MULTISCAN_TOTAL_KEYS_SCANNED**: Measures the total number of keys scanned across all multi-scan operations.
- **MULTISCAN_TOTAL_MICROS**: Captures the total time (in microseconds) spent in multi-scan operations.
- **MULTISCAN_PREFETCHED_BLOCKS**: Counts the number of blocks that were prefetched during multi-scan operations.
- **MULTISCAN_USED_PREFETCH_BLOCKS**: Tracks the number of prefetched blocks that were actually used during multi-scan operations.

### Impact

This diff provides more fine-grained statistics for multi-scan operations, allowing developers and users to better understand and optimize the performance of their RocksDB instances.

Reviewed By: krhancoc

Differential Revision: D91053297

fbshipit-source-id: 7158741b9f026c0b5ce8ba1264dbd137e7fe985d
2026-01-21 23:23:38 -08:00
Andrew Chang f84351de98 Fix AbortIO hang when aborting multiple io_uring handles (#14252)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14252

Fixed a bug in PosixFileSystem::AbortIO that could cause an infinite hang
when aborting multiple concurrent async IO handles.

The bug occurred in the completion processing loop: when an io_uring
completion arrived for a handle other than the one currently being waited
for (io_handles[i]), the code would increment that handle's req_count but
only mark it as finished if it also matched io_handles[i]. This meant
completions for other handles were consumed but those handles were never
marked as finished.

Later, when iterating to those handles, the code would enter
io_uring_wait_cqe expecting more completions, but they had already been
consumed - causing an infinite hang.

The fix aligns AbortIO's completion handling with what Poll() already does:
mark handles as finished whenever their completions arrive, regardless of
which handle we're currently waiting for in the outer loop. Only the break
statement remains conditional on matching io_handles[i].

Reviewed By: anand1976

Differential Revision: D91070044

fbshipit-source-id: 47faf5f0df3e26a2aa83444bbac623f43f560933
2026-01-21 19:17:05 -08:00
Andrew Chang f312633aff Fix AbortIO documentation to match actual behavior (#14251)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14251

The AbortIO API documentation incorrectly stated that the callback
should NOT be called for aborted io_handles. However, the actual
implementation in fs_posix.cc does invoke the callback with
IOStatus::Aborted() status after cancelling requests:

```
// fs_posix.cc:1252-1260
if (posix_handle->req_count == 2 &&
    static_cast<Posix_IOHandle*>(io_handles[i]) == posix_handle) {
  posix_handle->is_finished = true;
  FSReadRequest req;
  req.status = IOStatus::Aborted();
  posix_handle->cb(req, posix_handle->cb_arg);

  break;
}
```

This change corrects the documentation to match the actual behavior
in RocksDB.

Reviewed By: anand1976

Differential Revision: D91073466

fbshipit-source-id: 47ae14a09e9386cc68049ca272d6b712f5a9bed7
2026-01-21 16:26:06 -08:00
Peter Dillinger a6af317476 Use format_version=7 by default, fix perf bug (#14239)
Summary:
Since it's been > 6 months and we have production uses, migrate to fv=7 by default. One unit test needed an update for the change to table properties with fv=7.

On making this change, PresetCompressionDictTest tests detected extra memory usage by decompressing LZ4 with dictionary compression. This turned out to be a bug in `std::find` usage that led to using the ZSTD-optimized decompressor (with digested dictionary usage) in cases where it is not needed. I've fixed the bug and improved the unit tests that found the bug.

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

Test Plan: existing tests, including format compatible CI job (updated, and run locally with SHORT_TEST=1)

Reviewed By: hx235

Differential Revision: D90728697

Pulled By: pdillinger

fbshipit-source-id: 8f1a0e9ca59a88c18eaa4cdfdea00309175ce30a
2026-01-21 09:28:06 -08:00
Xingbo Wang eb5e1a2d1f Use unique DB directory when TEST_TMPDIR is set (#14249)
Summary:
Some of the stress tests script run tests multiple times with TEST_TMPDIR set. When TEST_TMPDIR is set, the db directory is a fixed string. This caused the same DB directory was reused across db_crashtest.py script run. Typically, the DB folder is cleaned up after db_crashtest.py complete. But sometimes, the clean up command could fail. This caused the DB folder to be reused across different db_crashtest.py runs. Meantime, each db_crashtest.py run would randomize some of the parameters. This caused different parameters to be used with same DB directory, violating some of the assumption such as use_put_entity_one_in parameter to be not changed between runs. This change added a suffix to DB directory, so that each db_crashtest.py script run would generate a unique DB directory, which prevents the clean up failure issue causing test flaky.

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

Test Plan:
Stress test local run

```
TEST_TMPDIR=/tmp/aaa /usr/local/bin/python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --simple blackbox  --duration 15 --interval 10

>>> Running db_stress with pid=113810: ./db_stress ... --db=/tmp/aaa/rocksdb_crashtest_blackbox_6967584463401575611 ...
```

Reviewed By: hx235

Differential Revision: D91069655

Pulled By: xingbowang

fbshipit-source-id: 327fc3cd0d8e3ef4b49e182e21bcd91a10647710
2026-01-20 17:31:28 -08:00
Xingbo Wang ea5e649225 Fix an infinite compaction loop bug with udt (#14228)
Summary:
Problem

The TEST_WaitForCompact in
TimestampCompatibleCompactionTest.UdtTombstoneCollapsingTest would sometimes
run forever, indicating an infinite compaction loop.
Issue https://github.com/facebook/rocksdb/issues/14223

Root Cause

In ComputeBottommostFilesMarkedForCompaction(), files were marked for
bottommost compaction based only on the condition largest_seqno <
oldest_snapshot_seqnum. However, for User-Defined Timestamps (UDT) columns,
compaction can only zero sequence numbers when the file's maximum timestamp is
below full_history_ts_low.

When timestamps were above this threshold:
1. File gets marked for compaction (seqno condition met)
2. Compaction runs but cannot zero seqno (timestamp condition not met)
3. Output file immediately gets re-marked for compaction
4. Infinite loop

Solution

Added timestamp range tracking to FileMetaData and updated the marking logic to
check timestamps before marking files.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D90586045

Pulled By: xingbowang

fbshipit-source-id: addfa4f988db8c87fb513a1bf58ee54623a6c210
2026-01-20 14:10:41 -08:00
anand76 2a7a6a6d72 Don't assert on async_read.status in MultiScan code path (#14244)
Summary:
Surface async read errors instead of asserting on them. This makes it easier to debug stress test failures. Async reads can fail for legitimate reasons, such as fs errors.

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

Reviewed By: hx235

Differential Revision: D90878515

Pulled By: anand1976

fbshipit-source-id: 6335d4b06ddf250b26842ce94e3f5263356b2695
2026-01-20 12:18:19 -08:00
Peter Dillinger 88aff40c97 New io stats for unknown file temperature last vs. non-last (#14243)
Summary:
These will be useful for qualifying non-tiered workloads for tiered storage.

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

Test Plan:
unit test included

I'm not concerned about performance because this fits pretty nicely into some existing code and only adds overhead when (expensive) IOs are done.

Reviewed By: jaykorean

Differential Revision: D90870348

Pulled By: pdillinger

fbshipit-source-id: 984411123bcd54c249a949da813ff04fedacc6a4
2026-01-16 11:01:37 -08:00
Peter Dillinger c6d08d3efe Use new compression APIs in db_bench (#14241)
Summary:
To move away from OLD_CompressData / OLD_UncompressData. Also improved some error/warning messages.

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

Test Plan: manual tests showing similar performance, runs with ASAN/UBSAN to check for issues

Reviewed By: hx235

Differential Revision: D90793708

Pulled By: pdillinger

fbshipit-source-id: e0655f7bed8d85e5ea110167dca73c6664f7465b
2026-01-16 10:01:42 -08:00
Peter Dillinger a1af6f9f64 Use new compression APIs internally for sample_for_compression (#14230)
Summary:
Trying to get rid of uses of OLD_CompressData / OLD_UncompressData. Some performance optimizations and corrections for better accounting also.

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

Test Plan:
* exanded unit test to be more complete / rigorous
* manual before-and-after db_bench runs with the option, seeing table properties as expected

Reviewed By: hx235

Differential Revision: D90545476

Pulled By: pdillinger

fbshipit-source-id: 2f7c577574bcc4b2acafa002761ec1cad7fdb093
2026-01-14 09:35:54 -08:00
Peter Dillinger 57036b68d9 Migrate blob handling to new compression APIs (#14234)
Summary:
as part of the effort to get rid of OLD_CompressData and OLD_UncompressData and the old implementations in compression.h.

It's unfortunate the the existing blob file schema doesn't allow storing blobs uncompressed when the compressed version is larger, so we have to work around that.

Note that use of GrowableBuffer in place of std::string is intended to avoid the potential performance overhead of zeroing out memory before overwriting it.

Also includes some cleanup of includes

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

Test Plan:
some unit test updates as needed. Crash test covers integrated blob support.

I'm not too concerned about performance, as until a future schema change, this code is committing the grave performance error of storing compressed data larger than uncompressed.

Reviewed By: mszeszko-meta, hx235

Differential Revision: D90544049

Pulled By: pdillinger

fbshipit-source-id: 2f2ed16de63990b797cc06c8dad36b5869dac302
2026-01-14 09:35:16 -08:00
Xingbo Wang 256838180e Fix stress test deadlock failure in TestPut (#14235)
Summary:
Deadlock or timeout is possible in TestPut, when TestMultiGet was executed at the same time, because it executes MaybeAddKeyToTxnForRYW, which writes to the same key space but does not acquire stress test level mutex. Therefore, RocksDB could return deadlock error.

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

Test Plan: Stress test

Reviewed By: hx235

Differential Revision: D90621772

Pulled By: xingbowang

fbshipit-source-id: eb808193ded06b69a8161320f88d5ba4e20b4901
2026-01-14 06:44:01 -08:00
Xingbo Wang 2893c25ca2 Support printing block checksum in sst_dump (#14222)
Summary:
Support printing block checksum in sst_dump

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

Test Plan:
manual test, sample output

```
Data Block # 1 @ 00B021
   Data block checksum type: 4  checksum value: 0x40614fb3  offset: 0  size: 4272  compression type: 0
```

Reviewed By: jaykorean

Differential Revision: D90286789

Pulled By: xingbowang

fbshipit-source-id: 71324e04549bea070d80b45a81b562ad331a7840
2026-01-10 05:05:18 -08:00
Xingbo Wang a6325e9564 Add block type to corruption error message (#14225)
Summary:
Add block type to corruption error message

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

Test Plan: Unit test

Reviewed By: jaykorean

Differential Revision: D90329899

Pulled By: xingbowang

fbshipit-source-id: 6fa925d1704c7c19c98d6067628b73a7c0904c3e
2026-01-09 14:37:46 -08:00
Xingbo Wang 4bcec5ae89 Fix autoconf download failure in folly build (#14226)
Summary:
Folly download dependencies directly from external source. Sometimes, this could fail due to external website instability. To solve this, we added github cache to cache the dependencies. We also added a python script to try different sources during download to reduce the chance of failure.

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

Test Plan: github CI

Reviewed By: krhancoc, archang19

Differential Revision: D90343051

Pulled By: xingbowang

fbshipit-source-id: 3faad6aaa6c1bfd361b9e405c298856cd64bf457
2026-01-09 10:43:54 -08:00
Josh Kang 4bf2bcdbb3 Allow setting options for multiple column families (#14201)
Summary:
Currently to set options for multiple CFs, the caller must repeatedly call SetOptions() for each CF. This in turn serializes the entire options file each time. This PR exposes a new API that allows SetOptions to be called on multiple CFs at once, thus only paying the OPTIONS file serialization once.

Also added a new unit test for SetOptions.

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

Reviewed By: pdillinger

Differential Revision: D89735181

Pulled By: joshkang97

fbshipit-source-id: 9b7a721b7e8769b653243b1581678ffd05d038e8
2026-01-08 15:15:31 -08:00
Ryan Hancock 2b28885c80 Introducing IO Dispatcher (#14135)
Summary:
This diff introduces the IO Dispatcher, which will be used to simplify the code path for MultiScan, while further providing a centralized place to enact policy on how MultiScan is done (i.e., limit memory usage and pinned buffers for example). Right now this diff only encapsulates the functionality done during the Prepare of MultiScan.

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

Reviewed By: anand1976

Differential Revision: D87837261

Pulled By: krhancoc

fbshipit-source-id: 2698910ade02bc3d182413ae07ce69fe7abb7ec5
2026-01-07 10:34:21 -08:00
zaidoon 429b36c22d Add C API for block_align option in BlockBasedTableOptions (#14153)
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/14153

Reviewed By: archang19

Differential Revision: D90211012

Pulled By: jaykorean

fbshipit-source-id: fd87d3d74664f75fbe47946764b1d25aa731c020
2026-01-06 19:03:18 -08:00
Xingbo Wang 6f03c3dfea Fix a flaky unit test UdtTombstoneCollapsingTest (#14220)
Summary:
As compaction scheduling is not deterministic, the existing check is too strict sometimes, causing test to be flaky.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D90143556

Pulled By: xingbowang

fbshipit-source-id: 6780423c63324a4b20fc8b8ccac2051a094c9f4a
2026-01-06 14:29:02 -08:00
Xingbo Wang a7c1acbe9f Mark RateLimiter::GetMode method as const (#14221)
Summary:
Mark RateLimiter::GetMode method as const

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

Test Plan: existing unit test

Reviewed By: jaykorean

Differential Revision: D90182630

Pulled By: xingbowang

fbshipit-source-id: 119f3cf0082e285a84ecdca224535f03f2afbf12
2026-01-06 10:30:34 -08:00
Peter Dillinger 4e10e0bcac Fix cases of db dir going missing in db_crashtest.py (#14219)
Summary:
My PR https://github.com/facebook/rocksdb/issues/14195 regressed a case in which db_crashtest.py calling db_stress with --destroy_db_initially=1 could lead to dbname directory being nonexistant for subsequent calls to gen_cmd -> finalize_and_sanitize -> is_direct_io_supported which would fail in creating a temporary file. Fix this (and clean up existing related code) using os.makedirs.

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

Test Plan: I don't have a good reproducer for the error but some manual testing indicates this change is at least safe

Reviewed By: virajthakur

Differential Revision: D90138248

Pulled By: pdillinger

fbshipit-source-id: 0ed6524cd50f8632346a8583f26bf1f4941817ce
2026-01-06 10:09:07 -08:00
Peter Dillinger 387cb4aae7 Clarify/rename atomic wrapper stuff + blog post (#14213)
Summary:
* Some existing commentary and motivation around my atomic wrappers in atomic.h was based on a misreading of documentation. seq_cst *is* a safe substitute for acq_rel in all cases. I still like having a distinct type for RelaxedAtomic (as folly does) and a wrapper also for other cases to avoid readability traps like implicit conversion and implicit memory order. This PR is only comment changes and renaming.
* Create a blog post about bit fields API to help with lock-free (and low-lock) programming.

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

Test Plan: esiting tests

Reviewed By: xingbowang

Differential Revision: D89971581

Pulled By: pdillinger

fbshipit-source-id: 9bd1181d692258d668189c2da8bd0e5d98fd6230
2026-01-05 20:47:46 -08:00
Pierre Moulon 8b6f98cdb4 Fixing typos in comments and documentation (#14205)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14205

Fixed various spelling errors throughout RocksDB codebase including:
- assiciated → associated
- disucssion → discussion
- satisifed → satisfied
- supoort → support
- capacit_limit → capacity_limit
- direclty → directly
- diable → disable
- opeartions → operations
- paylaod → payload
- happenning/happended → happening/happened
- intialized/initiallized → initialized
- asynchronosuly → asynchronously
- exisiting → existing
- persitence → persistence
- and several others

These changes are in comments, test code, and documentation only.

Reviewed By: pdillinger

Differential Revision: D89800154

fbshipit-source-id: 1681ec95a687b038c2bad48856f1abb4dbeb42cf
2026-01-02 17:25:57 -08:00
Peter Dillinger 1cc1df8dab Finish migrating HCC to BitFields API (#14154)
Summary:
This change builds on https://github.com/facebook/rocksdb/issues/14027 and https://github.com/facebook/rocksdb/issues/13965 to complete migration
of the HyperClockCache implementation to using the hygienic BitFields API.
No semantic change in the implementation details is intended, just
greatly improving readability and safety of the code while maintaining
the same performance.

In more detail,
* Refactor the main metadata atomic for each slot in an HCC table into
SlotMeta using BitFields.
* Extended BitFields APIs with some additional features, and renamed
  BlahTransform classes to BlahTransformer to resolve potential naming
  conflicts with member functions to create them.

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

Test Plan:
for correctness, mostly existing tests. but also added tests
for new BitFields features. I especially ran local TSAN whitebox crash
test extensively which caught a couple of refactoring errors.

For performance, I verified with release builds of cache_bench, using
default options, that there was no noticeable/consistent difference
after all these HCC migrations vs. backing them out. That test was with
GCC 11 and -O2, which is a reasonable baseline for expected compiler
optimizations.

Reviewed By: xingbowang

Differential Revision: D87960540

Pulled By: pdillinger

fbshipit-source-id: e0257b7fea8a5c7709daef18911959201ce4e0f3
2025-12-29 17:13:50 -08:00
Xingbo Wang 3818cc1aca Fix a bug in seqno zeroing logic with UDT (#14207)
Summary:
This bug caused seqno to be incorrectly zeroed when UDT is enabled. This is one of the contributing factor that caused tombstones to be accumulated at bottommost level, causing high space amp.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D89826564

Pulled By: xingbowang

fbshipit-source-id: 62ab1e37c36ae1ed95f26213c97a591a17e962a6
2025-12-29 10:53:58 -08:00
zaidoon 276721cd10 eliminate per-iterator heap allocation by constructing InternalKeyComparator in-place (#14044)
Summary:
resolve [13951](https://github.com/facebook/rocksdb/issues/13951)

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

Reviewed By: xingbowang

Differential Revision: D86217603

Pulled By: jaykorean

fbshipit-source-id: 8ed62503cfcfdfb26f7af7b0a5641cd47dd9e54c
2025-12-29 10:32:36 -08:00
nsaji-stripe 45690d0f6a CompactionServiceOptionsOverride setters C API (#14183)
Summary:
## Context
1. OpenAndCompact required CompactionServiceOptionsOverride
2. Currently there are no C APIs to create CompactionServiceOptionsOverride

## Changes
1. Create C API for compactionServiceOptionsOverride
2. Create helper function to create compactionServiceOptionsOverride from Options.  This was added in because The C API lacks getter methods for non-serializable options (comparator, table_factory, etc.). Without this, users would need to maintain separate references to all these options just to pass them to the override. If the user need to create a new comparator or table factory then C API for compactionServiceOptionsOverride already as the setters for the same.

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

Reviewed By: hx235

Differential Revision: D89690005

Pulled By: jaykorean

fbshipit-source-id: efe8211feec9d144b32be0f5e66c8cf8bde8dac0
2025-12-29 10:15:38 -08:00
Maciej Szeszko e77ba4bc95 Start 10.11.0 development (#14192)
Summary:
10.10.0 branch has been cut.

### Updated:

HISTORY.md
include/rocksdb/version.h
tools/check_format_compatible.sh

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

Reviewed By: virajthakur

Differential Revision: D89736225

Pulled By: pdillinger

fbshipit-source-id: d7fc592b33a5c60dc2b53aa72ceafaa507730f20
2025-12-23 12:18:44 -08:00
Peter Dillinger 7e9f54d56b Remove remaining pieces of Lua integration (#14200)
Summary:
Going from deprecated and partly removed to fully removed

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

Test Plan: existing tests

Reviewed By: archang19

Differential Revision: D89697543

Pulled By: pdillinger

fbshipit-source-id: bbf6161c04322e9756a9479758488cac6d03473a
2025-12-23 09:10:12 -08:00
Peter Dillinger 41beb1422f Improve db_crashtest.py for remote DB (#14195)
Summary:
Let db_crashtest.py work with TEST_TMPDIR on remote filesystem, by infering whether it's remote from the env_uri argument. Note that some other paths passed to db_stress are local paths and we can't reuse TEST_TMPDIR for those cases when it's remote.

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

Test Plan: public and private CI

Reviewed By: archang19

Differential Revision: D89590246

Pulled By: pdillinger

fbshipit-source-id: db6eb9c16d4e76617183780747353c798cc9bef6
2025-12-22 13:03:00 -08:00
Peter Dillinger 9065ace05a Disable multiscan+timestamp in crash test (#14189)
Summary:
Causing failures and not yet supported. Also putting a note in db.h about the combination being unsupported.

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

Test Plan: started up blackbox_crash_test_with_ts many times and checked command line to be confident it's excluded.

Reviewed By: hx235

Differential Revision: D89297971

Pulled By: pdillinger

fbshipit-source-id: c5134351d9ecb37879c7e3319c17dd9228d7f12a
2025-12-16 12:36:07 -08:00
Maciej Szeszko 5a06787a26 IO uring improvements (#14158)
Summary:
`PosixRandomAccessFile::MultiRead` was introduced in Dec 2019 in https://github.com/facebook/rocksdb/pull/5881. Subsequently, 2 years after, we introduced the `PosixRandomAccessFile::ReadAsync` API in https://github.com/facebook/rocksdb/pull/9578, which was reusing the same `PosixFileSystem` IO ring as `MultiRead` API, consequently writing to the very same ring's submission queue (without waiting!). This 'shared ring' design is problematic, since sequentially interleaving `ReadAsync` and `MultiRead` API calls on the very same thread might result in reading 'unknown' events in `MultiRead` leading to `Bad cqe data` errors (and therefore falsely perceived  as a corruption) - which, for some services (running on local flash), in itself is a hard blocker for adopting RocksDB async prefetching ('async IO') that heavily relies on the `ReadAsync` API. This change aims to solve this problem by maintaining separate thread local IO rings for `async reads` and `multi reads` assuring correct execution. In addition, we're adding more robust error handling in form of retries for kernel interrupts and draining the queue when process is experiencing terse memory condition. Separately, we're enhancing the performance aspect by explicitly marking the rings to be written to / read from by a single thread (`IORING_SETUP_SINGLE_ISSUER` [if available]) and defer the task just before the application intends to process completions (`IORING_SETUP_DEFER_TASKRUN` [if available]). See https://man7.org/linux/man-pages/man2/io_uring_setup.2.html for reference.

## Benchmark

**TLDR**
There's no evident advantage of using `io_uring_submit` (relative to proposed `io_uring_submit_and_wait`) across batches of size 10, 250 and 1000 simulating significantly-less, close-to and 4x-above `kIoUringDepth` batch size. `io_uring_submit` might be more appealing if (at least) one of the IOs is slow (which was NOT the case during the benchmark). More notably, with this PR switching from `io_uring_submit_and_wait` -> `io_uring_submit` can be done with a single line change due to implemented guardrails (we can followup with adding optional config for true ring semantics [if needed]).

**Compilation**
```
DEBUG_LEVEL=0 make db_bench
```

**Create DB**

```
./db_bench \
    --db=/db/testdb_2.5m_k100_v6144_16kB_LZ4 \
    --benchmarks=fillseq \
    --num=2500000 \
    --key_size=100 \
    --value_size=6144 \
    --compression_type=LZ4 \
    --block_size=16384 \
    --seed=1723056275
```

**LSM**

* L0: 2 files, L1: 5, L2: 49, L3: 79
* Each file is roughly ~35M in size

### MultiReadRandom (with caching disabled)

Each run was preceded by OS page cache cleanup with `echo 1 | sudo tee /proc/sys/vm/drop_caches`.

```
./db_bench \
    --use_existing_db=true \
    --db=/db/testdb_2.5m_k100_v6144_16kB_LZ4 \
    --compression_type=LZ4 \
    --benchmarks=multireadrandom \
    --num= **<N>** \
    --batch_size= **<B>** \
    --io_uring_enabled=true \
    --async_io=false \
    --optimize_multiget_for_io=false \
    --threads=4 \
    --cache_size=0 \
    --use_direct_reads=true \
    --use_direct_io_for_flush_and_compaction=true \
    --cache_index_and_filter_blocks=false \
    --pin_l0_filter_and_index_blocks_in_cache=false \
    --pin_top_level_index_and_filter=false \
    --prepopulate_block_cache=0 \
    --row_cache_size=0 \
    --use_blob_cache=false \
    --use_compressed_secondary_cache=false
```

  | B=10; N=100,000 | B = 250; N=80,000  | B = 1,000; N=20,000
-- | -- | -- | --
baseline | 31.5 (± 0.4) us/op | 17.5 (± 0.5) us/op | 13.5 (± 0.4) us/op
io_uring_submit_and_wait |  31.5 (± 0.6) us/op |  17.7 (± 0.4) us/op |  13.6 (± 0.4) us/op
io_uring_submit | 31.5 (± 0.6) us/op | 17.5 (± 0.5) us/op | 13.4 (± 0.45) us/op

### Specs

  | Property | Value
-- | --
RocksDB | version 10.9.0
Date | Tue Dec 9 15:57:03 2025
CPU | 56 * Intel Sapphire Rapids (T10 SPR)
Kernel version | 6.9.0-0_fbk12_0_g28f2d09ad102

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

Reviewed By: anand1976

Differential Revision: D88172809

Pulled By: mszeszko-meta

fbshipit-source-id: 5198de3d2f18f76fee661a2ec5f447e79ba06fbd
2025-12-12 14:25:40 -08:00
Hui Xiao a1d8318563 Fix resumable compaction to prevent resumption at truncated range deletion boundaries (#14184)
Summary:
**Context/Summary:**

Truncated range deletion in input files can be output by CompactionIterator with type kMaxValid instead of kTypeRangeDeletion, to satisfy ordering requirement between the truncated range deletion start key and a file's point keys. There was a plan to skip such key in https://github.com/facebook/rocksdb/pull/14122 but blockers remain to fulfill the plan.

Resumable compaction is not able to handle resumption from range deletion well at this point and should consider kMaxValid type same as kTypeRangeDeletion for resumption. Previously, it didn't and mistakenly allow resumption from a delete range. That led to an assertion failure, complaining about lacking information to update file boundaries in the presence of range deletion needed during cutting an output file, after the compaction resumes from that delete range and happens to cut the output file shortly after without any point keys in between.

```
frame https://github.com/facebook/rocksdb/issues/9: 0x00007f4f4743bc93 libc.so.6`__GI___assert_fail(assertion="meta.smallest.size() > 0", file="db/compaction/compaction_outputs.cc", line=530, function="rocksdb::Status rocksdb::CompactionOutputs::AddRangeDels(rocksdb::CompactionRangeDelAggregator&, const rocksdb::Slice*, const rocksdb::Slice*, rocksdb::CompactionIterationStats&, bool, const rocksdb::InternalKeyComparator&, rocksdb::SequenceNumber, std::pair<long unsigned int, long unsigned int>, const rocksdb::Slice&, const string&)") at assert.c:101:3
frame https://github.com/facebook/rocksdb/issues/10: 0x00007f4f4808c68c librocksdb.so.10.9`rocksdb::CompactionOutputs::AddRangeDels(this=0x00007f4f0c27e1a0, range_del_agg=0x00007f4f0c21ecc0, comp_start_user_key=0x0000000000000000, comp_end_user_key=0x0000000000000000, range_del_out_stats=0x00007f4f0dffa140, bottommost_level=false, icmp=0x00007f4ef4c93040, earliest_snapshot=13108729, keep_seqno_range=<unavailable>, next_table_min_key=0x00007f4ef4c8f540, full_history_ts_low="") at compaction_outputs.cc:530:7
frame https://github.com/facebook/rocksdb/issues/11: 0x00007f4f480480dd librocksdb.so.10.9`rocksdb::CompactionJob::FinishCompactionOutputFile(this=0x00007f4f0dffb890, input_status=<unavailable>, prev_table_last_internal_key=0x00007f4f0dffa650, next_table_min_key=0x00007f4ef4c8f540, comp_start_user_key=0x0000000000000000, comp_end_user_key=0x0000000000000000, c_iter=0x00007f4ef4c8f400, sub_compact=0x00007f4f0c27e000, outputs=0x00007f4f0c27e1a0) at compaction_job.cc:1917:31
```

This PR simply prevents  MaxValid from being a resumption point like regular range deletion - see commit 842d66eb18ea67e965d6acb1fce12c18eeb778d2

Besides that, the PR also improves the testing, variable naming, logging in resumable compaction codes that were needed to debug this assertion failure - see commit https://github.com/facebook/rocksdb/pull/14184/commits/aecd4e7f971f6dd4df672d9e5f1409fe4747c561. These improvements are covered by existing tests.

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

Test Plan:
- The stress initially surfaced the error. Using the exact same LSM shapes and files that were used in stress test but in a unit test, I'm able to get a deterministic repro and confirmed the fix resolves the error.  This is the repro test https://github.com/hx235/rocksdb/commit/1075936e693c68c960761855900c53f5b894f57a
```
./compaction_service_test --gtest_filter=ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
# Before fix
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ResumableCompactionServiceTest
[ RUN      ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
compaction_service_test: db/compaction/compaction_outputs.cc:530: rocksdb::Status rocksdb::CompactionOutputs::AddRangeDels(rocksdb::CompactionRangeDelAggregator&, const rocksdb::Slice*, const rocksdb::Slice*, rocksdb::CompactionIterationStats&, bool, const rocksdb::InternalKeyComparator&, rocksdb::SequenceNumber, std::pair<long unsigned int, long unsigned int>, const rocksdb::Slice&, const string&): Assertion `meta.smallest.size() > 0' failed.
Received signal 6 (Aborted)
Invoking GDB for stack trace...
[New LWP 2621610]
[New LWP 2621611]
[New LWP 2621612]
[New LWP 2621613]
[New LWP 2621614]
[New LWP 2621630]
[New LWP 2621631]

# After fix
Note: Google Test filter = ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ResumableCompactionServiceTest
[ RUN      ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume
[       OK ] ResumableCompactionServiceTest.CompactSpecificFilesFromExistingDBWithCancelAndResume (4722 ms)
[----------] 1 test from ResumableCompactionServiceTest (4722 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4722 ms total)
[  PASSED  ] 1 test.

```
- Follow-up: I tried a couple time to coerce the truncated range delete from scratch in the unit test but failed doing so. Considering kMaxValid may not be outputted by compaction iterator anymore after https://github.com/facebook/rocksdb/pull/14122/files gets landed again (and obsolete the bug) ADN the simple nature of this fix 842d66eb18ea67e965d6acb1fce12c18eeb778d2 AND the worst case of such fix going wrong is just less resumption, I decided to leave writing a unit test to coerce truncated ranged deletion from scratch a follow-up. Maybe I will draw inspiration from https://github.com/facebook/rocksdb/pull/14122/files.

Reviewed By: jaykorean

Differential Revision: D88912663

Pulled By: hx235

fbshipit-source-id: 80a01135684c8fea659650faaa00c2dc452c482a
2025-12-11 16:50:42 -08:00
Hui Xiao eedf1fe068 Display copy-paste friendly flag value in db_crashtest.py (#14180)
Summary:
**Context/Summary:**
Stress test flag printed by db_crashtest.py like `./db_stres ....-secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true --otherflags=xxxx` is not copy-paste-run friendly. Directly running this command will cause parsing hiccups due to special characters like // or ;. This PR made the db_crashtest.py print a single-quoted value so at least the copy-paste-run works for unix-like shell (the most common case).

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

Test Plan:
`python3 tools/db_crashtest.py --simple blackbox ...` display the following

Before fix, no single-quoted
```
Use random seed for iteration 9698536012932546857
Running db_stress with pid=1280640:./db_stress --secondary_cache_uri=compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true  ...

// Directly copy, paste and run the ./db_stress command will encounter
Error: Read(-readpercent=0)+Prefix(-prefixpercent=0)+Write(-writepercent=45)+Delete(-delpercent=0)+DeleteRange(-delrangepercent=30)+Iterate(-iterpercent=40)+CustomOps(-customopspercent=0) percents != 100!
bash: --set_options_one_in=0: command not found
```
After fix, has single-quoted
```
se random seed for iteration 6017815530972723112
Running db_stress with pid=1234632: ./db_stress --secondary_cache_uri='compressed_secondary_cache://capacity=8388608;enable_custom_split_merge=true' ....

// Directly copy, paste and run the ./db_stress command is fine
```

Reviewed By: archang19

Differential Revision: D88688584

Pulled By: hx235

fbshipit-source-id: 88b8b2de7c2c5619b6e19900f4144dcd8e032f7b
2025-12-09 10:35:41 -08:00
nsaji-stripe 80c4a67d6a Remote Compaction C API (#14136)
Summary:
r? cbi42

Exposes RocksDB's remote compaction functionality through the C API, enabling C/FFI clients (Go, Rust, Python, etc.) to offload compaction work to remote workers.

## API Components
### Compaction Service

Create service with schedule, wait, cancel, and on_installation callbacks
Ownership transfers to options object (auto-destroyed, no manual cleanup)

### Job Info (13 getters)

DB/CF metadata and compaction details (priority, reason, levels, flags)

### Schedule Response

Create with job ID and status (validated with errptr)
Status: success, failure, aborted, use_local

### OpenAndCompact (for remote workers)

Execute compaction on worker node with environment/comparator overrides
Cancellation support via atomic flags

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

Reviewed By: hx235

Differential Revision: D88316558

Pulled By: jaykorean

fbshipit-source-id: 60a0fee69ff1e650dd785d96ec656649263214f8
2025-12-08 10:08:19 -08:00
anand76 5d0cf98e6c Surface MultiScan async read failure instead of asserting (#14171)
Summary:
Crash tests have been failing of late with this assertion failure - db_stress: `./table/block_based/block_based_table_iterator.h:656: void rocksdb::BlockBasedTableIterator::PrepareReadAsyncCallBack(rocksdb::FSReadRequest &, void *): Assertion `async_state->status.IsAborted()' failed.` Instead of asserting, surface the failure status so we can troubleshoot.

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

Reviewed By: xingbowang

Differential Revision: D88396654

Pulled By: anand1976

fbshipit-source-id: 8d59d7ace0c522c17b7af17c50e16af876911bad
2025-12-05 10:45:26 -08:00
Peter Dillinger e3b5464785 GitHub Actions nightly crash test runs on ARM (#14172)
Summary:
To help find potential issues not showing up in ARM unit tests. I'm running it with and without TransactionDB (write-committed) for better coverage. The job expands the size of /dev/shm for adequate space on maximum performance storage, and adds swap space to reduce risk of OOM in case we fill that up.

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

Test Plan: earlier drafts of this PR added the job to PR jobs, and the last before putting in "nightly" can be seen here: https://github.com/facebook/rocksdb/actions/runs/19945493840/job/57193797390?pr=14172

Reviewed By: archang19

Differential Revision: D88429479

Pulled By: pdillinger

fbshipit-source-id: bd4d9cda9256950c3c6c126c299a44dbbbc30c7e
2025-12-04 20:39:10 -08:00
Xingbo Wang 7c48905ecd Fix missing const for arg of OptionChangeMigration (#14173)
Summary:
Fix missing const for arg of OptionChangeMigration

We switched from std::string to std::string & for API OptionChangeMigration, which caused const qualifier to be lost at call site, which causes compilation failure.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D88431457

Pulled By: xingbowang

fbshipit-source-id: a705f3b80cc5ff56dab73aa6a31c940798d8df45
2025-12-04 17:04:43 -08:00
Xingbo Wang 707e405492 Revert #14122 "Fix a bug where compaction ..." (#14170)
Summary:
Revert "Fix a bug where compaction with range deletion can persist kTypeMaxValid in file metadata (https://github.com/facebook/rocksdb/issues/14122)"

Add a new unit test to capture the situation found by stress test

This reverts commit 8c7c8b8dab.

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

Test Plan: Unit Test

Reviewed By: anand1976

Differential Revision: D88395956

Pulled By: xingbowang

fbshipit-source-id: 226649dc79a86010ad326ffb2eae35109dc96bc4
2025-12-04 12:28:01 -08:00
Xingbo Wang 340ac7ea6b Improve sst_dump raw mode dump result (#14166)
Summary:
Add a new option in sst_dump command to show seq no and value type in raw mode

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

Test Plan:
Sample output

```
sst_dump --file=rocksdb_crashtest_blackbox/000010.sst  --command=raw --show_sequence_number_type

...

Range deletions:
--------------------------------------
  HEX    000000000000038D000000000000012B000000000000029A  seq: 3016892  type: 15 : 000000000000038D000000000000012B000000000000029E
  ASCII  \0 \0 \0 \0 \0 \0   \0 \0 \0 \0 \0 \0  + \0 \0 \0 \0 \0 \0   : \0 \0 \0 \0 \0 \0   \0 \0 \0 \0 \0 \0  + \0 \0 \0 \0 \0 \0
  ------

Data Block # 1 @ 0073
--------------------------------------
  HEX    000000000000038D000000000000012B000000000000029D  seq: 3004554  type: 0 :
  ASCII  \0 \0 \0 \0 \0 \0   \0 \0 \0 \0 \0 \0  + \0 \0 \0 \0 \0 \0   :
  ------
  HEX    000000000000038D000000000000012B000000000000029D  seq: 0  type: 1 : 03000000070605040B0A09080F0E0D0C13121110171615141B1A19181F1E1D1C
  ASCII  \0 \0 \0 \0 \0 \0   \0 \0 \0 \0 \0 \0  + \0 \0 \0 \0 \0 \0   :  \0 \0 \0
```

Reviewed By: hx235

Differential Revision: D88396223

Pulled By: xingbowang

fbshipit-source-id: b006cd7f51f941951349e4ec60ed5ef1e838919d
2025-12-04 11:52:58 -08:00
Hui Xiao d2fe0ee389 Fix use-after-free in BlockBasedTable after best-efforts recovery retry (#14155)
Summary:
**Context/Summary:**

Best-efforts recovery can cause a use-after-free bug after retrying for a failed recovery attempt. The issue occurs in VersionSet::Reset():
- First recovery attempt: Opens SST files, caching BlockBasedTable objects in table_cache_
https://github.com/facebook/rocksdb/blob/ac412b10955d5a1d3d99aff8edf94eae1e4a22d5/db/version_edit_handler.cc#L565
- Recovery fails: Calls Reset() which deletes the old ColumnFamilySet (and all CFDs)
https://github.com/facebook/rocksdb/blob/ac412b10955d5a1d3d99aff8edf94eae1e4a22d5/db/version_set.cc#L6631
- Creates new CFDs: But reuses the same table_cache_
https://github.com/facebook/rocksdb/blob/ac412b10955d5a1d3d99aff8edf94eae1e4a22d5/db/version_set.cc#L5579
- Bug: Cached BlockBasedTable objects contain now-dangling reference to previous CFD's member such as rep_->internal_comparator or rep_->ioptions as below. References instead of object copies are used for memory efficiency
```
struct BlockBasedTable::Rep {
  Rep(const ImmutableOptions& _ioptions, ..
      const InternalKeyComparator& _internal_comparato...)) {}
  ~Rep() { status.PermitUncheckedError(); }
  const ImmutableOptions& ioptions;
  ...
  const InternalKeyComparator& internal_comparator;
```
- Crash: Accessing any of the above reference in cached tables during read or compaction after recovery finishes triggers use-after-free

This PR calls table_cache_->EraseUnRefEntries()  to clear tables containing the dangling reference in VersionSet::Reset() before creating the new ColumnFamilySet.

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

Test Plan:
- Add new unit test that fails before the fix under ASAN run and pass after
```
[ RUN      ] DBBasicTest.BestEffortRecoveryFailureWithTableCacheUseAfterFree
=================================================================
==1976446==ERROR: AddressSanitizer: heap-use-after-free on address 0x61e00000a8c8 at pc 0x7f6b21beae57 bp 0x7ffd65bacec0 sp 0x7ffd65baceb8
READ of size 8 at 0x61e00000a8c8 thread T0
    #0 0x7f6b21beae56 in rocksdb::UserComparatorWrapper::user_comparator() const util/user_comparator_wrapper.h:29 // rep_->ioptions
    https://github.com/facebook/rocksdb/issues/1 0x7f6b21beb02b in rocksdb::InternalKeyComparator::user_comparator() const db/dbformat.h:421
    https://github.com/facebook/rocksdb/issues/2 0x7f6b229a7a50 in rocksdb::BinarySearchIndexReader::NewIterator(rocksdb::ReadOptions const&, bool, rocksdb::IndexBlockIter*, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*) table/block_based/binary_search_index_reader.cc:62
    https://github.com/facebook/rocksdb/issues/3 0x7f6b22a9a649 in rocksdb::BlockBasedTable::NewIndexIterator(rocksdb::ReadOptions const&, bool, rocksdb::IndexBlockIter*, rocksdb::GetContext*, rocksdb::BlockCacheLookupContext*) const table/block_based/block_based_table_reader.cc:1683
    https://github.com/facebook/rocksdb/issues/4 0x7f6b22aa39be in rocksdb::BlockBasedTable::Get(rocksdb::ReadOptions const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::SliceTransform const*, bool) table/block_based/block_based_table_reader.cc:2533
    https://github.com/facebook/rocksdb/issues/5 0x7f6b2241201c in rocksdb::TableCache::Get(rocksdb::ReadOptions const&, rocksdb::InternalKeyComparator const&, rocksdb::FileMetaData const&, rocksdb::Slice const&, rocksdb::GetContext*, rocksdb::MutableCFOptions const&, rocksdb::HistogramImpl*, bool, int, unsigned long) db/table_cache.cc:492

0x61e00000a8c8 is located 72 bytes inside of 2784-byte region [0x61e00000a880,0x61e00000b360)
freed by thread T0 here:
    #0 0x7f6b248d20d7 in operator delete(void*, unsigned long) /home/engshare/third-party2/gcc/11.x/src/gcc-11.x/libsanitizer/asan/asan_new_delete.cpp:172
    https://github.com/facebook/rocksdb/issues/1 0x7f6b21ca8703 in rocksdb::ColumnFamilyData::UnrefAndTryDelete() db/column_family.cc:785
    https://github.com/facebook/rocksdb/issues/2 0x7f6b21cb25ee in rocksdb::ColumnFamilySet::~ColumnFamilySet() db/column_family.cc:1771
    https://github.com/facebook/rocksdb/issues/3 0x7f6b225683df in std::default_delete<rocksdb::ColumnFamilySet>::operator()(rocksdb::ColumnFamilySet*) const (/data/users/huixiao/rocksdb/librocksdb.so.10.10+0x1f683df)
    https://github.com/facebook/rocksdb/issues/4 0x7f6b22568ceb in std::__uniq_ptr_impl<rocksdb::ColumnFamilySet, std::default_delete<rocksdb::ColumnFamilySet> >::reset(rocksdb::ColumnFamilySet*) /mnt/gvfs/third-party2/libgcc/d1129753c8361ac8e9453c0f4291337a4507ebe6/11.x/platform010/5684a5a/include/c++/trunk/bits/unique_ptr.h:182
    https://github.com/facebook/rocksdb/issues/5 0x7f6b22550c52 in std::unique_ptr<rocksdb::ColumnFamilySet, std::default_delete<rocksdb::ColumnFamilySet> >::reset(rocksdb::ColumnFamilySet*) /mnt/gvfs/third-party2/libgcc/d1129753c8361ac8e9453c0f4291337a4507ebe6/11.x/platform010/5684a5a/include/c++/trunk/bits/unique_ptr.h:456
    https://github.com/facebook/rocksdb/issues/6 0x7f6b224fa09e in rocksdb::VersionSet::Reset() db/version_set.cc:5587
    https://github.com/facebook/rocksdb/issues/7 0x7f6b2250752c in rocksdb::VersionSet::TryRecover(std::vector<rocksdb::ColumnFamilyDescriptor, std::allocator<rocksdb::ColumnFamilyDescriptor> > const&, bool, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, bool*) db/version_set.cc:6640
    https://github.com/facebook/rocksdb/issues/8 0x7f6b220c5a88 in rocksdb::DBImpl::Recover(std::vector<rocksdb::ColumnFamilyDescriptor, std::allocator<rocksdb::ColumnFamilyDescriptor> > const&, bool, bool, bool, bool, unsigned long*, rocksdb::DBImpl::RecoveryContext*, bool*) db/db_impl/db_impl_open.cc:565

previously allocated by thread T0 here:
    #0 0x7f6b248d1257 in operator new(unsigned long) /home/engshare/third-party2/gcc/11.x/src/gcc-11.x/libsanitizer/asan/asan_new_delete.cpp:99
    https://github.com/facebook/rocksdb/issues/1 0x7f6b21cb30e0 in rocksdb::ColumnFamilySet::CreateColumnFamily(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, unsigned int, rocksdb::Version*, rocksdb::ColumnFamilyOptions const&, bool) db/column_family.cc:1827
    https://github.com/facebook/rocksdb/issues/2 0x7f6b22516a11 in rocksdb::VersionSet::CreateColumnFamily(rocksdb::ColumnFamilyOptions const&, rocksdb::ReadOptions const&, rocksdb::VersionEdit const*, bool) db/version_set.cc:7715
    https://github.com/facebook/rocksdb/issues/3 0x7f6b22494910 in rocksdb::VersionEditHandler::CreateCfAndInit(rocksdb::ColumnFamilyOptions const&, rocksdb::VersionEdit const&) db/version_edit_handler.cc:494
    https://github.com/facebook/rocksdb/issues/4 0x7f6b2249005f in rocksdb::VersionEditHandler::Initialize() db/version_edit_handler.cc:209
    https://github.com/facebook/rocksdb/issues/5 0x7f6b2248cd13 in rocksdb::VersionEditHandlerBase::Iterate(rocksdb::log::Reader&, rocksdb::Status*) db/version_edit_handler.cc:32
    https://github.com/facebook/rocksdb/issues/6 0x7f6b225081db in rocksdb::VersionSet::TryRecoverFromOneManifest(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::vector<rocksdb::ColumnFamilyDescriptor, std::allocator<rocksdb::ColumnFamilyDescriptor> > const&, bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, bool*) db/version_set.cc:6679
    https://github.com/facebook/rocksdb/issues/7 0x7f6b225074a1 in rocksdb::VersionSet::TryRecover(std::vector<rocksdb::ColumnFamilyDescriptor, std::allocator<rocksdb::ColumnFamilyDescriptor> > const&, bool, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, bool*) db/version_set.cc:6635
    https://github.com/facebook/rocksdb/issues/8 0x7f6b220c5a88 in rocksdb::DBImpl::Recover(std::vector<rocksdb::ColumnFamilyDescriptor, std::allocator<rocksdb::ColumnFamilyDescriptor> > const&, bool, bool, bool, bool, unsigned long*, rocksdb::DBImpl::RecoveryContext*, bool*) db/db_impl/db_impl_open.cc:565
```

Reviewed By: anand1976

Differential Revision: D87991593

Pulled By: hx235

fbshipit-source-id: 2379b297ff592cadf02659e355cdc8e170917cfc
2025-12-02 19:26:42 -08:00
Peter Dillinger 4951494a27 Continue migration of HCC impl to BitFields (#14027)
Summary:
Continuing work from https://github.com/facebook/rocksdb/issues/13965. Here I'm migrating the "next with shift" kind of bit field and for that I've added an API for atomic additive transformations that can be combined into a single atomic update for multiple fields. (I implemented more features than needed, just in case they are needed someday and to demonstrate what is possible.)

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

Test Plan: BitFields unit test updated/added, existing HCC tests

Reviewed By: xingbowang

Differential Revision: D83895094

Pulled By: pdillinger

fbshipit-source-id: e4487f34f5607b20f94b85a645ca654e6401e35d
2025-12-01 13:21:34 -08:00
Andrew Chang ac412b1095 Add checks to terminate early when backup is stopped (#14129)
Summary:
I want to reduce the time from when we call `StopBackup` to `CreateNewBackup` returning `BackupStopped`. We already check for the `stop_backup_` inside `CopyOrCreateFile` and `ReadFileAndComputeChecksum`, but we should add a check at the top of these methods to abort immediately. This could help save some latency from the file system metadata operations, like creating the sequential file and writable file.

We also want to update the API documentation for `StopBackup` which currently does not indicate that once it is called, all subsequent requests to create backups will fail.

In a follow up PR, we should also add coverage of `StopBackup` to the crash tests.

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

Test Plan:
We were missing unit test coverage for `StopBackup`. I added test cases which cancel backups at different points in time.

Once this change is rolled out to production, we can monitor the DB close latencies, which depend on first cancelling ongoing backups

Reviewed By: pdillinger

Differential Revision: D87356536

Pulled By: archang19

fbshipit-source-id: 687094a41f096f6a156be65b2cce0b5054fb26f2
2025-11-25 09:01:20 -08:00
Xingbo Wang 9e14d06143 Support ccache in make file (#14123)
Summary:
Support ccache in make file

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

Test Plan: local build

Reviewed By: cbi42

Differential Revision: D87332892

Pulled By: xingbowang

fbshipit-source-id: 2088bd19bdab1bd7070734c886200be80f1a65af
2025-11-24 10:48:09 -08:00
Peter Dillinger 9c2c8f54fa Fix AutoSkipCompressorWrapper with new logic (#14150)
Summary:
... from https://github.com/facebook/rocksdb/issues/14140. The assertion in the default implementation of CompressorWrapper::MaybeCloneSpecialized() could fail because this wrapper wasn't overriding it when it should. (See the NOTE on that implementation.)

Because this release already has a breaking modification to the Compressor API (adding Clone()), I took this opportunity to add 'const' to MaybeCloneSpecialized(). Also marked some compression classes as 'final' that could be marked as such.

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

Test Plan: unit test expanded to cover this case (verified failing before). Audited the rest of our CompressorWrappers.

Reviewed By: archang19

Differential Revision: D87793987

Pulled By: pdillinger

fbshipit-source-id: 61c4469b84e4a47451a9942df09277faeeccfe63
2025-11-24 10:36:12 -08:00
Xingbo Wang 42ba71fbbf Start 10.10.0 development (#14148)
Summary:
10.9.0 branch has been cut.

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

Reviewed By: nmk70

Differential Revision: D87688882

Pulled By: xingbowang

fbshipit-source-id: 5fe95d3c64851b4f9490aed5d92451b38abe008d
2025-11-24 08:45:40 -08:00
Peter Dillinger 35148aca91 Improve distinct compression for index and data blocks (#14140)
Summary:
This change enables a custom CompressionManager / Compressor to adopt custom handling for data and index blocks. In particular, index blocks for format_version >= 4 use a distinct variant of the block format. Thus, a potentially format-aware compression algorithm such as OpenZL should be told which kind of block we are compressing. (And previously I avoided passing block type in CompressBlock for efficient handling of things like dictionaries but also avoiding checks on every CompressBlock call.)

Most of the change is in BlockBasedTableBuilder to call MaybeCloneSpecialized for both kDataBlock and for kIndexBlock. But I also needed some small tweaks/additions to the public API also:
* Require a Clone() function from Compressors, to support proper implementations of MaybeCloneSpecialized() in wrapper Compressors.
* Assert that the default implementation of CompressorWrapper::MaybeCloneSpecialized() is only used in allowable cases.
* Convenience function Compressor::CloneMaybeSpecialized()

This also fixes a serious bug/oversight in ManagedPtr for (ManagedWorkingArea) that somehow wasn't showing up before. It probably doesn't need a release note because CompressionManager stuff is still considered experimental.

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

Test Plan: Greatly expanded DBCompressionTest.CompressionManagerWrapper to make sure the distinction between data blocks and index blocks is properly communicated to a custom CompressionManager/Compressor. The test includes processing the expected structure of data and index blocks, to serve as a tested example for structure-aware compressors.

Reviewed By: hx235

Differential Revision: D87600019

Pulled By: pdillinger

fbshipit-source-id: 252ef78910073a0e45f2c81dd45ac87ff8a41fc6
2025-11-21 16:34:49 -08:00
Changyu Bi 8c7c8b8dab Fix a bug where compaction with range deletion can persist kTypeMaxValid in file metadata (#14122)
Summary:
Range deletion start keys are considered during compaction for cutting output files. Due to some ordering requirement (see comment above InsertNextValidRangeTombstoneAtLevel()) between truncated range deletion start key and a file's point keys, there was logic in https://github.com/facebook/rocksdb/blob/f6c9c3bf1cf05096e8ff8c03ded60c1e199edbb7/db/range_del_aggregator.cc#L39 that changes the value type to be kTypeMaxValid. However, kTypeMaxValid is not supposed to be persisted per https://github.com/facebook/rocksdb/blob/f6c9c3bf1cf05096e8ff8c03ded60c1e199edbb7/db/dbformat.h#L75-L76. This can cause forward compatibility issues reported in https://github.com/facebook/rocksdb/issues/14101. This PR fixes this issue by removing the logic that sets kTypeMaxValid and always skip truncated range deletion start key in CompactionMergingIterator.

For existing SST files, we want to avoid using this kTypeMaxValid, so this PR also introduces a new placeholder value type. This allows us to re-strengthen the relevant value type checks (IsExtendedValueType()) that was loosen for kTypeMaxValid.

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

Test Plan:
- a unit test that persists kTypeMaxValid before this fix
- crash test with frequent range deletion: `python3 ./tools/db_crashtest.py blackbox --delrangepercent=11 --readpercent=35`
- Generate SST files with 0x1A as value type (kTypeMaxValid before this change) in file metadata. Run ldb with the strengthened check in IsExtendedValueType() to dump the MANIFEST. It failed to parse MANIFEST as expected before this PR and succeeds after this PR.
```
Error in processing file /tmp/rocksdbtest-543376/db_range_del_test_2549357_6547198162080866792/MANIFEST-000005 Corruption: VersionEdit: new-file4 entry  The file /tmp/rocksdbtest-543376/db_range_del_test_2549357_6547198162080866792/MANIFEST-000005 may be corrupted.
```

Reviewed By: pdillinger

Differential Revision: D87016541

Pulled By: cbi42

fbshipit-source-id: 9957a095db2cd9947463b403f352bd9a1fd70a76
2025-11-21 14:18:38 -08:00
Jay Huh 2f583aed8f Move prepared_iter size assertion after cleanup (#14144)
Summary:
Fixing crash test failure caused by `prepared_iters_.size() == 0`

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

Test Plan:
```
python3 -u tools/db_crashtest.py --stress_cmd=./db_stress --cleanup_cmd='' --simple blackbox
```

Reviewed By: krhancoc

Differential Revision: D87656914

Pulled By: jaykorean

fbshipit-source-id: 9ef7cf4ea5d34fe9dee6219b32323e91a2ea3e5f
2025-11-21 13:30:31 -08:00
Jay Huh c4bbad4dfe Update format-diff script to add text to new files (#14143)
Summary:
Fixing internal validator failure

```
Every project specific source file must contain a doc block with an appropriate copyright header. Unrelated files must be listed as exceptions in the Copyright Headers Exceptions page in the repo dashboard.
A copyright header clearly indicates that the code is owned by Meta. Every open source file must start with a comment containing "Meta Platforms, Inc. and affiliates"
https://github.com/facebook/rocksdb/blob/main/buckifier/targets_cfg.py:
The first 16 lines of 'buckifier/targets_cfg.py' do not contain the patterns:
	(Meta Platforms, Inc. and affiliates)|(Facebook, Inc(\.|,)? and its affiliates)|([0-9]{4}-present(\.|,)? Facebook)|([0-9]{4}(\.|,)? Facebook)
```

While fixing the text to pass the linter, I took the opportunity to modify `format-diff.sh` script to add the copyright header automatically if missing in new files.

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

Test Plan:
```
$> make format
```
**new python file**
```
build_tools/format-diff.sh
Checking format of uncommitted changes...
Checking for copyright headers in new files...
Added copyright header to build_tools/test.py
Copyright headers were added to new files.
Nothing needs to be reformatted!
```
**new header file**
```
build_tools/format-diff.sh
Checking format of uncommitted changes...
Checking for copyright headers in new files...
Added copyright header to db/db_impl/db_impl_jewoongh.h
Copyright headers were added to new files.
Nothing needs to be reformatted!
```

Reviewed By: hx235

Differential Revision: D87653124

Pulled By: jaykorean

fbshipit-source-id: 164322cfcd2c162bb3b41bb8f3bafefa3f20b695
2025-11-21 11:32:10 -08:00
Hui Xiao dc33c1adaf Include verify_output_flags to check resumable compaction compatibility (#14139)
Summary:
**Context/Summary:**

.. because verify_output_flags contains information of usage of paranoid_file_check that is currently not yet compatible with resumable remote compaction

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

Test Plan: Existing tests

Reviewed By: jaykorean

Differential Revision: D87582635

Pulled By: hx235

fbshipit-source-id: ef21223da53a0696fa3ca9b1617c2c1ee2e19878
2025-11-21 11:32:00 -08:00
Hui Xiao c76cacc696 Fix overflow in MultiplyCheckOverflow() due to std::numeric_limits<uint64_t>::max()'s promotion to double (#14132)
Summary:
**Context/Summary:**
Due to double's 53-bit mantissa limitation, large uint64_t values lose precision when converted to double. Value equals to or smaller than UINT64_MAX (but greater than 2^64 - 1024) round up to 2^64 since rounding up results in less error than rounding down, which exceeds UINT64_MAX. `std::numeric_limits<uint64_t>::max() / op1 < op2` won't catch those cases. Casting such out-of-range doubles back to uint64_t causes undefined behavior. T

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14132
UndefinedBehaviorSanitizer: undefined-behavior options/cf_options.cc:1087:32 in
```
before the fix but not after.

Test Plan:
```
COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j55 db_stress

python3 tools/db_crashtest.py --simple blackbox --compact_range_one_in=5 --target_file_size_base=9223372036854775807 // Half of std::numeric_limits<uint64_t>::max()
```
It fails with
```
stderr:
 options/cf_options.cc:1087:32: runtime error: 1.84467e+19 is outside the range of representable values of type 'unsigned long'

Reviewed By: pdillinger

Differential Revision: D87434936

Pulled By: hx235

fbshipit-source-id: 65563edf9faf732410bdba8b9e4b7fd61b958169
2025-11-19 16:25:53 -08:00
Jay Huh 8c8586aa23 Add oncall to BUCK file (#14134)
Summary:
As title

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

Test Plan:
The following command generated the BUCK file correctly
```
python3 buckifier/buckify_rocksdb.py
```

Reviewed By: anand1976

Differential Revision: D87469877

Pulled By: jaykorean

fbshipit-source-id: 9ec330084cfe96ad9b71aa13c8eb16593256a5ac
2025-11-19 14:04:58 -08:00
Peter Dillinger 678690274d More options for sst_dump recompress (#14133)
Summary:
I have been using sst_dump --command=recompress for some ad hoc automation for compression engineering and these new options help with that.

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

Test Plan: manual

Reviewed By: hx235

Differential Revision: D87453635

Pulled By: pdillinger

fbshipit-source-id: 2ae54e13a9221ec27c6637fea16623465a9163ae
2025-11-19 13:16:06 -08:00
Peter Dillinger 0762586067 Relax an assertion related to parallel compression (#14130)
Summary:
Saw a mysterious failure of assertion
`assert(rep_->props.num_data_blocks == 0)` in
DBCompressionTest/CompressionFailuresTest.CompressionFailures/45. This seems to be caused by a parallel compression failure arriving after the emit thread has started Finish() but before the Flush() at the start of Finish(). We can fix this by relaxing the assertion to allow for the !ok() case. Testing revealed more ok() assertions that needed to be relaxed/moved.

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

Test Plan: Added a sync point to inject a failure status in the right place and added to unit test to be sure the case is essentially covered. It would arguably be a more realistic test to force a particular thread interleaving but I believe simple is good here.

Reviewed By: hx235

Differential Revision: D87377709

Pulled By: pdillinger

fbshipit-source-id: 4bd465673b084afcc235688503d1c2f464eed32d
2025-11-19 09:23:41 -08:00
Hui Xiao 57a6fb9e3a Refactor and support option migration for db with multiple CFs (#14059)
Summary:
**Context/Summary:**
This PR adds multi-cf support to option migration. The original implementation sets options, opens db, compacts files and reopens the db in almost all the three branches below. Such design makes expanding to multi-cf difficult as it needs to change all these places within each of the branch causing code redundancy.
```
Status OptionChangeMigration(std::string dbname, const Options& old_opts,
                             const Options& new_opts) {
  if (old_opts.compaction_style == CompactionStyle::kCompactionStyleFIFO) {
    // LSM generated by FIFO compaction can be opened by any compaction.
    return Status::OK();
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleUniversal) {
    return MigrateToUniversal(dbname, old_opts, new_opts);
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleLevel) {
    return MigrateToLevelBase(dbname, old_opts, new_opts);
  } else if (new_opts.compaction_style ==
             CompactionStyle::kCompactionStyleFIFO) {
    return CompactToLevel(old_opts, dbname, 0, 0 /* l0_file_size */, true);
  } else {
    return Status::NotSupported(
        "Do not how to migrate to this compaction style");
  }
}
```

Therefore this PR
-  Refactor the option migration implementation by moving the common parts into the high-level `OptionChangeMigration()` through `PrepareNoCompactionCFDescriptors()` and `OpenDBWithCFs()` so `MigrateAllCFs()` can focus on compaction only.
-  Treat the original OptionChangeMigration() API as a special case of the multi-cf version option migration
- Add multiple-cf support

A few notes:
- CompactToLevel() originally modifies the compaction-related options conditionally before doing compaction. This is moved into earlier steps through `ApplySpecialSingleLevelSettings()` in `PrepareNoCompactionCFDescriptors()`
- MigrateToUniversal() originally opens the db twice with essentially the same option. This PR reduces that to one open
- Option migration does not always use the old option to compact the db and reopen the db after migration, see `  return CompactToLevel(new_opts, dbname, new_opts.num_levels - 1,/*l0_file_size=*/0, false);`. `PrepareNoCompactionCFDescriptors()` is where we handle those decisions.

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

Test Plan:
- Existing UTs
- New UTs

Reviewed By: cbi42

Differential Revision: D84852970

Pulled By: hx235

fbshipit-source-id: 936b456cf9fb4c3ccb687e5d1387f2d67a1448be
2025-11-19 05:10:03 -08:00
Ryan Hancock b9951ded37 Introducing Prepare all iterators for LevelIterator (#14100)
Summary:
This diff introduces the async prepare of all iterators within a MultiScan. The current state has each iterator be prepared as its needed, and with this diff, we prepare all iterators during the prepare phase of the Level Iterator, this will allow more time for each IO to be dispatched and serviced, increasing the odds that a block is ready as the scan seeks to it.

Benchmark is prefilled using
```
KEYSIZE=64
VALUESIZE=512
NUMKEYS=5000000
SCAN_SIZE=100
DISTANCE=25000
NUM_SCANS=15
THREADS=1

./db_bench --db=$DB \
    --benchmarks="fillseq" \
    --write_buffer_size=5242880 \
    --max_write_buffer_number=4 \
    --target_file_size_base=5242880 \
    --disable_wal=1 --key_size=$KEYSIZE \
    --value_size=$VALUESIZE --num=$NUMKEYS --threads=32

}
```

And benchmark ran is
```
run() {
echo 1 | sudo tee /proc/sys/vm/drop_caches
./db_bench --db=$DB --use_existing_db=1 \
    --benchmarks=multiscan \
    --disable_auto_compactions=1 --seek_nexts=$SCAN_SIZE \
    --multiscan-use-async-io=1 \
    --multiscan-size=$NUM_SCANS --multiscan-stride=$DISTANCE \
    --key_size=$KEYSIZE --value_size=$VALUESIZE \
    --num=$NUMKEYS --threads=$THREADS --duration=60 --statistics
}
```

The benchmark uses large stride sides to ensure that two scans would touch separate files. We reduce the size of the block cache to increase likelyhood of reads (and simulate larger data sets)

**Branch:**

```
Integrated BlobDB: blob cache disabled
RocksDB:    version 10.8.0
Date:       Tue Nov 11 13:26:29 2025
CPU:        166 * AMD EPYC-Milan Processor
CPUCache:   512 KB
Keys:       64 bytes each (+ 0 bytes user-defined timestamp)
Values:     512 bytes each (256 bytes after compression)
Entries:    5000000
Prefix:    0 bytes
Keys per prefix:    0
RawSize:    2746.6 MB (estimated)
FileSize:   1525.9 MB (estimated)
Write rate: 0 bytes/second
Read rate: 0 ops/second
Compression: Snappy
Compression sampling rate: 0
Memtablerep: SkipListFactory
Perf Level: 1
------------------------------------------------
multiscan_stride = 25000
multiscan_size = 15
seek_nexts = 100
DB path: [/data/rocksdb/mydb]
multiscan    :     837.941 micros/op 1193 ops/sec 60.001 seconds 71605 operations; (multscans:71605)
```

**Baseline:**

```
Set seed to 1762898809121995 because --seed was 0
Initializing RocksDB Options from the specified file
Initializing RocksDB Options from command-line flags
Integrated BlobDB: blob cache disabled
RocksDB:    version 10.9.0
Date:       Tue Nov 11 14:06:49 2025
CPU:        166 * AMD EPYC-Milan Processor
CPUCache:   512 KB
Keys:       64 bytes each (+ 0 bytes user-defined timestamp)
Values:     512 bytes each (256 bytes after compression)
Entries:    5000000
Prefix:    0 bytes
Keys per prefix:    0
RawSize:    2746.6 MB (estimated)
FileSize:   1525.9 MB (estimated)
Write rate: 0 bytes/second
Read rate: 0 ops/second
Compression: Snappy
Compression sampling rate: 0
Memtablerep: SkipListFactory
Perf Level: 1
------------------------------------------------
multiscan_stride = 25000
multiscan_size = 15
seek_nexts = 100
DB path: [/data/rocksdb/mydb]
multiscan    :    1129.916 micros/op 885 ops/sec 60.001 seconds 53102 operations; (multscans:53102)
```
Repeated for confirmation.

This introduces a ~20% improvement in latency and op/s.

Note: Benchmarks are single threaded as, when increasing thread count, we start seeing large amounts of overhead being induced by block cache contention, finally resulting in both baseline and branch becoming equal.

Further on network attached storage with high latency, the level iterator, preparing all iterators so a 20% improvement even at high thread counts.

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

Reviewed By: anand1976

Differential Revision: D86913584

Pulled By: krhancoc

fbshipit-source-id: da9d0c890e25e392a33389ce6b80f9bfb84d3f85
2025-11-18 15:57:03 -08:00
Peter Dillinger f6c9c3bf1c Use AutoHCC by default in tools (#14120)
Summary:
Oversight in https://github.com/facebook/rocksdb/issues/13964. More detail:
* Applies to cache_bench and db_bench (db_stress already using it)
* Make sure those along with db_stress treat "hyper_clock_cache" as "auto_hyper_clock_cache" because this is now the blessed implementation.

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

Test Plan: manual runs of the tools

Reviewed By: krhancoc

Differential Revision: D86913202

Pulled By: pdillinger

fbshipit-source-id: 07b425d3522103417f4b034735376b9d759af5fb
2025-11-12 21:40:15 -08:00
Viraj Thakur 2cf81e0a20 fix compiler warning for mutex->AssertHeld (#14115)
Summary:
We are seeing Github actions failures due to a compiler error:

https://github.com/facebook/rocksdb/actions/runs/19190877461/job/54865138898?fbclid=IwY2xjawN_Hc9leHRuA2FlbQIxMQBicmlkETFZeGlpZXZXMGlDTVhTYldwc3J0YwZhcHBfaWQBMAABHp6JoIoMBbZq-8Kgfc1honBdkAbHAZzW2ORiCM2Br2D9utxtMlq6IIqUUQnu_aem_SOU-DDsjDDMB3mTncKfLwQ&brid=VRqQ-asf2myW425wX1qqhg

When UpdatedMutableDbOptions is called from the VersionSet constructor, manifest_file_size_ is 0, and mu is nullptr. This is expected and fine, and we never enter the block where AssertHeld is called.

All other times UpdatedMutableDbOptions is called, the mutex must be held. This PR just checks that mu is not null, to satisfy the compiler. We could alternatively intentionally crash if there is concern over a silent failure if mu is passed as nullptr

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

Reviewed By: pdillinger

Differential Revision: D86733318

Pulled By: virajthakur

fbshipit-source-id: ce9ed6275c9495a3ea2a12f984dbceef7b441e24
2025-11-12 10:29:44 -08:00
Siying Dong c757f5b4e3 Java's Get() to directly return for NotFound (#14095)
Summary:
Right now, in Java's Get() calls, the way Get() is treated is inefficient. Status.NotFound is turned into an exception in the JNI layer, and is caught in the same function to turn into not found return. This causes significant overhead in the scenario where most of the queries ending up with not found. For example, in Spark's deduplication query, this exception creation overhead is higher than Get() itself. With the proposed change, if return status is NotFound, we directly return, rather than going through the exception path

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

Test Plan: Existing tests should cover all Get() cases, and they are passing.

Reviewed By: jaykorean

Differential Revision: D86797594

Pulled By: cbi42

fbshipit-source-id: 1202d24e46a2358976bb7c8ff38a2fd4783d0f99
2025-11-11 15:58:00 -08:00
Ranjan Banerjee 9fbb68be17 Api to get SST file with key ranges for a particular level and key range (startKey, EndKey)rocksdb [Internal version] (#14009)
Summary:
There are instances where  an application might be interested in knowing the distribution in SST files for a key range in a particular level.

This implementation creates an overloaded GetColumnFamilyMetaData api where  (startKey, EndKey) can be passed along with level information to filter the necessary sst files along with the keyranges for each sst file

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

Reviewed By: anand1976

Differential Revision: D83389707

fbshipit-source-id: 6df1dc1f9233efe9000b03cc1831b3c618cbcef3
2025-11-10 17:13:34 -08:00
Xingbo Wang b33c547b06 Add trivial move support in CompactFiles API (#14112)
Summary:
Support trivial move in CompactFiles API, which is not supported previously.

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

Test Plan: Unit test

Reviewed By: cbi42

Differential Revision: D86546150

Pulled By: xingbowang

fbshipit-source-id: 08a3ae9a055f3d3d41711403b1695f44977e6ea8
2025-11-10 15:20:50 -08:00
ngina b897c3789b Merge BuiltinFilterBitsBuilder into FilterBitsBuilder for accurate filter size estimation (#14111)
Summary:
**Summary:**
Merge the BuiltinFilterBitsBuilder into FilterBitsBuilder.  This enables using
CalculateSpace() for accurate filter size estimation instead of hardcoded
bits-per-key which could result in incorrect estimations for different filter types.
The previous hardcoded estimate of 15 bits per key was in the filter block builders UpdateFilterSizeEstimate().

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

Test Plan: - Existing filter tests pass (bloom_test, full_filter_block_test, filter_bench, db_bloom_filter_test)

Reviewed By: pdillinger

Differential Revision: D86473287

Pulled By: nmk70

fbshipit-source-id: cd4a47351e67444e944d5b1b375b3b13274dd6e3
2025-11-10 14:47:36 -08:00
Jay Huh 5879f8b62b Add option to verify block checksums of output files (#14103)
Summary:
For all compactions, RocksDB performs a lightweight sanity check on output SST files before installation (in `CompactionJob::VerifyOutputFiles()`). However, this lightweight check may not catch corruption that is small enough to allow the SST files to still be opened.

There is an existing feature, `paranoid_file_check`, which opens the SST file, iterates through all keys, and checks the hash of each key. While this provides the ultimate level of data integrity checking, it comes at a high computational cost.

In this PR, we introduce a new mutable CF option, `verify_output_flags`. The `verify_output_flags` is a bitmask enum that allows users to select various verification types, including block checksum verification, full key iteration, and file checksum verification (to be added in subsequent PRs). Note that the existing `paranoid_file_check` option is equivalent to a full key iteration check. Block-level checksum verification is much lighter than the full key iteration check.

Please note that the previously deprecated `verify_checksums_in_compaction` option (removed in version 5.3.0) was for verifying the checksum of **input SST files**. RocksDB continues to perform this verification for both local and remote compactions, and this behavior remains unchanged. In contrast, this PR focuses on verifying the **output SST files**.

## To follow up
- File-level Checksum verification for output SST files
- Deprecate `paranoid_file_checks` option in favor of the new option
- Add to stress test / db_bench

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

Test Plan:
New Unit Test added. The corruption is both detected by `paranoid_file_check` and various types of verification set by this new option, `verify_output_flags`
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.CorruptedOutput*"
```

Reviewed By: pdillinger

Differential Revision: D86357924

Pulled By: jaykorean

fbshipit-source-id: a9e04798f249c7e977231e179622a0830d6675fe
2025-11-07 14:22:00 -08:00
Changyu Bi ea75cdc493 Fix a bug in MultiScan that moves iterator backward (#14106)
Summary:
MultiScanUnexpectedSeekTarget() currently uses user key comparison to decide on the next data block for multiscan. This can cause a multiscan to move backward in the following scenario:

data block 1: ..., k@7, k@6
data block 2: k@5, ...

DB iter scan through k@7, k@6 and k@5 and decides to seek to k@0 due to option [`max_sequential_skip_in_iterations`](https://github.com/facebook/rocksdb/blob/d56da8c112b4e6968fd79ce2bf15e6435df40656/include/rocksdb/advanced_options.h#L621-L629). Multiscan was on data block 2, but moves to data block 1 after the seek.

This can cause assertion failure in debug mode and seg fault in prod since older data blocks are unpinned and freed as we advanced a multiscan. This PR fixes the issue by forcing a multiscan to never go backward.

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

Test Plan: - added a new unit test that reproduces the scenario: `./db_iterator_test --gtest_filter="*ReseekAcrossBlocksSameUserKey*"`

Reviewed By: xingbowang

Differential Revision: D86428845

Pulled By: cbi42

fbshipit-source-id: ab623f93e73298a60857fb2ff268366f289092a0
2025-11-07 11:04:57 -08:00
Peter Dillinger 2bee29729a CI: move valgrind to weekly (#14110)
Summary:
This test is now taking > 6 hours, timing out, and has low signal, so creating a weekly job for it, with an explicit timeout of 12 hours.

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

Test Plan: watch CI

Reviewed By: virajthakur

Differential Revision: D86428262

Pulled By: pdillinger

fbshipit-source-id: 44103518064ca378f3fd2ff8d21967ede698c8ea
2025-11-07 10:36:34 -08:00
Peter Dillinger 37176a4a44 Auto-tune manifest file size (#14076)
Summary:
Adds auto-tuning of manifest file size to avoid the need to scale `max_manifest_file_size` in proportion to things like number of SST files to properly balance (a) manifest file write amp and new file creation, vs. (b) manifest file space amp and replay time, including non-incremental space usage in backups. (Manifest file write amp comes from re-writing a "live" record when the manifest file is re-created, or "compacted"; space amp is usage beyond what would be used by a compacted manifest file.) In more detail,

* Add new option `max_manifest_space_amp_pct` with default value of 500, which defaults to 0.2 write amp and up to roughly 5.0 space amp, except `max_manifest_file_size` is treated as the "minimum" size before re-creating ("compacting") the manifest file.
* `max_manifest_file_size` in a way means the same thing, with the same default of 1GB, but in a way has taken on a new role. What is the same is that we do not re-create the manifest file before reaching this size (except for DB re-open), and so users are very unlikely to see a change in default behavior (auto-tuning only kicking in if auto-tuning would exceed 1GB for effective max size for the current manifest file). The new role is as a file size lower bound before auto-tuning kicks in, to minimize churn in files considered "negligibly small." We recommend a new setting of around 1MB or even smaller like 64KB, and expect something like this to become the default soon.
* These two options along with `manifest_preallocation_size` are now mutable with SetDBOptions. The effect is nearly immediate, affecting the next write to the current manifest file.

Also in this PR:
* Refactoring of VersionSet to allow it to get (more) settings from MutableDBOptions. This touches a number of files in not very interesting ways, but notably we have to be careful about thread-safe access to MutableDBOptions fields, and even fields within VersionSet. I have decided to save copies of relevant fields from MutableDBOptions to simplify testing, etc. by not saving a reference to MutableDBOptions but getting notified of updates.
* Updated some logging in VersionSet to provide some basic data about final and compacted manifest sizes (effects of auto-tuning), making sure to avoid I/O while holding DB mutex.
* Added db_etc3_test.cc which is intended as a successor to db_test and db_test2, but having "test.cc" in its name for easier exclusion of test files when using `git grep`. Intended follow-up: rename db_test2 to db_etc2_test
* Moved+updated `ManifestRollOver` test to the new file to be closer to other manifest file rollover testing.

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

Test Plan:
As for correctness, new unit test AutoTuneManifestSize is pretty thorough. Some other unit tests updated appropriately. Manual tests in the performance section were also audited for expected behavior based on the new logging in the DB LOG. Example LOG data with -max_manifest_file_size=2048 -max_manifest_space_amp_pct=500:

```
2025/10/24-11:12:48.979472 2150678 [/version_set.cc:5927] Created manifest 5, compacted+appended from 52 to 116
2025/10/24-11:12:49.626441 2150682 [/version_set.cc:5927] Created manifest 24, compacted+appended from 2169 to 1801
2025/10/24-11:12:52.194592 2150682 [/version_set.cc:5927] Created manifest 91, compacted+appended from 10913 to 8707
2025/10/24-11:13:02.969944 2150682 [/version_set.cc:5927] Created manifest 362, compacted+appended from 52259 to 13321
2025/10/24-11:13:18.815120 2150681 [/version_set.cc:5927] Created manifest 765, compacted+appended from 80064 to 13304
2025/10/24-11:13:35.590905 2150681 [/version_set.cc:5927] Created manifest 1167, compacted+appended from 79863 to 13304
```

As you can see, it only took a few iterations of ramp-up to settle on the auto-tuned max manifest size for tracking ~122 live SST files, around 80KB and compacting down to about 13KB. (13KB * (500 + 100) / 100 = 78KB). With the default large setting for max_manifest_file_size, we end up with a 232KB manifest, which is more than 90% wasted space. (A long-running DB would be much worse.)

As for performance, we don't expect a difference, even with TransactionDB because actual writing of the manifest is done without holding the DB mutex. I was not able to see a performance regression using db_bench with FIFO compaction and >1000 ~10MB SST files, including settings of -max_manifest_file_size=2048 -max_manifest_space_amp_pct={500,10,0}. No "hiccups" visible with -histogram either.

I also tried seeding a 1 second delay in writing new manifest files (other than the first). This had no significant effect at -max_manifest_space_amp_pct=500 but at 100 started causing write stalls in my test. In many ways this is kind of a worst case scenario and out-of-proportion test, but gives me more confidence that a higher number like 500 is probably the best balance in general.

Reviewed By: xingbowang

Differential Revision: D85445178

Pulled By: pdillinger

fbshipit-source-id: 1e6e07e89c586762dd65c65bb7cb2b8b719513f9
2025-11-07 09:04:52 -08:00
ngina 7603712a88 Introduce tail estimation to prevent oversized compaction files (#14051)
Summary:
**Summary:**
This change introduces tail size estimation during SST construction to improve compaction file cutting accuracy to prevent oversized files. The BlockBasedTableBuilder now estimates the SST tail size (index and filter blocks) and uses this estimate, in addition to the data size, to determine when to cut files during compaction.

**Problem:**
Currently, file cutting logic only considers data size when determining where to cut a file, failing to reserve space for index and filter blocks that are added when the file is finalized. This often leads to SST files that exceed target file size limits.

**Behavior Change:**
Implement size estimation methods for index and filter builders, and integrate these estimates into BlockBasedTableBuilder via a new EstimatedTailSize() method. This method aggregates estimates from all tail components and is used for file cutting decisions during compaction.

**Performance Considerations:**
To minimize CPU overhead, size estimates are updated when data blocks are finalized rather than on every key add. For index builders, estimates are updated when index entries are added (one per data block). For filter builders, the OnDataBlockFinalized() hook triggers estimate updates when data blocks are cut/finalized.

This approach provides:
* Minimal impact to compaction hot path (key additions)
* Near real-time estimates for file cutting decisions
* Meaningful estimate changes only when data blocks are finalized

**Usage:**
* Set true mutable cf option `compaction_use_tail_size_estimation`
to use tail size estimation for compaction file cutting decisions.

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

Test Plan:
* Assert tail size estimate is an overestimate in BlockBasedTableBuilder::Finish
* Add new test to verify compaction output file is below target file size

**Next steps:**
* Enable tail size estimation for compaction file cutting by default (and other improvements)

Reviewed By: pdillinger, cbi42

Differential Revision: D84852285

Pulled By: nmk70

fbshipit-source-id: c43cf5dbd2cb2f623a0622591ef24eee30ce0c87
2025-11-05 20:00:00 -08:00
Peter Dillinger d56da8c112 More folly build updates (#14099)
Summary:
* Fix nightly build-linux-cmake-with-folly-lite-no-test for real this time
  with correct include directory. (CMakeLists.txt)
* Add test runs to that build (and rename)
* Improve folly build caching with a folly.mk file with most of the relevant
  parts of Makefile that contribute to the checkout_folly and
  build_folly builds. This reduces the risk of false passing of CI job with
  cache folly build. This caching is still only for folly debug builds, (which
  is probably OK with just a single nightly build relying on release folly
  build, which also serves as a rough canary against false passing
  because of caching).
* Use `make VERBOSE=1` after cmake calls for detailed output

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

Test Plan:
temporary CI change to put the relevant parts in pr-jobs,
then back to homes including in nightly

Reviewed By: mszeszko-meta

Differential Revision: D86243363

Pulled By: pdillinger

fbshipit-source-id: f7975fa190ef45195c6d0b74417f7886e551516a
2025-11-05 11:39:21 -08:00
Peter Dillinger befa6b8050 Fix and check for potential ODR violations (#14096)
Summary:
... caused by public headers depending on build parameters (macro definitions). This change also adds a check under 'make check-headers' (already in CI) looking for potential future violations.

I've audited the uses of '#if' in public headers and either
* Eliminated them
* Systematically excluded them because they are intentional or similar (details in comments in check-public-header.sh
* Manually excluded them as being ODR-SAFE

In the case of ROCKSDB_USING_THREAD_STATUS, there was no good reason for this to appear in public headers so I've replaced it with a static bool ThreadStatus::kEnabled. I considered getting rid of the ability to disable this code but some relatively recent PRs have been submitted for fixing that case. I've added a release note and updated one of the CI jobs to use this build configuration. (I didn't want to combine with some jobs like no_compression and status_checked because the interaction might limit what is checked.

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

Test Plan: manual 'make check-headers' + manual cmake as in new CI config + CI

Reviewed By: jaykorean

Differential Revision: D86241864

Pulled By: pdillinger

fbshipit-source-id: d16addc9e3480706b174a006720a4def0740bf2e
2025-11-04 19:47:42 -08:00
Peter Dillinger 9577b92b55 Fix ODR violation from open source folly build, update (#14094)
Summary:
Following up on https://github.com/facebook/rocksdb/pull/14071, updating folly to
https://github.com/facebook/folly/commit/8a9fc1e80a18cafadbec85e33d5042ce13a7c634 or beyond was failing an F14Table assertion for a very subtle reason: ODR violation between the folly build and RocksDB build because folly build was release mode and RocksDB build was debug mode. What was happening was that folly change introduced a dependence on kDebug (whether build is debug) in a hashing implementation in a .h file, and the inconsistency between the inlined implementation during RocksDB build and the linked-to implementation from the folly build was leading to inconsistencies in the data structure.

The primary fix is to ensure we build folly in debug mode for debug mode RocksDB builds. Also,

* Needed to use the `patchelf` tool in `build_folly` to ensure the glog dependency shared library can always find its own gflags dependency. I explored many options for working around this, and this is what would work without reworking folly's own build.
* Updated folly to latest commit.
* Thrown in an ad hoc folly patch to use ftp.gnu.org mirrors (the canonical is super slow)
* Moved the placement of GETDEPS_USE_WGET=1 to apply to local builds also, to avoid the issue of a large download almost reaching completion and then stalling indefinitely.
* Fix failing nightly build-linux-cmake-with-folly-lite-no-test with fmt includes in cmake build (as was done with make build)
* Add a release mode folly+RocksDB to nightly CI, including both cmake and make. This also serves as a non-cached folly build to detect potential problems with PR jobs working from cached folly build.
* Move build-linux-cmake-with-folly to nightly because it's mostly covered by build-linux-cmake-with-folly-coroutines

Intended follow-up:
* folly-lite build with tests
* Make the folly build caching more friendly+accurate by hashing the relevant Makefile parts and tagging whether debug or release. Not in this PR because then you wouldn't be able to see what changed in the folly build steps themselves.

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

Test Plan: manual + CI

Reviewed By: mszeszko-meta

Differential Revision: D85864871

Pulled By: pdillinger

fbshipit-source-id: 50009b33422d5781074fcbbdf18089be9e36800d
2025-11-02 16:08:09 -08:00
Peter Dillinger 94d91daddb Update folly (part way), fix USE_FOLLY_LITE (#14071)
Summary:
Resolving this folly upgrade required fixing the FOLLY_LITE build with header include from the 'fmt' library.

I was close to timing out on fixing USE_FOLLY_LITE and removing it altogether - it could be considered obsolete and/or not worth the maintenance cost.

Follow-up: make the folly build caching more friendly by hashing the relevant makefile parts. Not in this PR because then you wouldn't be able to see what changed in the folly build steps themselves.

UPDATE/NOTE: I wasn't able to fully update to latest due to a failure seen in F14, using the next folly commit or later. The source of the bug is likely outside of F14 but investigation is in progress.

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

Test Plan: CI

Reviewed By: jaykorean

Differential Revision: D85268833

Pulled By: pdillinger

fbshipit-source-id: 1d0a2d61f095524a20e6ec796ef46c02d0696f4e
2025-10-29 16:02:10 -07:00
anand76 0eb5b43b4f Change PosixWritableFile Truncate to reseek to new end of file (#14088)
Summary:
Change PosixWritableFile's Truncate to the new end offset. This ensures that future appends are written with no holes or overwrites. RocksDB doesn't guarantee this in the FileSystem contract, and its left up to the specific implementation.

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

Reviewed By: cbi42

Differential Revision: D85786398

Pulled By: anand1976

fbshipit-source-id: 3520d9d6336362f5128a17bbf396297d821a5da3
2025-10-29 12:58:03 -07:00
zaidoon 1bb704b6e0 optimize memory allocations and vector overhead in RocksDB C API using unique_ptr and PinnableSlice (#14036)
Summary:
Comprehensive performance optimizations for the RocksDB C API that eliminate unnecessary memory allocations and copies.

## Key Changes

### 1. PinnableSlice for Get Operations (50% reduction in copies)
- Changed all `rocksdb_get*` functions to use `PinnableSlice` internally instead of `std::string`
- **Before:** RocksDB → std::string → malloc'd buffer (2 copies)
- **After:** RocksDB → malloc'd buffer (1 copy)
- Affects: Get, Transaction Get, TransactionDB Get, WriteBatch Get variants

### 2. Array-Based MultiGet with PinnableSlice (30% allocation reduction)
- Switched MultiGet operations to use optimized array-based RocksDB API with `PinnableSlice`
- Eliminates vector overhead and string allocations
- Affects: MultiGet, Transaction MultiGet, TransactionDB MultiGet variants

### New Zero-Copy APIs
Added high-performance zero-copy functions for applications that can use them:
- `rocksdb_iter_key_slice()` / `value_slice()` / `timestamp_slice()` - Return slices by value (eliminates output param overhead)
- `rocksdb_batched_multi_get_cf_slice()` - Batched get with slice array input
- `rocksdb_slice_t` - ABI-compatible slice type

Note that this pr builds on top of https://github.com/facebook/rocksdb/pull/13911

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

Reviewed By: pdillinger

Differential Revision: D85604919

Pulled By: jaykorean

fbshipit-source-id: 7f04b935eea79af1d45b3125a79b90e4706666f6
2025-10-29 12:57:49 -07:00
Changyu Bi 64817ae604 Disable internal reseeking for multiscan stress test (#14087)
Summary:
Stress test can fail with assertion inside MultiScan in some reseek scenario. E.g., data block 1 ends with k@9, data block 2 starts with k@8, when a DB iter seeks to k@0 (see option `max_sequential_skip_in_iterations`), MultiScan will land in data block 1 due to https://github.com/facebook/rocksdb/blob/fd0b4e0cf08315f6a644d54d585fe70ca958d4ba/table/block_based/block_based_table_iterator.cc#L1258-L1263.

We can't just use internal key as separator since index block might not use it. I plan to follow up with a fix that never moves `cur_data_block_idx` backward within a MultiScan.

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

Test Plan: CI and internal crash tests

Reviewed By: anand1976

Differential Revision: D85701668

Pulled By: cbi42

fbshipit-source-id: d3f1aaff40a12be4e3d1b4b7160bf2547f43b849
2025-10-29 12:42:34 -07:00
Jay Huh fd0b4e0cf0 Disable mmap_read in Stress Test (#14083)
Summary:
All remote compaction test failures had `mmap_read=1` in common. Unfortunately, the failure hasn't been very reproducible. Try disabling `mmap_read` to see if that shed some light.

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

Test Plan: CI

Reviewed By: hx235

Differential Revision: D85622229

Pulled By: jaykorean

fbshipit-source-id: bbe9e08efc369813f0fec388c910446089e43650
2025-10-28 12:59:00 -07:00
Changyu Bi 12b85c8ce9 Fix timestamp handling in LevelIterator MultiScan seeks (#14085)
Summary:
As titled, this fixes some internal crash test failures when UDT is enabled.

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

Test Plan: monitor crash tests.

Reviewed By: anand1976

Differential Revision: D85617949

Pulled By: cbi42

fbshipit-source-id: da6fb21c0ca5803ea24e8daf7de8558321babcf4
2025-10-28 11:15:42 -07:00
Jay Huh a3aa44a716 Fix regression test script for internal use (#14079)
Summary:
Due to some internal requirements, what's being used for`$SSH` and `$SCP` has changed and it broke the regression test. (e.g. tarball streaming to remote host no longer works)

Minor behavior changes to the script to make the internal workflow work.

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

Test Plan:
```
./tools/regression_test.sh
```
Meta Internal automation

Reviewed By: pdillinger

Differential Revision: D85502798

Pulled By: jaykorean

fbshipit-source-id: d294c2ee47661fbe368ccc318062e891f3ac7c81
2025-10-27 14:22:47 -07:00
zaidoon 32f66712c8 optimize C API to reduce memory allocations and using PinnableSlice for zero-copy reads (#13911)
Summary:
### Problem
The current C API implementation has inefficiencies that impact performance in production environments:

1. **Double allocations in Get operations**: Values are first copied into a `std::string`, then copied again into a malloc'd buffer
2. **Unnecessary string temporaries**: Using `std::string` as intermediate storage adds allocation/deallocation overhead
3. **No zero-copy read path**: All read operations require at least one allocation and copy
4. **Redundant operations**: CopyString performed unnecessary `sizeof(char)` multiplication

### Solution

#### 1. Use PinnableSlice for Get Operations
- **Before**: `DB::Get() → std::string → malloc'd buffer` (2 allocations, 2 copies)
- **After**: `DB::Get() → PinnableSlice → malloc'd buffer` (1 allocation, 1 copy)
- **Impact**: 50% reduction in allocations and copies

#### 2. Optimize CopyString Helper
- Removed redundant `sizeof(char)` multiplication
- Single implementation using `Slice` parameter (works with all types via implicit conversion)
- Added `inline` for better optimization

#### 3. New Zero-Copy API Functions
Added high-performance alternatives for allocation-sensitive workloads:
- rocksdb_get_pinned_v2/ rocksdb_get_pinned_cf_v2 - Zero-copy read access
- rocksdb_get_into_buffer/ rocksdb_get_into_buffer_cf - Copy into user-provided buffer
- `rocksdb_pinnable_handle_*` - Handle management functions

### Performance Improvements

| Operation | Allocations | Improvement |
|-----------|------------|-------------|
| [rocksdb_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1391:0-1411:1) | 2 → 1 | **50% reduction** |
| [rocksdb_get_cf](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1411:0-1431:1) | 2 → 1 | **50% reduction** |
| [rocksdb_multi_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:1495:0-1520:1) (per key) | 2 → 1 | **50% reduction** |
| [rocksdb_transaction_get](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:6730:0-6748:1) | 2 → 1 | **50% reduction** |
| [rocksdb_writebatch_wi_get_from_batch](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:2714:0-2732:1) | 2 → 1 | **50% reduction** |
| [rocksdb_get_pinned_v2](cci:1://file:///Users/zaidoon/public%20repos/rocksdb/db/c.cc:7761:0-7775:1) (new) | 0 | **100% reduction** |

### Functions Optimized (30+)
- All Get variants (regular, CF, with timestamps)
- All MultiGet variants
- All Transaction Get/MultiGet operations
- All WriteBatch Get operations
- KeyMayExist operations
- Metadata getters (column family names, SST file keys, transaction names, DB identity)

### Testing
- Added tests for new zero-copy functions
- Added tests for previously untested functions rocksdb_column_family_handle_get_name, rocksdb_transaction_get_name

### Migration Path
Applications can adopt improvements in three ways:
1. **No changes needed** - Existing code automatically benefits from 50% allocation reduction
2. **Incremental adoption** - Replace hot-path calls with zero-copy variants
3. **Full optimization** - Use rocksdb_get_into_buffer

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

Reviewed By: cbi42

Differential Revision: D83508431

Pulled By: jaykorean

fbshipit-source-id: 96146a59b0f9e839f6603b376d4e51f0e97c3a8c
2025-10-27 13:16:33 -07:00
Andrew Kryczka 10478b98a5 Fix unsigned underflow in WAL TTL logic when system clock goes backwards (#14016)
Summary:
The TTL-based WAL archive cleanup logic could incorrectly delete an archived WAL if the system clock moved backwards between the last write to that WAL and `WALManager::PurgeObsoleteWALFiles()`. This happened due to unsigned underflow in subtraction of two wall clock based timestamps: `now_seconds - file_m_time`.

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

Test Plan: unit test repro

Reviewed By: pdillinger

Differential Revision: D83879806

Pulled By: hx235

fbshipit-source-id: 643e7f623c6b5c31711565854314cfd6cbbcf3a7
2025-10-24 17:10:48 -07:00
Andrew Kryczka e687ca79b4 Fix a missing CV signal in FindObsoleteFiles() (#14069)
Summary:
Fixed a missing CV signal when `FindObsoleteFiles()` decides there is nothing to purge and then decrements `pending_purge_obsolete_files_` to zero.  This bug could cause `DB::GetSortedWalFiles()` to hang, at least.

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

Test Plan: unit test repro

Reviewed By: hx235

Differential Revision: D85453534

Pulled By: cbi42

fbshipit-source-id: cf5cfe7f5087459ca1f1f28ce81ea6afc84178f0
2025-10-24 13:11:26 -07:00
Changyu Bi 2edc660e28 Fix multiscan assert failure in stress test (#14077)
Summary:
should not use async_io when not supported to avoid the assert failure here: https://github.com/facebook/rocksdb/blob/dce33f9443815dcbe1d9a98d4d34776dfdf1112e/table/block_based/block_based_table_iterator.cc#L1710.

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

Test Plan: monitor future CI failure.

Reviewed By: anand1976

Differential Revision: D85456447

Pulled By: cbi42

fbshipit-source-id: dccc865a5aedf194029a53616f4bbc99d0162691
2025-10-24 12:51:43 -07:00
Xingbo Wang dce33f9443 Follow up on MultiScan change in #14040 (#14055)
Summary:
* Address feedback from https://github.com/facebook/rocksdb/issues/14040
* Add additional test for MultiScan
* Fix a bug when del range and data are in same file for multi-scan
* Rewrite the cases need to be handled in SeekMultiScan

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

Test Plan: Unit test

Reviewed By: cbi42, anand1976

Differential Revision: D84851788

Pulled By: xingbowang

fbshipit-source-id: 0f69632733afb99685f6341badbf239681010c38
2025-10-23 20:34:21 -07:00
Jay Huh fac8222bfe Make Meta Internal Linter happy (#14074)
Summary:
Linter complains like this
```
  void foo(Arg parameter_name) {}
    void bar() {
    Arg a;
    foo(/*some_other_name=*/ a); // Wrong! Comment/parameter name mismatch
    foo(/*parameter_name=*/ a);  // This is OK; the names match.
  }
```
```
Argument name in comment (`read_only`) does not match parameter name (`unchanging`).
```

This used to be warning, but now treated as an error :(

Fixing a few other linter warnings before they become errors in the future.

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

Test Plan: CI

Reviewed By: archang19

Differential Revision: D85370353

Pulled By: jaykorean

fbshipit-source-id: 20e96aad740d516a29c0424282674e655f99c0a2
2025-10-23 18:10:12 -07:00
Changyu Bi 144e9f1e42 Fix compaction picking with L0 standalone range deletion file (#14061)
Summary:
When a standalone range deletion file is ingested in L0, currently it is compacted with any overlapping L0 files. This is not desirable when we ingest new data on top of the range deletion file. This PR fixes the compaction picking logic to only consider L0 files older than the standalone range deletion file.

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

Test Plan: added a new unit test and updated an existing one.

Reviewed By: xingbowang

Differential Revision: D84930780

Pulled By: cbi42

fbshipit-source-id: 65f4403ccb40ba964b9e65b09e2f7f7efebe81df
2025-10-23 13:34:07 -07:00
Jay Huh e691965558 Start 10.9.0 development (#14067)
Summary:
10.8.0 branch has been cut.

Updated
- HISTORY.md
- include/rocksdb/version.h
- tools/check_format_compatible.sh

To follow up
- folly update

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

Test Plan: CI

Reviewed By: pdillinger

Differential Revision: D85186398

Pulled By: jaykorean

fbshipit-source-id: 44920156aa2a62ba40626766dc4ebdbc02f23fa8
2025-10-22 12:48:31 -07:00
238 changed files with 13614 additions and 3314 deletions

No files matched your search

+14 -7
View File
@@ -7,20 +7,27 @@ outputs:
runs:
using: composite
steps:
- name: Extract FOLLY_COMMIT_HASH from Makefile
- name: Extract FOLLY_MK_HASH
id: extract-folly-hash
shell: bash
run: |
FOLLY_COMMIT_HASH=$(grep '^FOLLY_COMMIT_HASH' Makefile | awk '{print $3}')
echo "hash=$FOLLY_COMMIT_HASH" >> $GITHUB_OUTPUT
FOLLY_MK_HASH=$(md5sum folly.mk | cut -d' ' -f1)
echo "hash=$FOLLY_MK_HASH" >> $GITHUB_OUTPUT
- name: Extract FOLLY_INSTALL_DIR
id: extract-folly-install-dir
shell: bash
run: |
FOLLY_INSTALL_DIR=$(cd third-party/folly && python3 build/fbcode_builder/getdeps.py show-inst-dir)
echo "dir=$(echo $FOLLY_INSTALL_DIR | sed 's|installed/folly|installed|')" >> $GITHUB_OUTPUT
- name: Cache folly build
id: cache-folly-build
uses: actions/cache@v4
with:
# Cache the folly build directory
path: /tmp/fbcode_builder_getdeps-Z__wZrocksdbZrocksdbZthird-partyZfollyZbuildZfbcode_builder-root/installed
path: ${{ steps.extract-folly-install-dir.outputs.dir }}
# Key is based on:
# - OS and architecture
# - The specific folly commit hash from Makefile
# - The container image version to account for different compiler/library versions
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.extract-folly-hash.outputs.hash }}-ubuntu22.1-v1
# - The docker image, which may not always be specified/known
# - Hash of folly.mk, which includes the folly repository commit hash
# NOTE: this is still only intended for DEBUG folly builds
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ github.job_container.image }}-${{ steps.extract-folly-hash.outputs.hash }}
@@ -0,0 +1,21 @@
name: cache-getdeps-downloads
description: Cache getdeps downloads to avoid unreliable mirrors and speed up builds
outputs:
cache-hit:
description: Whether the cache was hit
value: ${{ steps.cache-downloads.outputs.cache-hit }}
runs:
using: composite
steps:
- name: Cache getdeps downloads
id: cache-downloads
uses: actions/cache@v4
with:
# Use a fixed path that we control - folly.mk will sync with getdeps downloads dir
path: /tmp/rocksdb-getdeps-cache
# Use a rolling cache key - the cache accumulates downloads over time
# The key includes a weekly timestamp to ensure periodic refresh
key: getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-week-${{ github.run_id }}
restore-keys: |
getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-week-
getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-
+3 -1
View File
@@ -5,5 +5,7 @@ runs:
- name: Checkout folly sources
run: |
make checkout_folly
echo "GETDEPS_USE_WGET=1" >> "$GITHUB_ENV"
shell: bash
- name: Install patchelf
run: apt-get update -y && apt-get install -y patchelf
shell: bash
+43 -4
View File
@@ -54,11 +54,12 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/build-folly"
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
- uses: "./.github/actions/post-steps"
build-linux-valgrind:
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -68,7 +69,30 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j32 valgrind_test
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-release-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-windows-vs2022-avx2:
if: ${{ github.repository_owner == 'facebook' }}
@@ -89,6 +113,20 @@ jobs:
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev
- run: make V=1 J=4 -j4 check
- uses: "./.github/actions/post-steps"
build-linux-arm-crashtest:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 4-core-ubuntu-arm
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
- run: sudo mount -o remount,size=16G /dev/shm
- run: sudo dd bs=1048576 count=4096 if=/dev/zero of=/swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_atomic_flush
- run: rm -rf /dev/shm/rocksdb.*
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_multiops_wc_txn
- uses: "./.github/actions/post-steps"
build-examples:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
@@ -117,7 +155,7 @@ jobs:
- name: Build fuzzers
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly-lite-no-test:
build-linux-cmake-with-folly-lite:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -127,6 +165,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make V=1 -j20)"
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
+7 -22
View File
@@ -92,24 +92,6 @@ jobs:
which javac && javac -version
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-make-with-folly:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
@@ -120,6 +102,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
@@ -138,6 +121,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
- uses: "./.github/actions/post-steps"
@@ -151,15 +135,16 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- uses: "./.github/actions/cache-getdeps-downloads"
- uses: "./.github/actions/setup-folly"
- uses: "./.github/actions/cache-folly"
id: cache-folly
- uses: "./.github/actions/build-folly"
with:
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
- uses: "./.github/actions/post-steps"
build-linux-cmake-with-benchmark:
build-linux-cmake-with-benchmark-no-thread-status:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
@@ -169,7 +154,7 @@ jobs:
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
- uses: "./.github/actions/post-steps"
build-linux-encrypted_env-no_compression:
if: ${{ github.repository_owner == 'facebook' }}
@@ -372,7 +357,7 @@ jobs:
- name: cmake generate project file
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
- name: Build tests
run: cd build && make V=1 -j8
run: cd build && make VERBOSE=1 -j8
- name: Run shard 0 out of 4 test shards
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
if: ${{ matrix.run_sharded_tests == 0 }}
+20
View File
@@ -0,0 +1,20 @@
name: facebook/rocksdb/weekly
on:
schedule:
- cron: 0 9 * * 0
workflow_dispatch:
permissions: {}
jobs:
build-linux-valgrind:
if: ${{ github.repository_owner == 'facebook' }}
runs-on:
labels: 16-core-ubuntu
timeout-minutes: 840
container:
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
options: --shm-size=16gb
steps:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: make V=1 -j20 valgrind_test
- uses: "./.github/actions/post-steps"
+11 -8
View File
@@ -1,12 +1,14 @@
# This file @generated by:
#$ python3 buckifier/buckify_rocksdb.py
# --> DO NOT EDIT MANUALLY <--
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
oncall("rocksdb_point_of_contact")
cpp_library_wrapper(name="rocksdb_lib", srcs=[
"cache/cache.cc",
"cache/cache_entry_roles.cc",
@@ -266,6 +268,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"util/dynamic_bloom.cc",
"util/file_checksum_helper.cc",
"util/hash.cc",
"util/io_dispatcher_imp.cc",
"util/murmurhash.cc",
"util/random.cc",
"util/rate_limiter.cc",
@@ -5200,12 +5203,6 @@ cpp_unittest_wrapper(name="import_column_family_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="index_builder_test",
srcs=["table/block_based/index_builder_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="inlineskiplist_test",
srcs=["memtable/inlineskiplist_test.cc"],
deps=[":rocksdb_test_lib"],
@@ -5218,6 +5215,12 @@ cpp_unittest_wrapper(name="interval_test",
extra_compiler_flags=[])
cpp_unittest_wrapper(name="io_dispatcher_test",
srcs=["util/io_dispatcher_test.cc"],
deps=[":rocksdb_test_lib"],
extra_compiler_flags=[])
cpp_unittest_wrapper(name="io_posix_test",
srcs=["env/io_posix_test.cc"],
deps=[":rocksdb_test_lib"],
+26 -2
View File
@@ -80,6 +80,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
"Default BUILD_TYPE is ${default_build_type}" FORCE)
endif()
message(STATUS "CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
@@ -132,7 +133,9 @@ else()
option(WITH_GFLAGS "build with GFlags" ON)
endif()
set(GFLAGS_LIB)
if(WITH_GFLAGS)
# Skip all gflags detection and setup when USE_FOLLY or USE_COROUTINES is enabled
# since Folly provides its own gflags (USE_COROUTINES automatically sets USE_FOLLY)
if(WITH_GFLAGS AND NOT USE_FOLLY AND NOT USE_COROUTINES)
# Config with namespace available since gflags 2.2.2
option(GFLAGS_USE_TARGET_NAMESPACE "Use gflags import target with namespace." ON)
find_package(gflags CONFIG)
@@ -151,6 +154,9 @@ else()
include_directories(${GFLAGS_INCLUDE_DIR})
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
add_definitions(-DGFLAGS=1)
elseif(WITH_GFLAGS AND (USE_FOLLY OR USE_COROUTINES))
# Still set the DGFLAGS=1 define when using Folly since Folly provides gflags
add_definitions(-DGFLAGS=1)
endif()
if(WITH_SNAPPY)
@@ -213,6 +219,10 @@ if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DNDEBUG")
message(STATUS "Setting /DNDEBUG as CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
endif()
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
@@ -642,6 +652,12 @@ if(USE_FOLLY)
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
# Fix gflags library name for debug builds
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=${GFLAGS_INST_PATH}/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_INST_PATH}/lib/libgflags_debug.so.2.2")
endif()
endif()
add_compile_definitions(USE_FOLLY FOLLY_NO_CONFIG HAVE_CXX11_ATOMIC)
@@ -1082,12 +1098,21 @@ if(USE_FOLLY_LITE)
third-party/folly/folly/synchronization/DistributedMutex.cpp
third-party/folly/folly/synchronization/ParkingLot.cpp)
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
# Add boost to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir boost OUTPUT_VARIABLE
BOOST_SOURCE_PATH)
exec_program(ls ARGS -d ${BOOST_SOURCE_PATH}/boost* OUTPUT_VARIABLE
BOOST_INCLUDE_DIR)
include_directories(${BOOST_INCLUDE_DIR})
# Add fmt to the include path
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
build/fbcode_builder/getdeps.py show-source-dir fmt OUTPUT_VARIABLE
FMT_SOURCE_PATH)
exec_program(ls ARGS -d ${FMT_SOURCE_PATH}/fmt*/include OUTPUT_VARIABLE
FMT_INCLUDE_DIR)
include_directories(${FMT_INCLUDE_DIR})
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
list(APPEND THIRDPARTY_LIBS glog)
endif()
@@ -1453,7 +1478,6 @@ if(WITH_TESTS)
table/block_based/block_based_table_reader_test.cc
table/block_based/block_test.cc
table/block_based/data_block_hash_index_test.cc
table/block_based/index_builder_test.cc
table/block_based/full_filter_block_test.cc
table/block_based/partitioned_filter_block_test.cc
table/cleanable_test.cc
+42 -5
View File
@@ -1,18 +1,55 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.8.3 (11/10/2025)
### New Features
* Add a GetColumnFamilyMetaData API variant in DB to get the SST files intersecting a given key range.
## 10.11.2 (02/25/2026)
### Bug Fixes
* Fix longstanding failures that can arise from reading and/or compacting old DB dirs with range deletions (likely from version < 5.19.0) in many newer versions.
## 10.8.2 (11/07/2025)
## 10.11.1 (02/06/2026)
### Bug Fixes
* Fixed a bug in PosixFS AbortIO that could cause it to ignore an IO completion and a subsequent Poll to hang
## 10.11.0 (01/23/2026)
### Public API Changes
* New SetOptions API that allows setting options for multiple CFs, avoiding the need to reserialize OPTIONS file for each CF
* Remove remaining pieces of Lua integration
### Behavior Changes
* The new default for `BlockBasedTableOptions::format_version` is 7, which has been supported since RocksDB 10.4.0 and is required in order to use CompressionManagers supporting custom compression types.
### Bug Fixes
* Fixed a small performance bug with `format_version=7` when decompressing formats other than Snappy and ZSTD.
* Fixed an infinite compaction loop bug with User-Defined Timestamps (UDT) where bottommost files were repeatedly marked for compaction even though their timestamp could not be collapsed.
* Bugfix for persisted UDT record sequence number zeroing logic.
## 10.10.0 (12/16/2025)
### Bug Fixes
* Fixed a bug in best-efforts recovery that causes use-after-free crashes when accessing SST files that were cached during the recovery.
* Fix resumable compaction incorrectly allowing resumption from a truncated range deletion that is not well handled currently.
* Fixed a bug in `PosixRandomFileAccess` IO uring submission queue ownership & management. Fix eliminates the false positive 'Bad cqe data' IO errors in `PosixRandomFileAccess::MultiRead` when interleaved with `PosixRandomFileAccess::ReadAsync` on the same thread.
## 10.9.0 (11/21/2025)
### New Features
* Added an auto-tuning feature for DB manifest file size that also (by default) improves the safety of existing configurations in case `max_manifest_file_size` is repeatedly exceeded. The new recommendation is to set `max_manifest_file_size` to something small like 1MB and tune `max_manifest_space_amp_pct` as needed to balance write amp and space amp in the manifest. Refer to comments on those options in `DBOptions` for details. Both options are (now) mutable.
* Added a new API to support option migration for multiple column families
* Added new option target_file_size_is_upper_bound that makes most compaction output SST files come close to the target file size without exceeding it, rather than commonly exceeding it by some fraction (current behavior). For now the new behavior is off by default, but we expect to enable it by default in the future.
* Add a new option allow_trivial_move in CompactionOptions to allow CompactFiles to perform trivial move if possible. By default the flag of allow_trivial_move is false, so it preserve the original behavior.
## 10.8.1 (10/29/2025)
### Public API Changes
* To reduce risk of ODR violations or similar, `ROCKSDB_USING_THREAD_STATUS` has been removed from public headers and replaced with static `const bool ThreadStatus::kEnabled`. Some other uses of conditional compilation have been removed from public API headers to reduce risk of ODR violations or other issues.
### Behavior Changes
* PosixWritableFile now repositions the seek pointer to the new end of file after a call to Truncate.
* Updated standalone range deletion L0 file compaction behavior to avoid compacting with any newer L0 files (which is expensive and not useful).
### Bug Fixes
* Fix a bug where compaction with range deletion can persist kTypeMaxValid in MANIFEST as file metadata. kTypeMaxValid is not supposed to be persisted and can change as new value types are introduced. This can cause a forward compatibility issue where older versions of RocksDB don't recognize kTypeMaxValid from newer versions. A new placeholder value type kTypeTruncatedRangeDeletionSentinel is also introduced to replace kTypeMaxValid when reading existing SST files' metadata from MANIFEST. This allows us to strengthen some checks to avoid using kTypeMaxValid in the future.
* Fixed a bug where `DB::GetSortedWalFiles()` could hang when waiting for a purge operation that found nothing to do (potentially triggered by iterator release, flush, compaction, etc.).
* Fixed a bug in MultiScan where `max_sequential_skip_in_iterations` could cause the iterator to seek backward to already-unpinned blocks when the same user key spans multiple data blocks, leading to assertion failures or seg fault.
* Fixed a bug for `WAL_ttl_seconds > 0` use cases where the newest archived WAL files could be incorrectly deleted when the system clock moved backwards.
### Performance Improvements
* Added optimization that allowed for the asynchronous prefetching of all data outlined in a multiscan iterator. This optimization was applied to the level iterator, which prefetches all data through each of the block-based iterators.
## 10.8.0 (10/21/2025)
### New Features
+29 -146
View File
@@ -296,6 +296,28 @@ $(info $(shell $(CC) --version))
$(info $(shell $(CXX) --version))
endif
# ccache support
# Set USE_CCACHE=1 to enable ccache, or let it auto-detect
ifndef USE_CCACHE
CCACHE := $(shell which ccache 2>/dev/null)
ifneq ($(CCACHE),)
USE_CCACHE := 1
else
USE_CCACHE := 0
endif
endif
ifeq ($(USE_CCACHE), 1)
CCACHE := $(shell which ccache 2>/dev/null)
ifneq ($(CCACHE),)
$(info Using ccache: $(CCACHE))
CC := $(CCACHE) $(CC)
CXX := $(CCACHE) $(CXX)
else
$(warning ccache requested but not found in PATH)
endif
endif
missing_make_config_paths := $(shell \
grep "\./\S*\|/\S*" -o $(CURDIR)/make_config.mk | \
while read path; \
@@ -368,8 +390,6 @@ ifdef COMPILE_WITH_TSAN
# Turn off -pg when enabling TSAN testing, because that induces
# a link failure. TODO: find the root cause
PROFILING_FLAGS =
# LUA is not supported under TSAN
LUA_PATH =
# Limit keys for crash test under TSAN to avoid error:
# "ThreadSanitizer: DenseSlabAllocator overflow. Dying."
CRASH_TEST_EXT_ARGS += --max_key=1000000
@@ -446,83 +466,7 @@ else
PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
endif
# This provides a Makefile simulation of a Meta-internal folly integration.
# It is not validated for general use.
#
# USE_FOLLY links the build targets with libfolly.a. The latter could be
# built using 'make build_folly', or built externally and specified in
# the CXXFLAGS and EXTRA_LDFLAGS env variables. The build_detect_platform
# script tries to detect if an external folly dependency has been specified.
# If not, it exports FOLLY_PATH to the path of the installed Folly and
# dependency libraries.
#
# USE_FOLLY_LITE cherry picks source files from Folly to include in the
# RocksDB library. Its faster and has fewer dependencies on 3rd party
# libraries, but with limited functionality. For example, coroutine
# functionality is not available.
ifeq ($(USE_FOLLY),1)
ifeq ($(USE_FOLLY_LITE),1)
$(error Please specify only one of USE_FOLLY and USE_FOLLY_LITE)
endif
ifneq ($(strip $(FOLLY_PATH)),)
BOOST_PATH = $(shell (ls -d $(FOLLY_PATH)/../boost*))
DBL_CONV_PATH = $(shell (ls -d $(FOLLY_PATH)/../double-conversion*))
GFLAGS_PATH = $(shell (ls -d $(FOLLY_PATH)/../gflags*))
GLOG_PATH = $(shell (ls -d $(FOLLY_PATH)/../glog*))
LIBEVENT_PATH = $(shell (ls -d $(FOLLY_PATH)/../libevent*))
XZ_PATH = $(shell (ls -d $(FOLLY_PATH)/../xz*))
LIBSODIUM_PATH = $(shell (ls -d $(FOLLY_PATH)/../libsodium*))
FMT_PATH = $(shell (ls -d $(FOLLY_PATH)/../fmt*))
# For some reason, glog and fmt libraries are under either lib or lib64
GLOG_LIB_PATH = $(shell (ls -d $(GLOG_PATH)/lib*))
FMT_LIB_PATH = $(shell (ls -d $(FMT_PATH)/lib*))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
PLATFORM_CXXFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
else
PLATFORM_CCFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
PLATFORM_CXXFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
endif
# Add -ldl at the end as gcc resolves a symbol in a library by searching only in libraries specified later
# in the command line
PLATFORM_LDFLAGS += $(FOLLY_PATH)/lib/libfolly.a $(BOOST_PATH)/lib/libboost_context.a $(BOOST_PATH)/lib/libboost_filesystem.a $(BOOST_PATH)/lib/libboost_atomic.a $(BOOST_PATH)/lib/libboost_program_options.a $(BOOST_PATH)/lib/libboost_regex.a $(BOOST_PATH)/lib/libboost_system.a $(BOOST_PATH)/lib/libboost_thread.a $(DBL_CONV_PATH)/lib/libdouble-conversion.a $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2 $(LIBEVENT_PATH)/lib/libevent-2.1.so -ldl
PLATFORM_LDFLAGS += -Wl,-rpath=$(GFLAGS_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(LIBSODIUM_PATH)/lib -Wl,-rpath=$(LIBEVENT_PATH)/lib
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
endif
ifeq ($(USE_FOLLY_LITE),1)
# Path to the Folly source code and include files
FOLLY_DIR = ./third-party/folly
ifneq ($(strip $(BOOST_SOURCE_PATH)),)
BOOST_INCLUDE = $(shell (ls -d $(BOOST_SOURCE_PATH)/boost*/))
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -I$(BOOST_INCLUDE)
else
PLATFORM_CCFLAGS += -isystem $(BOOST_INCLUDE)
PLATFORM_CXXFLAGS += -isystem $(BOOST_INCLUDE)
endif
endif # BOOST_SOURCE_PATH
# AIX: pre-defined system headers are surrounded by an extern "C" block
ifeq ($(PLATFORM), OS_AIX)
PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
else
PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
endif
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
# TODO: fix linking with fbcode compiler config
PLATFORM_LDFLAGS += -lglog
endif
include folly.mk
ifdef TEST_CACHE_LINE_SIZE
PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
@@ -562,32 +506,6 @@ ifndef DISABLE_WARNING_AS_ERROR
endif
ifdef LUA_PATH
ifndef LUA_INCLUDE
LUA_INCLUDE=$(LUA_PATH)/include
endif
LUA_INCLUDE_FILE=$(LUA_INCLUDE)/lualib.h
ifeq ("$(wildcard $(LUA_INCLUDE_FILE))", "")
# LUA_INCLUDE_FILE does not exist
$(error Cannot find lualib.h under $(LUA_INCLUDE). Try to specify both LUA_PATH and LUA_INCLUDE manually)
endif
LUA_FLAGS = -I$(LUA_INCLUDE) -DLUA -DLUA_COMPAT_ALL
CFLAGS += $(LUA_FLAGS)
CXXFLAGS += $(LUA_FLAGS)
ifndef LUA_LIB
LUA_LIB = $(LUA_PATH)/lib/liblua.a
endif
ifeq ("$(wildcard $(LUA_LIB))", "") # LUA_LIB does not exist
$(error $(LUA_LIB) does not exist. Try to specify both LUA_PATH and LUA_LIB manually)
endif
EXEC_LDFLAGS += $(LUA_LIB)
endif
ifeq ($(NO_THREEWAY_CRC32C), 1)
CXXFLAGS += -DNO_THREEWAY_CRC32C
endif
@@ -658,8 +576,8 @@ ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
# TODO: add/support JNI headers
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
# Some headers like in port/ are platform-specific
DEV_HEADERS_TO_CHECK := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|lua/|range_tree/|secondary_index/')
PUBLIC_HEADERS_TO_CHECK := $(shell $(FIND) include/ -type f -name '*.h' | grep -E -v 'lua/')
DEV_HEADERS_TO_CHECK := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/')
PUBLIC_HEADERS_TO_CHECK := $(shell $(FIND) include/ -type f -name '*.h')
else
DEV_HEADERS_TO_CHECK :=
PUBLIC_HEADERS_TO_CHECK :=
@@ -683,6 +601,7 @@ am__v_CCH_1 =
%.h.pub: %.h # .h.pub not actually created, so re-checked on each invocation
$(AM_V_CCH) cd include/ && echo '#include "$(patsubst include/%,%,$<)"' | \
$(CXX) -std=$(or $(ROCKSDB_CXX_STANDARD),c++20) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
build_tools/check-public-header.sh $<
check-headers: $(HEADER_OK_FILES)
@@ -1742,9 +1661,6 @@ block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
index_builder_test: $(OBJ_DIR)/table/block_based/index_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
inlineskiplist_test: $(OBJ_DIR)/memtable/inlineskiplist_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2006,6 +1922,9 @@ blob_source_test: $(OBJ_DIR)/db/blob/blob_source_test.o $(TEST_LIBRARY) $(LIBRAR
blob_garbage_meter_test: $(OBJ_DIR)/db/blob/blob_garbage_meter_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
io_dispatcher_test: $(OBJ_DIR)/util/io_dispatcher_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
timer_test: $(OBJ_DIR)/util/timer_test.o $(TEST_LIBRARY) $(LIBRARY)
$(AM_LINK)
@@ -2498,42 +2417,6 @@ commit_prereq:
false # J=$(J) build_tools/precommit_checker.py unit clang_unit release clang_release tsan asan ubsan lite unit_non_shm
# $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
FOLLY_COMMIT_HASH = e95383b7c8b5b1e46cf47acf2f317d54f93c8268
# For public CI runs, checkout folly in a way that can build with RocksDB.
# This is mostly intended as a test-only simulation of Meta-internal folly
# integration.
checkout_folly:
if [ -e third-party/folly ]; then \
cd third-party/folly && ${GIT_COMMAND} fetch origin; \
else \
cd third-party && ${GIT_COMMAND} clone https://github.com/facebook/folly.git; \
fi
@# Pin to a particular version for public CI, so that PR authors don't
@# need to worry about folly breaking our integration. Update periodically
cd third-party/folly && git reset --hard $(FOLLY_COMMIT_HASH)
@# Apparently missing include
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
@# Warning-as-error on memcpy
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
@# const mismatch
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
build_folly:
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
if [ "$$FOLLY_INST_PATH" ]; then \
rm -rf $${FOLLY_INST_PATH}/../../*; \
else \
echo "Please run checkout_folly first"; \
false; \
fi
cd third-party/folly && \
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
# ---------------------------------------------------------------------------
# Build size testing
# ---------------------------------------------------------------------------
+3
View File
@@ -135,6 +135,9 @@ def generate_buck(repo_path, deps_map):
BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
# Add oncall("rocksdb_point_of_contact") at the top
BUCK.add_oncall("rocksdb_point_of_contact")
# rocksdb_lib
BUCK.add_library(
"rocksdb_lib",
+5
View File
@@ -45,6 +45,11 @@ class TARGETSBuilder:
self.total_bin = 0
self.total_test = 0
self.tests_cfg = ""
def add_oncall(self, oncall):
with open(self.path, "ab") as targets_file:
targets_file.write(targets_cfg.oncall_template.format(name=oncall).encode("utf-8"))
def add_library(
self,
+10 -3
View File
@@ -1,10 +1,12 @@
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
rocksdb_target_header_template = """# This file \100generated by:
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
# --> DO NOT EDIT MANUALLY <--
# This file is a Facebook-specific integration for buck builds, so can
# only be validated by Facebook employees.
# This file is a Meta-specific integration for buck builds, so can
# only be validated by Meta employees.
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
@@ -41,3 +43,8 @@ fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={ben
export_file_template = """
export_file(name = "{name}")
"""
oncall_template = """
oncall("{name}")
"""
+6 -3
View File
@@ -311,7 +311,8 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is gflags
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -320,7 +321,8 @@ EOF
EOF
then
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# Hack: don't link extra gflags assuming it comes with folly
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
# check if namespace is google
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
#include <gflags/gflags.h>
@@ -772,6 +774,7 @@ fi
if [ "$USE_FOLLY_LITE" ]; then
if [ "$FOLLY_DIR" ]; then
BOOST_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir boost`
FMT_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir fmt`
fi
fi
@@ -816,6 +819,7 @@ echo "FIND=$FIND" >> "$OUTPUT"
echo "WATCH=$WATCH" >> "$OUTPUT"
echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT"
echo "BOOST_SOURCE_PATH=$BOOST_SOURCE_PATH" >> "$OUTPUT"
echo "FMT_SOURCE_PATH=$FMT_SOURCE_PATH" >> "$OUTPUT"
# This will enable some related identifiers for the preprocessor
if test -n "$JEMALLOC"; then
@@ -827,7 +831,6 @@ fi
if test -n "$WITH_JEMALLOC_FLAG"; then
echo "WITH_JEMALLOC_FLAG=$WITH_JEMALLOC_FLAG" >> "$OUTPUT"
fi
echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
if test -n "$USE_FOLLY"; then
echo "USE_FOLLY=$USE_FOLLY" >> "$OUTPUT"
fi
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
#
# Check for some simple mistakes in public headers (on the command line)
# that should prevent commit or push
BAD=""
# Look for potential for ODR violations caused by public headers depending on
# build parameters that could vary between RocksDB build and application build.
# * Cases like ROCKSDB_NAMESPACE, and ROCKSDB_ASSERT_STATUS_CHECKED are
# intentional, hard to avoid. (We expect definitions to change and the user
# should also.)
# * Cases like _WIN32, OS_WIN, and __cplusplus are essentially ODR-safe.
# * Cases like
# #ifdef BLAH // ODR-SAFE
# #undef BLAH
# #endif
# that should not cause ODR violations can be exempted with the ODR-SAFE
# marker recognized here.
grep -nHE '^#if' -- "$@" | grep -vE 'ROCKSDB_NAMESPACE|ROCKSDB_ASSERT_STATUS_CHECKED|_WIN32|OS_WIN|ODR-SAFE|__cplusplus|ROCKSDB_DLL|ROCKSDB_LIBRARY_EXPORTS'
if [ "$?" != "1" ]; then
echo "^^^^^ #if in public API could cause an ODR violation."
echo " Add // ODR-SAFE if verified safe."
BAD=1
fi
if [ "$BAD" ]; then
exit 1
fi
-1
View File
@@ -19,4 +19,3 @@ BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/780c7a0f9cf0967961e69ad08e61cddd
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/624a2f8f6c93c3c1df8aa4a6255d8202631a6c80/fb/platform010/da39a3e
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/39579e8603b48b3540f8b0633f43adf29acccb8b/2.37/centos8-native/da39a3e
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/cd9cc656d49ecb53797ce4d055e49fde29fd57ff/3.19.0/platform010/76ebdda
LUA_BASE=/mnt/gvfs/third-party2/lua/363787fa5cac2a8aa20638909210443278fa138e/5.3.4/platform010/9079c97
+1 -9
View File
@@ -164,12 +164,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
VALGRIND_VER="$VALGRIND_BASE/bin/"
LUA_PATH="$LUA_BASE"
if test -z $PIC_BUILD; then
LUA_LIB=" $LUA_PATH/lib/liblua.a"
else
LUA_LIB=" $LUA_PATH/lib/liblua_pic.a"
fi
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
+1 -1
View File
@@ -172,4 +172,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
VALGRIND_VER="$VALGRIND_BASE/bin/"
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
+64
View File
@@ -148,6 +148,70 @@ else
echo "Checking format of uncommitted changes..."
fi
# Check for missing copyright in new files
echo "Checking for copyright headers in new files..."
# Get list of new files (added, not just modified)
if [ -z "$uncommitted_code" ]; then
# Post-commit: check files added since merge base
new_files=$(git diff --name-only --diff-filter=A "$FORMAT_UPSTREAM_MERGE_BASE" -- '*.h' '*.cc' '*.py' $EXCLUDE)
else
# Pre-commit: check staged new files
new_files=$(git diff --name-only --diff-filter=A --cached HEAD -- '*.h' '*.cc' '*.py' $EXCLUDE)
fi
if [ -n "$new_files" ]; then
files_missing_copyright=""
for file in $new_files; do
if [ -f "$file" ]; then
# Check if file is missing copyright
# For .py files, check for Python-style comment
# For .h and .cc files, check for C++-style comment
if [[ "$file" == *.py ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to Python file
temp_file=$(mktemp)
{
echo "# Copyright (c) Meta Platforms, Inc. and affiliates."
echo "# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)"
echo "# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
elif [[ "$file" == *.h ]] || [[ "$file" == *.cc ]]; then
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
files_missing_copyright="$files_missing_copyright $file"
# Add copyright header to C++ file
temp_file=$(mktemp)
{
echo "// Copyright (c) Meta Platforms, Inc. and affiliates. "
echo "// This source code is licensed under both the GPLv2 (found in the "
echo "// COPYING file in the root directory) and Apache 2.0 License "
echo "// (found in the LICENSE.Apache file in the root directory)."
echo
cat "$file"
} > "$temp_file"
mv "$temp_file" "$file"
echo "Added copyright header to $file"
fi
fi
fi
done
if [ -n "$files_missing_copyright" ]; then
echo "Copyright headers were added to new files."
else
echo "All new files have copyright headers."
fi
else
echo "No new files to check for copyright headers."
fi
if [ -z "$diffs" ]
then
echo "Nothing needs to be reformatted!"
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Pre-download packages with unreliable mirrors using fallback mirrors.
Reads package info from folly's getdeps manifest files.
"""
import sys
import os
import hashlib
import subprocess
import configparser
def sha256_file(path):
"""Calculate SHA256 hash of a file."""
h = hashlib.sha256()
try:
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
h.update(chunk)
return h.hexdigest()
except Exception:
return None
def parse_manifest(manifest_path):
"""Parse a getdeps manifest file to extract download info."""
config = configparser.ConfigParser()
try:
config.read(manifest_path)
if 'download' in config:
return {
'url': config['download'].get('url', ''),
'sha256': config['download'].get('sha256', ''),
}
except Exception:
pass
return None
def get_fallback_mirrors(url):
"""Get fallback mirror URLs for a given URL."""
# Fallback mirror patterns for known unreliable hosts
mirror_fallbacks = {
"ftp.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
"ftpmirror.gnu.org/gnu/": [
"https://mirrors.kernel.org/gnu/",
"https://ftpmirror.gnu.org/gnu/",
"https://ftp.gnu.org/gnu/",
],
}
for pattern, mirrors in mirror_fallbacks.items():
if pattern in url:
# Extract the path after the pattern
path_start = url.find(pattern) + len(pattern)
path = url[path_start:]
return [mirror + path for mirror in mirrors]
return [url] # No fallback, use original
def main():
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <download_dir> <cache_dir> <manifests_dir>")
sys.exit(1)
download_dir, cache_dir, manifests_dir = sys.argv[1], sys.argv[2], sys.argv[3]
# Packages known to have unreliable mirrors
packages_to_check = ["autoconf", "automake", "libtool"]
for package in packages_to_check:
manifest_path = os.path.join(manifests_dir, package)
if not os.path.exists(manifest_path):
continue
info = parse_manifest(manifest_path)
if not info or not info['url'] or not info['sha256']:
continue
# Determine filename from URL
url = info['url']
expected_sha256 = info['sha256']
url_filename = os.path.basename(url)
# getdeps uses format: {package}-{filename}
filename = f"{package}-{url_filename}"
filepath = os.path.join(download_dir, filename)
cache_path = os.path.join(cache_dir, filename)
# Check if already valid
if os.path.exists(filepath) and sha256_file(filepath) == expected_sha256:
print(f" {filename}: OK (already downloaded)")
continue
# Check cache
if os.path.exists(cache_path) and sha256_file(cache_path) == expected_sha256:
print(f" {filename}: OK (from cache)")
subprocess.run(['cp', cache_path, filepath], check=True)
continue
# Try fallback mirrors
mirrors = get_fallback_mirrors(url)
downloaded = False
for mirror_url in mirrors:
print(f" {filename}: trying {mirror_url}...")
try:
subprocess.run(['wget', '-q', '-O', filepath, mirror_url], check=True, timeout=120)
if sha256_file(filepath) == expected_sha256:
print(f" {filename}: OK (downloaded)")
subprocess.run(['cp', filepath, cache_path], check=False)
downloaded = True
break
else:
os.remove(filepath)
except Exception:
if os.path.exists(filepath):
os.remove(filepath)
if not downloaded:
print(f" {filename}: WARNING - all mirrors failed")
if __name__ == "__main__":
main()
-1
View File
@@ -101,6 +101,5 @@ get_lib_base benchmark LATEST platform010
get_lib_base kernel-headers fb platform010
get_lib_base binutils LATEST centos8-native
get_lib_base valgrind LATEST platform010
get_lib_base lua 5.3.4 platform010
git diff $OUTPUT
+1 -1
View File
@@ -119,7 +119,7 @@ DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
DEFINE_string(secondary_cache_uri, "",
"Full URI for creating a custom secondary cache object");
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
DEFINE_string(cache_type, "hyper_clock_cache", "Type of block cache.");
DEFINE_bool(use_jemalloc_no_dump_allocator, false,
"Whether to use JemallocNoDumpAllocator");
+1 -1
View File
@@ -644,7 +644,7 @@ using TypedHandle = SharedCache::TypedHandle;
TEST_P(CacheTest, SetCapacity) {
if (IsHyperClock()) {
// TODO: update test & code for limited supoort
// TODO: update test & code for limited support
ROCKSDB_GTEST_BYPASS(
"HyperClockCache doesn't support arbitrary capacity "
"adjustments.");
+355 -414
View File
File diff suppressed because it is too large. Load diff
+135 -69
View File
@@ -317,40 +317,89 @@ struct ClockHandle : public ClockHandleBasicData {
// | acquire counter | release counter | hit bit | state marker |
// -----------------------------------------------------------------------
// For reading or updating counters in meta word.
static constexpr uint8_t kCounterNumBits = 30;
static constexpr uint64_t kCounterMask = (uint64_t{1} << kCounterNumBits) - 1;
struct SlotMeta : public BitFields<uint64_t, SlotMeta> {
// For reading or updating counters in meta word.
static constexpr uint8_t kCounterNumBits = 30;
// Number of times the a reference has been acquired (or attempted)
// since last reset by eviction processing
using AcquireCounter =
UnsignedBitField<SlotMeta, kCounterNumBits, NoPrevBitField>;
// Number of times the a reference has been released (or attempted)
// since last reset by eviction processing
using ReleaseCounter =
UnsignedBitField<SlotMeta, kCounterNumBits, AcquireCounter>;
// Metadata bit in support of secondary cache
using HitFlag = BoolBitField<SlotMeta, ReleaseCounter>;
// Occupied means any state other than empty
using OccupiedFlag = BoolBitField<SlotMeta, HitFlag>;
// Shareable means the entry is reference counted (visible or invisible)
// (only set if also occupied)
using ShareableFlag = BoolBitField<SlotMeta, OccupiedFlag>;
// Visible is only set if also shareable (invisible can't be found by
// Lookup)
using VisibleFlag = BoolBitField<SlotMeta, ShareableFlag>;
static constexpr uint8_t kAcquireCounterShift = 0;
static constexpr uint64_t kAcquireIncrement = uint64_t{1}
<< kAcquireCounterShift;
static constexpr uint8_t kReleaseCounterShift = kCounterNumBits;
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
<< kReleaseCounterShift;
// Convenience functions
uint32_t GetAcquireCounter() const { return Get<AcquireCounter>(); }
void SetAcquireCounter(uint32_t val) { Set<AcquireCounter>(val); }
uint32_t GetReleaseCounter() const { return Get<ReleaseCounter>(); }
void SetReleaseCounter(uint32_t val) { Set<ReleaseCounter>(val); }
uint32_t GetRefcount() const {
return Get<AcquireCounter>() - Get<ReleaseCounter>();
}
bool GetHit() const { return Get<HitFlag>(); }
void SetHit(bool val) { Set<HitFlag>(val); }
// For setting the hit bit
static constexpr uint8_t kHitBitShift = 2U * kCounterNumBits;
static constexpr uint64_t kHitBitMask = uint64_t{1} << kHitBitShift;
// Some distinct states for the various state flags
bool IsEmpty() const {
bool rv = !Get<OccupiedFlag>();
if (rv) {
assert(!Get<ShareableFlag>());
assert(!Get<VisibleFlag>());
}
return rv;
}
// For reading or updating the state marker in meta word
static constexpr uint8_t kStateShift = kHitBitShift + 1;
bool IsUnderConstruction() const {
bool rv = Get<OccupiedFlag>() && !Get<ShareableFlag>();
if (rv) {
assert(!Get<VisibleFlag>());
}
return rv;
}
void SetUnderConstruction() {
Set<OccupiedFlag>(true);
Set<ShareableFlag>(false);
Set<VisibleFlag>(false);
}
// Bits contribution to state marker.
// Occupied means any state other than empty
static constexpr uint8_t kStateOccupiedBit = 0b100;
// Shareable means the entry is reference counted (visible or invisible)
// (only set if also occupied)
static constexpr uint8_t kStateShareableBit = 0b010;
// Visible is only set if also shareable
static constexpr uint8_t kStateVisibleBit = 0b001;
bool IsShareable() const { return Get<ShareableFlag>(); }
bool IsInvisible() const {
bool rv = Get<ShareableFlag>() && !Get<VisibleFlag>();
if (rv) {
assert(Get<OccupiedFlag>());
}
return rv;
}
void SetInvisible() {
Set<OccupiedFlag>(true);
Set<ShareableFlag>(true);
Set<VisibleFlag>(false);
}
// Complete state markers (not shifted into full word)
static constexpr uint8_t kStateEmpty = 0b000;
static constexpr uint8_t kStateConstruction = kStateOccupiedBit;
static constexpr uint8_t kStateInvisible =
kStateOccupiedBit | kStateShareableBit;
static constexpr uint8_t kStateVisible =
kStateOccupiedBit | kStateShareableBit | kStateVisibleBit;
bool IsVisible() const {
bool rv = Get<ShareableFlag>() && Get<VisibleFlag>();
if (rv) {
assert(Get<OccupiedFlag>());
}
return rv;
}
void SetVisible() {
Set<OccupiedFlag>(true);
Set<ShareableFlag>(true);
Set<VisibleFlag>(true);
}
};
// Constants for initializing the countdown clock. (Countdown clock is only
// in effect with zero refs, acquire counter == release counter, and in that
@@ -364,7 +413,7 @@ struct ClockHandle : public ClockHandleBasicData {
// TODO: make these coundown values tuning parameters for eviction?
// See above. Mutable for read reference counting.
mutable AcqRelAtomic<uint64_t> meta{};
mutable BitFieldsAtomic<SlotMeta> meta{};
}; // struct ClockHandle
class BaseClockTable {
@@ -431,9 +480,9 @@ class BaseClockTable {
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data) const;
#ifndef NDEBUG
// Acquire N references
void TEST_RefN(ClockHandle& handle, size_t n);
void TEST_RefN(ClockHandle& handle, uint32_t n);
// Helper for TEST_ReleaseN
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
void TEST_ReleaseNMinus1(ClockHandle* handle, uint32_t n);
#endif
private: // fns
@@ -489,13 +538,13 @@ class BaseClockTable {
// TODO: is this separation needed if we don't do background evictions?
ALIGN_AS(CACHE_LINE_SIZE)
// Number of elements in the table.
AcqRelAtomic<size_t> occupancy_{};
Atomic<size_t> occupancy_{};
// Memory usage by entries tracked by the cache (including standalone)
AcqRelAtomic<size_t> usage_{};
Atomic<size_t> usage_{};
// Part of usage by standalone entries (not in table)
AcqRelAtomic<size_t> standalone_usage_{};
Atomic<size_t> standalone_usage_{};
// Maximum total charge of all elements stored in the table.
// (Relaxed: eventual consistency/update is OK)
@@ -586,7 +635,7 @@ class FixedHyperClockTable : public BaseClockTable {
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
uint64_t initial_countdown, bool take_ref,
uint32_t initial_countdown, bool take_ref,
InsertState& state);
// Runs the clock eviction algorithm trying to reclaim at least
@@ -614,7 +663,7 @@ class FixedHyperClockTable : public BaseClockTable {
}
// Release N references
void TEST_ReleaseN(HandleImpl* handle, size_t n);
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
#endif
// The load factor p is a real number in (0, 1) such that at all
@@ -775,6 +824,7 @@ class AutoHyperClockTable : public BaseClockTable {
// chain--specifically the next entry in the chain.
// * The end of a chain is given a special "end" marker and refers back
// to the head of the chain.
// These decorated pointers use the NextWithShift bit field struct below.
//
// Why do we need shift on each pointer? To make Lookup wait-free, we need
// to be able to query a chain without missing anything, and preferably
@@ -794,47 +844,63 @@ class AutoHyperClockTable : public BaseClockTable {
// it is normal to see "under construction" entries on the chain, and it
// is not safe to read their hashed key without either a read reference
// on the entry or a rewrite lock on the chain.
struct NextWithShift : public BitFields<uint64_t, NextWithShift> {
// The "shift" associated with this decorated pointer (see description
// above).
using Shift = UnsignedBitField<NextWithShift, 6, NoPrevBitField>;
// Marker for the end of a chain. Must also (a) point back to the head of
// the chain (with end marker removed), and (b) set the LockedFlag
// (below), so that attempting to lock an empty chain has no effect (not
// needed, as the lock is only needed for removals).
using EndFlag = BoolBitField<NextWithShift, Shift>;
// Marker that some thread owning writes to the chain structure (except
// for inserts), but only if not an "end" pointer. Also called the
// "rewrite lock."
using LockedFlag = BoolBitField<NextWithShift, EndFlag>;
// The "next" associated with this decorated pointer, which is an index
// into the table's array_ (see description above).
using Next = UnsignedBitField<NextWithShift, 56, LockedFlag>;
// Marker in a "with_shift" head pointer for some thread owning writes
// to the chain structure (except for inserts), but only if not an
// "end" pointer. Also called the "rewrite lock."
static constexpr uint64_t kHeadLocked = uint64_t{1} << 7;
bool IsLocked() const { return Get<LockedFlag>(); }
bool IsEnd() const {
// End flag should imply locked flag
assert(!Get<EndFlag>() || Get<LockedFlag>());
return Get<EndFlag>();
}
bool IsLockedNotEnd() const {
// NOTE: helping GCC to optimize this simpler code:
// return IsLocked() && !IsEnd();
constexpr U kEndFlag = U{1} << EndFlag::kBitOffset;
constexpr U kLockedFlag = U{1} << LockedFlag::kBitOffset;
return (underlying & (kEndFlag | kLockedFlag)) == kLockedFlag;
}
auto GetNext() const { return Get<Next>(); }
auto GetShift() const { return Get<Shift>(); }
// Marker in a "with_shift" pointer for the end of a chain. Must also
// point back to the head of the chain (with end marker removed).
// Also includes the "locked" bit so that attempting to lock an empty
// chain has no effect (not needed, as the lock is only needed for
// removals).
static constexpr uint64_t kNextEndFlags = (uint64_t{1} << 6) | kHeadLocked;
static NextWithShift Make(size_t next, int shift) {
return NextWithShift{}.With<Next>(next).With<Shift>(
static_cast<uint8_t>(shift));
}
static inline bool IsEnd(uint64_t next_with_shift) {
// Assuming certain values never used, suffices to check this one bit
constexpr auto kCheckBit = kNextEndFlags ^ kHeadLocked;
return next_with_shift & kCheckBit;
}
// Bottom bits to right shift away to get an array index from a
// "with_shift" pointer.
static constexpr int kNextShift = 8;
// A bit mask for the "shift" associated with each "with_shift" pointer.
// Always bottommost bits.
static constexpr int kShiftMask = 63;
static NextWithShift MakeEnd(size_t next, int shift) {
return Make(next, shift).With<EndFlag>(true).With<LockedFlag>(true);
}
};
// A marker for head_next_with_shift that indicates this HandleImpl is
// heap allocated (standalone) rather than in the table.
static constexpr uint64_t kStandaloneMarker = UINT64_MAX;
static constexpr NextWithShift kStandaloneMarker{UINT64_MAX};
// A marker for head_next_with_shift indicating the head is not yet part
// of the usable table, or for chain_next_with_shift indicating that the
// entry is not present or is not yet part of a chain (must not be
// "shareable" state).
static constexpr uint64_t kUnusedMarker = 0;
static constexpr NextWithShift kUnusedMarker{0};
// See above. The head pointer is logically independent of the rest of
// the entry, including the chain next pointer.
AcqRelAtomic<uint64_t> head_next_with_shift{kUnusedMarker};
AcqRelAtomic<uint64_t> chain_next_with_shift{kUnusedMarker};
BitFieldsAtomic<NextWithShift> head_next_with_shift{kUnusedMarker};
BitFieldsAtomic<NextWithShift> chain_next_with_shift{kUnusedMarker};
// For supporting CreateStandalone and some fallback cases.
inline bool IsStandalone() const {
@@ -880,7 +946,7 @@ class AutoHyperClockTable : public BaseClockTable {
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
uint64_t initial_countdown, bool take_ref,
uint32_t initial_countdown, bool take_ref,
InsertState& state);
// Runs the clock eviction algorithm trying to reclaim at least
@@ -908,7 +974,7 @@ class AutoHyperClockTable : public BaseClockTable {
}
// Release N references
void TEST_ReleaseN(HandleImpl* handle, size_t n);
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
#endif
// Maximum ratio of number of occupied slots to number of usable slots. The
@@ -990,7 +1056,7 @@ class AutoHyperClockTable : public BaseClockTable {
// To maximize parallelization of Grow() operations, this field is only
// updated opportunistically after Grow() operations and in DoInsert() where
// it is found to be out-of-date. See CatchUpLengthInfoNoWait().
AcqRelAtomic<uint64_t> length_info_;
Atomic<uint64_t> length_info_;
// An already-computed version of the usable length times the max load
// factor. Could be slightly out of date but GrowIfNeeded()/Grow() handle
@@ -1113,8 +1179,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
return table_.TEST_MutableOccupancyLimit();
}
// Acquire/release N references
void TEST_RefN(HandleImpl* handle, size_t n);
void TEST_ReleaseN(HandleImpl* handle, size_t n);
void TEST_RefN(HandleImpl* handle, uint32_t n);
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
#endif
private: // data
+7 -7
View File
@@ -1503,7 +1503,7 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
/*context*/ this, Cache::Priority::LOW);
ASSERT_EQ(handle1, nullptr);
// k1 promotion can fail with strict_capacit_limit=true, but Lookup still
// k1 promotion can fail with strict_capacity_limit=true, but Lookup still
// succeeds using a standalone handle
handle1 = cache->Lookup(k1.AsSlice(), GetHelper(),
/*context*/ this, Cache::Priority::LOW);
@@ -1680,7 +1680,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
// After Flush is successful, RocksDB will do the paranoid check for the new
// SST file. Meta blocks are always cached in the block cache and they
// will not be evicted. When block_2 is cache miss and read out, it is
// inserted to the block cache. Thefore, block_1 is evicted from block
// inserted to the block cache. Therefore, block_1 is evicted from block
// cache and successfully inserted to the secondary cache. Here are 2
// lookups in the secondary cache for block_1 and block_2.
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
@@ -1721,7 +1721,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
// This Get needs to access block_1, since block_1 is not in block cache
// there is one econdary cache lookup. Then, block_1 is cached in the
// there is one secondary cache lookup. Then, block_1 is cached in the
// block cache.
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
@@ -1785,7 +1785,7 @@ TEST_P(DBSecondaryCacheTest, NoSecondaryCacheInsertion) {
std::string v = Get(Key(0));
ASSERT_EQ(1000, v.size());
// Since the block cache is large enough, all the blocks are cached. we
// do not need to lookup the seondary cache.
// do not need to lookup the secondary cache.
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
@@ -2150,7 +2150,7 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) {
ASSERT_OK(Flush());
Compact("a", "z");
// do th eread for all the key value pairs, so all the blocks should be in
// do the read for all the key value pairs, so all the blocks should be in
// cache
uint32_t start_insert = cache->GetInsertCount();
uint32_t start_lookup = cache->GetLookupcount();
@@ -2464,7 +2464,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
// Check the data in first block. Cache miss, direclty read from SST file.
// Check the data in first block. Cache miss, directly read from SST file.
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
@@ -2598,7 +2598,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
}
// Two DB test. We create 2 DBs sharing the same block cache and secondary
// cache. We diable the secondary cache option for DB2.
// cache. We disable the secondary cache option for DB2.
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
+5 -1
View File
@@ -8,7 +8,7 @@ DB_STRESS_CMD?=./db_stress
include common.mk
CRASHTEST_MAKE=$(MAKE) -f crash_test.mk
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)'
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)' --destroy_db_initially=1
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
crash_test_with_wc_txn crash_test_with_wp_txn crash_test_with_wup_txn \
@@ -34,6 +34,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
whitebox_crash_test_with_optimistic_txn \
whitebox_crash_test_with_tiered_storage \
crash_test_db_cleanup \
crash_test: $(DB_STRESS_CMD)
# Do not parallelize
@@ -161,6 +162,9 @@ whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
crash_test_db_cleanup: $(DB_STRESS_CMD)
$(DB_STRESS_CMD) --delete_dir_and_exit=$(TEST_TMPDIR)
# Old names DEPRECATED
crash_test_with_txn: crash_test_with_wc_txn
whitebox_crash_test_with_txn: whitebox_crash_test_with_wc_txn
+23 -22
View File
@@ -67,6 +67,16 @@ BlobFileBuilder::BlobFileBuilder(
min_blob_size_(mutable_cf_options->min_blob_size),
blob_file_size_(mutable_cf_options->blob_file_size),
blob_compression_type_(mutable_cf_options->blob_compression_type),
// TODO: support most CompressionOptions with a new CF option
// blob_compression_opts
// TODO with schema change: support custom compression manager and options
// such as max_compressed_bytes_per_kb
// NOTE: returns nullptr for kNoCompression
blob_compressor_(GetBuiltinV2CompressionManager()->GetCompressor(
CompressionOptions{}, blob_compression_type_)),
blob_compressor_wa_(blob_compressor_
? blob_compressor_->ObtainWorkingArea()
: Compressor::ManagedWorkingArea{}),
prepopulate_blob_cache_(mutable_cf_options->prepopulate_blob_cache),
file_options_(file_options),
write_options_(write_options),
@@ -113,7 +123,7 @@ Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
}
Slice blob = value;
std::string compressed_blob;
GrowableBuffer compressed_blob;
{
const Status s = CompressBlobIfNeeded(&blob, &compressed_blob);
@@ -254,36 +264,27 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
}
Status BlobFileBuilder::CompressBlobIfNeeded(
Slice* blob, std::string* compressed_blob) const {
Slice* blob, GrowableBuffer* compressed_blob) const {
assert(blob);
assert(compressed_blob);
assert(compressed_blob->empty());
assert(immutable_options_);
if (blob_compression_type_ == kNoCompression) {
if (!blob_compressor_) {
assert(blob_compression_type_ == kNoCompression);
return Status::OK();
}
assert(blob_compression_type_ != kNoCompression);
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
CompressionOptions opts;
CompressionContext context(blob_compression_type_, opts);
// WART: always stored as compressed even when that increases the size.
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
blob_compression_type_);
constexpr uint32_t compression_format_version = 2;
bool success = false;
{
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
BLOB_DB_COMPRESSION_MICROS);
success = OLD_CompressData(*blob, info, compression_format_version,
compressed_blob);
}
if (!success) {
return Status::Corruption("Error compressing blob");
Status s;
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
BLOB_DB_COMPRESSION_MICROS);
s = LegacyForceBuiltinCompression(*blob_compressor_, &blob_compressor_wa_,
*blob, compressed_blob);
if (!s.ok()) {
return s;
}
*blob = Slice(*compressed_blob);
+6 -1
View File
@@ -10,12 +10,14 @@
#include <string>
#include <vector>
#include "rocksdb/advanced_compression.h"
#include "rocksdb/advanced_options.h"
#include "rocksdb/compression_type.h"
#include "rocksdb/env.h"
#include "rocksdb/options.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/types.h"
#include "util/aligned_buffer.h"
namespace ROCKSDB_NAMESPACE {
@@ -76,7 +78,8 @@ class BlobFileBuilder {
private:
bool IsBlobFileOpen() const;
Status OpenBlobFileIfNeeded();
Status CompressBlobIfNeeded(Slice* blob, std::string* compressed_blob) const;
Status CompressBlobIfNeeded(Slice* blob,
GrowableBuffer* compressed_blob) const;
Status WriteBlobToFile(const Slice& key, const Slice& blob,
uint64_t* blob_file_number, uint64_t* blob_offset);
Status CloseBlobFile();
@@ -91,6 +94,8 @@ class BlobFileBuilder {
uint64_t min_blob_size_;
uint64_t blob_file_size_;
CompressionType blob_compression_type_;
std::unique_ptr<Compressor> blob_compressor_;
mutable Compressor::ManagedWorkingArea blob_compressor_wa_;
PrepopulateBlobCache prepopulate_blob_cache_;
const FileOptions* file_options_;
const WriteOptions* write_options_;
+7 -6
View File
@@ -457,11 +457,12 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
[](void* arg) {
bool* ret = static_cast<bool*>(arg);
*ret = false;
});
SyncPoint::GetInstance()->SetCallBack(
"LegacyForceBuiltinCompression:TamperWithStatus", [](void* arg) {
Status* ret = static_cast<Status*>(arg);
ASSERT_OK(*ret);
*ret = Status::Corruption("Tampered result");
});
SyncPoint::GetInstance()->EnableProcessing();
constexpr char key[] = "1";
@@ -469,7 +470,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
std::string blob_index;
ASSERT_TRUE(builder.Add(key, value, &blob_index).IsCorruption());
ASSERT_EQ(builder.Add(key, value, &blob_index).code(), Status::kCorruption);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
+37 -25
View File
@@ -17,10 +17,10 @@
#include "rocksdb/file_system.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "table/format.h"
#include "table/multiget_context.h"
#include "test_util/sync_point.h"
#include "util/compression.h"
#include "util/crc32c.h"
#include "util/stop_watch.h"
namespace ROCKSDB_NAMESPACE {
@@ -69,9 +69,16 @@ Status BlobFileReader::Create(
}
}
blob_file_reader->reset(
new BlobFileReader(std::move(file_reader), file_size, compression_type,
immutable_options.clock, statistics));
std::shared_ptr<Decompressor> decompressor;
if (compression_type != kNoCompression) {
// The blob format has always used compression format 2
decompressor = GetBuiltinV2CompressionManager()->GetDecompressorOptimizeFor(
compression_type);
}
blob_file_reader->reset(new BlobFileReader(
std::move(file_reader), file_size, compression_type,
std::move(decompressor), immutable_options.clock, statistics));
return Status::OK();
}
@@ -282,11 +289,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
BlobFileReader::BlobFileReader(
std::unique_ptr<RandomAccessFileReader>&& file_reader, uint64_t file_size,
CompressionType compression_type, SystemClock* clock,
CompressionType compression_type,
std::shared_ptr<Decompressor> decompressor, SystemClock* clock,
Statistics* statistics)
: file_reader_(std::move(file_reader)),
file_size_(file_size),
compression_type_(compression_type),
decompressor_(std::move(decompressor)),
clock_(clock),
statistics_(statistics) {
assert(file_reader_);
@@ -375,8 +384,9 @@ Status BlobFileReader::GetBlob(
const Slice value_slice(record_slice.data() + adjustment, value_size);
{
const Status s = UncompressBlobIfNeeded(
value_slice, compression_type, allocator, clock_, statistics_, result);
const Status s = UncompressBlobIfNeeded(value_slice, compression_type,
decompressor_.get(), allocator,
clock_, statistics_, result);
if (!s.ok()) {
return s;
}
@@ -524,9 +534,9 @@ void BlobFileReader::MultiGetBlob(
// Uncompress blob if needed
Slice value_slice(record_slice.data() + adjustments[i], req->len);
*req->status =
UncompressBlobIfNeeded(value_slice, compression_type_, allocator,
clock_, statistics_, &blob_reqs[i].second);
*req->status = UncompressBlobIfNeeded(
value_slice, compression_type_, decompressor_.get(), allocator, clock_,
statistics_, &blob_reqs[i].second);
if (req->status->ok()) {
total_bytes += record_slice.size();
}
@@ -583,8 +593,8 @@ Status BlobFileReader::VerifyBlob(const Slice& record_slice,
Status BlobFileReader::UncompressBlobIfNeeded(
const Slice& value_slice, CompressionType compression_type,
MemoryAllocator* allocator, SystemClock* clock, Statistics* statistics,
std::unique_ptr<BlobContents>* result) {
Decompressor* decompressor, MemoryAllocator* allocator, SystemClock* clock,
Statistics* statistics, std::unique_ptr<BlobContents>* result) {
assert(result);
if (compression_type == kNoCompression) {
@@ -593,31 +603,33 @@ Status BlobFileReader::UncompressBlobIfNeeded(
return Status::OK();
}
UncompressionContext context(compression_type);
UncompressionInfo info(context, UncompressionDict::GetEmptyDict(),
compression_type);
assert(decompressor);
size_t uncompressed_size = 0;
constexpr uint32_t compression_format_version = 2;
Decompressor::Args args;
args.compression_type = compression_type;
args.compressed_data = value_slice;
CacheAllocationPtr output;
Status s = decompressor->ExtractUncompressedSize(args);
if (!s.ok()) {
return Status::Corruption(s.ToString());
}
CacheAllocationPtr output = AllocateBlock(args.uncompressed_size, allocator);
{
PERF_TIMER_GUARD(blob_decompress_time);
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
output = OLD_UncompressData(info, value_slice.data(), value_slice.size(),
&uncompressed_size, compression_format_version,
allocator);
s = decompressor->DecompressBlock(args, output.get());
}
TEST_SYNC_POINT_CALLBACK(
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", &output);
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", &s);
if (!output) {
return Status::Corruption("Unable to uncompress blob");
if (!s.ok()) {
return Status::Corruption(s.ToString());
}
result->reset(new BlobContents(std::move(output), uncompressed_size));
result->reset(new BlobContents(std::move(output), args.uncompressed_size));
return Status::OK();
}
+5 -1
View File
@@ -10,6 +10,7 @@
#include "db/blob/blob_read_request.h"
#include "file/random_access_file_reader.h"
#include "rocksdb/advanced_compression.h"
#include "rocksdb/compression_type.h"
#include "rocksdb/rocksdb_namespace.h"
#include "util/autovector.h"
@@ -64,7 +65,8 @@ class BlobFileReader {
private:
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
uint64_t file_size, CompressionType compression_type,
SystemClock* clock, Statistics* statistics);
std::shared_ptr<Decompressor> decompressor, SystemClock* clock,
Statistics* statistics);
static Status OpenFile(const ImmutableOptions& immutable_options,
const FileOptions& file_opts,
@@ -96,6 +98,7 @@ class BlobFileReader {
static Status UncompressBlobIfNeeded(const Slice& value_slice,
CompressionType compression_type,
Decompressor* decompressor,
MemoryAllocator* allocator,
SystemClock* clock,
Statistics* statistics,
@@ -104,6 +107,7 @@ class BlobFileReader {
std::unique_ptr<RandomAccessFileReader> file_reader_;
uint64_t file_size_;
CompressionType compression_type_;
std::shared_ptr<Decompressor> decompressor_;
SystemClock* clock_;
Statistics* statistics_;
};
+15 -18
View File
@@ -65,7 +65,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
ASSERT_OK(blob_log_writer.WriteHeader(WriteOptions(), header));
std::vector<std::string> compressed_blobs(num);
std::vector<GrowableBuffer> compressed_blobs(num);
std::vector<Slice> blobs_to_write(num);
if (kNoCompression == compression) {
for (size_t i = 0; i < num; ++i) {
@@ -73,16 +73,13 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
blob_sizes[i] = blobs[i].size();
}
} else {
CompressionOptions opts;
CompressionContext context(compression, opts);
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
compression);
constexpr uint32_t compression_format_version = 2;
auto compressor =
GetBuiltinV2CompressionManager()->GetCompressor({}, compression);
for (size_t i = 0; i < num; ++i) {
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
ASSERT_OK(LegacyForceBuiltinCompression(*compressor,
/*working_area=*/nullptr,
blobs[i], &compressed_blobs[i]));
blobs_to_write[i] = compressed_blobs[i];
blob_sizes[i] = compressed_blobs[i].size();
}
@@ -809,11 +806,10 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
SyncPoint::GetInstance()->SetCallBack(
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
CacheAllocationPtr* const output =
static_cast<CacheAllocationPtr*>(arg);
assert(output);
auto* result = static_cast<Status*>(arg);
assert(result);
output->reset();
*result = Status::Corruption("Injected result");
});
SyncPoint::GetInstance()->EnableProcessing();
@@ -824,11 +820,12 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
std::unique_ptr<BlobContents> value;
uint64_t bytes_read = 0;
ASSERT_TRUE(reader
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
kSnappyCompression, prefetch_buffer, allocator,
&value, &bytes_read)
.IsCorruption());
ASSERT_EQ(reader
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
kSnappyCompression, prefetch_buffer, allocator,
&value, &bytes_read)
.code(),
Status::Code::kCorruption);
ASSERT_EQ(value, nullptr);
ASSERT_EQ(bytes_read, 0);
+6 -9
View File
@@ -67,7 +67,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
ASSERT_OK(blob_log_writer.WriteHeader(WriteOptions(), header));
std::vector<std::string> compressed_blobs(num);
std::vector<GrowableBuffer> compressed_blobs(num);
std::vector<Slice> blobs_to_write(num);
if (kNoCompression == compression) {
for (size_t i = 0; i < num; ++i) {
@@ -75,16 +75,13 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
blob_sizes[i] = blobs[i].size();
}
} else {
CompressionOptions opts;
CompressionContext context(compression, opts);
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
compression);
constexpr uint32_t compression_format_version = 2;
auto compressor =
GetBuiltinV2CompressionManager()->GetCompressor({}, compression);
for (size_t i = 0; i < num; ++i) {
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
&compressed_blobs[i]));
ASSERT_OK(LegacyForceBuiltinCompression(*compressor,
/*working_area=*/nullptr,
blobs[i], &compressed_blobs[i]));
blobs_to_write[i] = compressed_blobs[i];
blob_sizes[i] = compressed_blobs[i].size();
}
+13
View File
@@ -56,6 +56,18 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
return tboptions.moptions.table_factory->NewTableBuilder(tboptions, file);
}
void ExtractTimestampFromTableProperties(const TableProperties& tp,
FileMetaData* meta) {
auto min_ts_iter = tp.user_collected_properties.find("rocksdb.timestamp_min");
if (min_ts_iter != tp.user_collected_properties.end()) {
meta->min_timestamp = min_ts_iter->second;
}
auto max_ts_iter = tp.user_collected_properties.find("rocksdb.timestamp_max");
if (max_ts_iter != tp.user_collected_properties.end()) {
meta->max_timestamp = max_ts_iter->second;
}
}
Status BuildTable(
const std::string& dbname, VersionSet* versions,
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
@@ -355,6 +367,7 @@ Status BuildTable(
assert(meta->fd.GetFileSize() > 0);
tp = builder
->GetTableProperties(); // refresh now that builder is finished
ExtractTimestampFromTableProperties(tp, meta);
if (memtable_payload_bytes != nullptr &&
memtable_garbage_bytes != nullptr) {
const CompactionIterationStats& ci_stats = c_iter.iter_stats();
+7
View File
@@ -41,6 +41,13 @@ class BlobFileCompletionCallback;
TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
WritableFileWriter* file);
// Extract min/max timestamps from table properties and populate FileMetaData.
// This is used by both flush (BuildTable) and compaction (CompactionOutputs)
// to populate timestamp range in FileMetaData from the TimestampTableProperties
// collector output.
void ExtractTimestampFromTableProperties(const TableProperties& tp,
FileMetaData* meta);
// Build a Table file from the contents of *iter. The generated file
// will be named according to number specified in meta. On success, the rest of
// *meta will be filled with metadata about the generated table.
+1017 -131
View File
File diff suppressed because it is too large. Load diff
+648
View File
@@ -722,6 +722,88 @@ static void LoadAndCheckLatestOptions(const char* db_name, rocksdb_env_t* env,
num_column_families);
}
// Global state for tracking remote compaction calls
typedef struct {
int schedule_called;
int wait_called;
int cancel_called;
char last_scheduled_job_id[256];
char last_db_name[256];
} RemoteCompactionState;
// Schedule callback - gets called when compaction is scheduled
static rocksdb_compactionservice_scheduleresponse_t* RemoteCompactionSchedule(
void* state, const rocksdb_compactionservice_jobinfo_t* info,
const char* input, size_t input_len) {
(void)input;
(void)input_len;
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
rcs->schedule_called++;
// Extract job info
size_t db_name_len;
const char* db_name =
rocksdb_compactionservice_jobinfo_t_get_db_name(info, &db_name_len);
memcpy(rcs->last_db_name, db_name, db_name_len);
rcs->last_db_name[db_name_len] = '\0';
// Generate a job ID
snprintf(rcs->last_scheduled_job_id, sizeof(rcs->last_scheduled_job_id),
"job-%d", rcs->schedule_called);
// Create response with success status
char* err = NULL;
rocksdb_compactionservice_scheduleresponse_t* response =
rocksdb_compactionservice_scheduleresponse_create(
rcs->last_scheduled_job_id,
rocksdb_compactionservice_jobstatus_success, &err);
if (err) {
free(err);
}
return response;
}
// Wait callback - simulates waiting for remote compaction to complete
static int RemoteCompactionWait(void* state, const char* scheduled_job_id,
char** result, size_t* result_len) {
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
rcs->wait_called++;
if (strcmp(scheduled_job_id, rcs->last_scheduled_job_id) != 0) {
return rocksdb_compactionservice_jobstatus_failure;
}
// For testing purposes, return kUseLocal to cause RocksDB to fall back to
// local compaction. This tests the callback mechanism without needing a fully
// serialized result. In a real scenario, this would communicate with a remote
// worker that calls rocksdb_open_and_compact() and returns a properly
// serialized CompactionServiceResult
*result = NULL;
*result_len = 0;
return rocksdb_compactionservice_jobstatus_use_local;
}
// Cancel callback - cancels pending jobs
static void RemoteCompactionCancel(void* state) {
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
rcs->cancel_called++;
}
// Destructor callback
static void RemoteCompactionDestroy(void* state) { (void)state; }
// NULL schedule callback for testing failure handling
static rocksdb_compactionservice_scheduleresponse_t* NullSchedule(
void* state, const rocksdb_compactionservice_jobinfo_t* info,
const char* input, size_t input_len) {
(void)state;
(void)info;
(void)input;
(void)input_len;
return NULL; // Return NULL to simulate failure
}
int main(int argc, char** argv) {
(void)argc;
(void)argv;
@@ -1255,6 +1337,70 @@ int main(int argc, char** argv) {
rocksdb_writebatch_destroy(wb);
}
StartPhase("writebatch_vectors_cf");
{
const char* cf_name = "wb_vectors_cf";
rocksdb_column_family_handle_t* wb_cf =
rocksdb_create_column_family(db, options, cf_name, &err);
CheckNoError(err);
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
// Test putv_cf: concatenates multiple slices into a single key/value
const char* put_keys[2] = {"k", "ey"};
const size_t put_key_sizes[2] = {1, 2};
const char* put_vals[3] = {"v", "a", "l"};
const size_t put_val_sizes[3] = {1, 1, 1};
rocksdb_writebatch_putv_cf(wb, wb_cf, 2, put_keys, put_key_sizes, 3,
put_vals, put_val_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// putv_cf concatenates: key="k"+"ey"="key", value="v"+"a"+"l"="val"
CheckGetCF(db, roptions, wb_cf, "key", "val");
CheckGetCF(db, roptions, wb_cf, "k", NULL);
CheckGetCF(db, roptions, wb_cf, "ey", NULL);
// Test deletev_cf: concatenates multiple slices for key
rocksdb_writebatch_clear(wb);
const char* del_keys[2] = {"k", "ey"};
const size_t del_key_sizes[2] = {1, 2};
rocksdb_writebatch_deletev_cf(wb, wb_cf, 2, del_keys, del_key_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "key", NULL);
// Test delete_rangev_cf: concatenates slices for range deletion
rocksdb_writebatch_clear(wb);
rocksdb_writebatch_put_cf(wb, wb_cf, "a", 1, "1", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "b", 1, "2", 1);
rocksdb_writebatch_put_cf(wb, wb_cf, "c", 1, "3", 1);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
CheckGetCF(db, roptions, wb_cf, "a", "1");
CheckGetCF(db, roptions, wb_cf, "b", "2");
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_clear(wb);
const char* range_start[2] = {"a", ""}; // "a" + "" = "a"
const size_t range_start_sizes[2] = {1, 0};
const char* range_end[2] = {"c", ""};
const size_t range_end_sizes[2] = {1, 0};
rocksdb_writebatch_delete_rangev_cf(wb, wb_cf, 2, range_start,
range_start_sizes, range_end,
range_end_sizes);
rocksdb_write(db, woptions, wb, &err);
CheckNoError(err);
// Range [a, c) should delete "a" and "b", but not "c"
CheckGetCF(db, roptions, wb_cf, "a", NULL);
CheckGetCF(db, roptions, wb_cf, "b", NULL);
CheckGetCF(db, roptions, wb_cf, "c", "3");
rocksdb_writebatch_destroy(wb);
rocksdb_drop_column_family(db, wb_cf, &err);
CheckNoError(err);
rocksdb_column_family_handle_destroy(wb_cf);
}
StartPhase("writebatch_vectors");
{
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
@@ -1420,6 +1566,43 @@ int main(int argc, char** argv) {
rocksdb_iter_destroy(iter);
}
StartPhase("iter_slice");
{
// Test the new slice-based iterator API for better performance
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
CheckCondition(!rocksdb_iter_valid(iter));
rocksdb_iter_seek_to_first(iter);
CheckCondition(rocksdb_iter_valid(iter));
// Test rocksdb_iter_key_slice
rocksdb_slice_t key_slice = rocksdb_iter_key_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
// Test rocksdb_iter_value_slice
rocksdb_slice_t value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("c", value_slice.data, value_slice.size);
// Move to next entry and test again
rocksdb_iter_next(iter);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("foo", key_slice.data, key_slice.size);
CheckEqual("hello", value_slice.data, value_slice.size);
// Test seeking with slice API
rocksdb_iter_seek(iter, "b", 1);
CheckCondition(rocksdb_iter_valid(iter));
key_slice = rocksdb_iter_key_slice(iter);
value_slice = rocksdb_iter_value_slice(iter);
CheckEqual("box", key_slice.data, key_slice.size);
CheckEqual("c", value_slice.data, value_slice.size);
rocksdb_iter_get_error(iter, &err);
CheckNoError(err);
rocksdb_iter_destroy(iter);
}
StartPhase("wbwi_iter");
{
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, roptions);
@@ -1505,6 +1688,53 @@ int main(int argc, char** argv) {
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
}
StartPhase("zero_copy_get_pinned_v2");
{
// Test new zero-copy get functions
// Test rocksdb_get_pinned_v2
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_v2(db, roptions, "foo", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("hello", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_v2(db, roptions, "notfound", 8, &err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer(
db, roptions, "foo", 3, buffer, sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 5);
CheckCondition(memcmp(buffer, "hello", 5) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer(db, roptions, "foo", 3, buffer,
2, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 5); // Should still report actual size
// Test with non-existent key
success = rocksdb_get_into_buffer(db, roptions, "notfound", 8, buffer,
sizeof(buffer), &val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0);
CheckCondition(found == 0);
}
StartPhase("pin_get");
{
CheckPinGet(db, roptions, "box", "c");
@@ -1922,6 +2152,55 @@ int main(int argc, char** argv) {
rocksdb_flush_wal(db, 1, &err);
CheckNoError(err);
// Test column family handle get name
{
size_t name_len;
char* cf_name =
rocksdb_column_family_handle_get_name(handles[1], &name_len);
CheckCondition(name_len == 3);
CheckCondition(memcmp(cf_name, "cf1", 3) == 0);
rocksdb_free(cf_name);
}
// Test zero-copy get with column families
{
rocksdb_pinnable_handle_t* handle =
rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "box", 3, &err);
CheckNoError(err);
CheckCondition(handle != NULL);
size_t val_len;
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
CheckEqual("c", val, val_len);
rocksdb_pinnable_handle_destroy(handle);
// Test with non-existent key
handle = rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "notfound", 8,
&err);
CheckNoError(err);
CheckCondition(handle == NULL);
// Test rocksdb_get_into_buffer_cf
char buffer[100];
unsigned char found;
unsigned char success = rocksdb_get_into_buffer_cf(
db, roptions, handles[1], "buff", 4, buffer, sizeof(buffer), &val_len,
&found, &err);
CheckNoError(err);
CheckCondition(success == 1);
CheckCondition(found == 1);
CheckCondition(val_len == 7);
CheckCondition(memcmp(buffer, "rocksdb", 7) == 0);
// Test with buffer too small
success = rocksdb_get_into_buffer_cf(db, roptions, handles[1], "buff", 4,
buffer, 3, // Buffer too small
&val_len, &found, &err);
CheckNoError(err);
CheckCondition(success == 0); // Should fail due to small buffer
CheckCondition(found == 1);
CheckCondition(val_len == 7); // Should still report actual size
}
// Test WriteBatchWithIndex iteration with Column Family
rocksdb_writebatch_wi_t* wbwi = rocksdb_writebatch_wi_create(0, true);
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "boat", 4, "row",
@@ -1998,6 +2277,74 @@ int main(int argc, char** argv) {
}
}
{
// Test rocksdb_batched_multi_get_cf_slice for better performance
// Build rocksdb_slice_t array directly to avoid conversion overhead
rocksdb_slice_t batched_key_slices[4];
batched_key_slices[0].data = "box";
batched_key_slices[0].size = 3;
batched_key_slices[1].data = "buff";
batched_key_slices[1].size = 4;
batched_key_slices[2].data = "barfooxx";
batched_key_slices[2].size = 8;
batched_key_slices[3].data = "box";
batched_key_slices[3].size = 3;
const char* expected_value[4] = {"c", "rocksdb", NULL, "c"};
char* batched_errs[4];
rocksdb_pinnableslice_t* pvals[4];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 4,
batched_key_slices, pvals,
batched_errs, false);
const char* val;
size_t val_len;
for (i = 0; i < 4; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
// Test rocksdb_batched_multi_get_cf_slice with sorted_input=true
// Keys must be in sorted order for this optimization
rocksdb_slice_t sorted_key_slices[3];
sorted_key_slices[0].data = "box";
sorted_key_slices[0].size = 3;
sorted_key_slices[1].data = "buff";
sorted_key_slices[1].size = 4;
sorted_key_slices[2].data = "notfound";
sorted_key_slices[2].size = 8;
const char* expected_value[3] = {"c", "rocksdb", NULL};
char* batched_errs[3];
rocksdb_pinnableslice_t* pvals[3];
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 3,
sorted_key_slices, pvals, batched_errs,
true);
const char* val;
size_t val_len;
for (i = 0; i < 3; ++i) {
CheckNoError(batched_errs[i]);
if (pvals[i] != NULL) {
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
CheckEqual(expected_value[i], val, val_len);
rocksdb_pinnableslice_destroy(pvals[i]);
} else {
CheckEqual(expected_value[i], NULL, 0);
}
}
}
{
unsigned char value_found = 0;
@@ -3469,6 +3816,17 @@ int main(int argc, char** argv) {
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
CheckNoError(err);
// test transaction get/set name (before commit)
{
rocksdb_transaction_set_name(txn, "test_txn", 8, &err);
CheckNoError(err);
size_t name_len;
char* txn_name = rocksdb_transaction_get_name(txn, &name_len);
CheckCondition(name_len == 8);
CheckCondition(memcmp(txn_name, "test_txn", 8) == 0);
rocksdb_free(txn_name);
}
// read from outside transaction, before commit
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
@@ -4207,6 +4565,296 @@ int main(int argc, char** argv) {
rocksdb_cache_destroy(lru);
}
StartPhase("remote_compaction_service");
{
RemoteCompactionState remote_state = {0, 0, 0, "", ""};
// Create compaction service
rocksdb_compactionservice_t* service = rocksdb_compactionservice_create(
&remote_state, // state
RemoteCompactionDestroy, // destructor
RemoteCompactionSchedule, // schedule callback
"TestRemoteCompaction", // name
RemoteCompactionWait, // wait callback
RemoteCompactionCancel, // cancel_awaiting_jobs
NULL); // on_installation
// Create options with remote compaction
rocksdb_options_t* remote_options = rocksdb_options_create();
rocksdb_options_set_create_if_missing(remote_options, 1);
rocksdb_options_set_level0_file_num_compaction_trigger(remote_options, 2);
rocksdb_options_set_write_buffer_size(remote_options,
64 * 1024); // 64KB buffer
rocksdb_options_set_max_bytes_for_level_base(remote_options,
256 * 1024); // 256KB
rocksdb_options_set_target_file_size_base(
remote_options, 64 * 1024); // 64KB target file size
// Disable automatic compactions to test manual compaction only
rocksdb_options_set_disable_auto_compactions(remote_options, 1);
rocksdb_options_set_compaction_service(remote_options, service);
// Destroy old DB and create new one
rocksdb_close(db);
rocksdb_destroy_db(remote_options, dbname, &err);
CheckNoError(err);
db = rocksdb_open(remote_options, dbname, &err);
CheckNoError(err);
// Create multiple SST files to trigger compaction
rocksdb_flushoptions_t* flush_opts = rocksdb_flushoptions_create();
rocksdb_flushoptions_set_wait(flush_opts, 1);
// Write and flush multiple times to create multiple L0 files
// Write more data with larger values to ensure files are substantial
for (int batch = 0; batch < 5; batch++) {
for (int i = 0; i < 200; i++) {
char key[20], val[1000];
snprintf(key, sizeof(key), "key%d_%d", batch, i);
// Fill value with repeated data to make it larger
memset(val, 'a' + (batch % 26), sizeof(val) - 1);
val[sizeof(val) - 1] = '\0';
rocksdb_put(db, woptions, key, strlen(key), val, strlen(val), &err);
CheckNoError(err);
}
rocksdb_flush(db, flush_opts, &err);
CheckNoError(err);
}
rocksdb_flushoptions_destroy(flush_opts);
// Trigger manual compaction to invoke remote compaction service
rocksdb_compact_range(db, NULL, 0, NULL, 0);
rocksdb_wait_for_compact_options_t* wco =
rocksdb_wait_for_compact_options_create();
rocksdb_wait_for_compact(db, wco, &err);
CheckNoError(err);
rocksdb_wait_for_compact_options_destroy(wco);
// Verify that callbacks were actually called
CheckCondition(remote_state.schedule_called > 0);
CheckCondition(remote_state.wait_called > 0);
CheckCondition(strlen(remote_state.last_db_name) > 0);
CheckCondition(strstr(remote_state.last_db_name, "rocksdb_c_test") != NULL);
// Verify data is still accessible after remote compaction
// Just check a few keys to verify data integrity
for (int batch = 0; batch < 5; batch++) {
char key[20];
snprintf(key, sizeof(key), "key%d_0", batch);
size_t vallen;
char* val = rocksdb_get(db, roptions, key, strlen(key), &vallen, &err);
CheckNoError(err);
CheckCondition(val != NULL);
CheckCondition(vallen == 999); // strlen of 1000-byte string
free(val);
}
// Test cancellation API directly
RemoteCompactionCancel(&remote_state);
CheckCondition(remote_state.cancel_called > 0);
// Cleanup
rocksdb_close(db);
rocksdb_destroy_db(remote_options, dbname, &err);
CheckNoError(err);
rocksdb_options_destroy(remote_options);
// Reopen DB with original options for subsequent tests
db = rocksdb_open(options, dbname, &err);
CheckNoError(err);
}
StartPhase("remote_compaction_scheduleresponse");
{
// Test scheduleresponse creation and getters
rocksdb_compactionservice_scheduleresponse_t* response;
// Test success response
err = NULL;
response = rocksdb_compactionservice_scheduleresponse_create(
"test-job-123", rocksdb_compactionservice_jobstatus_success, &err);
CheckNoError(err);
CheckCondition(response != NULL);
CheckCondition(
rocksdb_compactionservice_scheduleresponse_getstatus(response) ==
rocksdb_compactionservice_jobstatus_success);
size_t job_id_len;
const char* job_id =
rocksdb_compactionservice_scheduleresponse_get_scheduled_job_id(
response, &job_id_len);
CheckCondition(job_id_len == strlen("test-job-123"));
CheckCondition(memcmp(job_id, "test-job-123", job_id_len) == 0);
rocksdb_compactionservice_scheduleresponse_t_destroy(response);
// Test failure response
response = rocksdb_compactionservice_scheduleresponse_create_with_status(
rocksdb_compactionservice_jobstatus_failure, &err);
CheckCondition(response != NULL);
CheckCondition(
rocksdb_compactionservice_scheduleresponse_getstatus(response) ==
rocksdb_compactionservice_jobstatus_failure);
rocksdb_compactionservice_scheduleresponse_t_destroy(response);
response = rocksdb_compactionservice_scheduleresponse_create_with_status(
999, &err);
CheckCondition(response == NULL); // Invalid status
if (err) {
Free(&err);
}
}
StartPhase("remote_compaction_options_override");
{
// Test CompactionServiceOptionsOverride API
rocksdb_compaction_service_options_override_t* override_opts =
rocksdb_compaction_service_options_override_create();
CheckCondition(override_opts != NULL);
// Set up override options
rocksdb_compaction_service_options_override_set_env(override_opts, env);
rocksdb_compaction_service_options_override_set_comparator(override_opts,
cmp);
// Test file checksum gen factory
rocksdb_file_checksum_gen_factory_t* checksum_factory =
rocksdb_file_checksum_gen_crc32c_factory_create();
CheckCondition(checksum_factory != NULL);
rocksdb_compaction_service_options_override_set_file_checksum_gen_factory(
override_opts, checksum_factory);
// Test SST partitioner factory
rocksdb_sst_partitioner_factory_t* partitioner_factory =
rocksdb_sst_partitioner_fixed_prefix_factory_create(4);
CheckCondition(partitioner_factory != NULL);
rocksdb_compaction_service_options_override_set_sst_partitioner_factory(
override_opts, partitioner_factory);
// Test merge operator
rocksdb_compaction_service_options_override_set_merge_operator(
override_opts, NULL);
// Test compaction filter
rocksdb_compaction_service_options_override_set_compaction_filter(
override_opts, NULL);
// Test prefix extractor
rocksdb_compaction_service_options_override_set_prefix_extractor(
override_opts, NULL);
// Test table factory - block based
rocksdb_block_based_table_options_t* table_opts =
rocksdb_block_based_options_create();
rocksdb_compaction_service_options_override_set_block_based_table_factory(
override_opts, table_opts);
rocksdb_block_based_options_destroy(table_opts);
// Test statistics via options
rocksdb_options_t* stats_opts = rocksdb_options_create();
rocksdb_options_enable_statistics(stats_opts);
rocksdb_compaction_service_options_override_set_statistics(override_opts,
stats_opts);
rocksdb_options_destroy(stats_opts);
// Test info log
rocksdb_logger_t* logger =
rocksdb_logger_create_stderr_logger(1, "test_prefix");
rocksdb_compaction_service_options_override_set_info_log(override_opts,
logger);
rocksdb_logger_destroy(logger);
// Test options map
rocksdb_compaction_service_options_override_set_option(
override_opts, "max_bytes_for_level_base", "67108864");
// Cleanup
rocksdb_file_checksum_gen_factory_destroy(checksum_factory);
rocksdb_sst_partitioner_factory_destroy(partitioner_factory);
rocksdb_compaction_service_options_override_destroy(override_opts);
}
StartPhase("factory_options_on_regular_options");
{
// Test that the new factory types work with regular rocksdb_options_t
rocksdb_options_t* test_opts = rocksdb_options_create();
// Test file checksum gen factory on regular options
rocksdb_file_checksum_gen_factory_t* checksum_factory =
rocksdb_file_checksum_gen_crc32c_factory_create();
CheckCondition(checksum_factory != NULL);
rocksdb_options_set_file_checksum_gen_factory(test_opts, checksum_factory);
// Test SST partitioner factory on regular options
rocksdb_sst_partitioner_factory_t* partitioner_factory =
rocksdb_sst_partitioner_fixed_prefix_factory_create(8);
CheckCondition(partitioner_factory != NULL);
rocksdb_options_set_sst_partitioner_factory(test_opts, partitioner_factory);
// Cleanup
rocksdb_file_checksum_gen_factory_destroy(checksum_factory);
rocksdb_sst_partitioner_factory_destroy(partitioner_factory);
rocksdb_options_destroy(test_opts);
}
StartPhase("remote_compaction_null_callback_handling");
{
// Test that NULL callback returns are handled gracefully
// This simulates a failure in the remote compaction service
rocksdb_compactionservice_t* null_service =
rocksdb_compactionservice_create(NULL, NULL, NullSchedule,
"NullTestService", NULL, NULL, NULL);
rocksdb_options_t* null_opts = rocksdb_options_create();
rocksdb_options_set_create_if_missing(null_opts, 1);
rocksdb_options_set_compaction_service(null_opts, null_service);
const char* null_db = "rocksdb_c_test_null_service";
rocksdb_t* null_db_handle = rocksdb_open(null_opts, null_db, &err);
CheckNoError(err);
// Write data and trigger compaction
for (int i = 0; i < 100; i++) {
char key[20], val[50];
snprintf(key, sizeof(key), "key%d", i);
snprintf(val, sizeof(val), "val%d", i);
rocksdb_put(null_db_handle, woptions, key, strlen(key), val, strlen(val),
&err);
CheckNoError(err);
}
// This should fall back to local compaction (not crash)
rocksdb_compact_range(null_db_handle, NULL, 0, NULL, 0);
// Data should still be readable
CheckGet(null_db_handle, roptions, "key50", "val50");
rocksdb_close(null_db_handle);
rocksdb_destroy_db(null_opts, null_db, &err);
rocksdb_options_destroy(null_opts);
}
StartPhase("remote_compaction_canceled_flag");
{
// Test atomic cancellation flag API
unsigned char* canceled = rocksdb_open_and_compact_canceled_create();
CheckCondition(canceled != NULL);
// Set cancellation
rocksdb_open_and_compact_canceled_set(canceled, 1);
// Use with OpenAndCompactOptions
rocksdb_open_and_compact_options_t* oac_opts =
rocksdb_open_and_compact_options_create();
rocksdb_open_and_compact_options_set_canceled(oac_opts, canceled);
rocksdb_open_and_compact_options_set_allow_resumption(oac_opts, 1);
// Cleanup
rocksdb_open_and_compact_options_destroy(oac_opts);
rocksdb_open_and_compact_canceled_destroy(canceled);
}
StartPhase("sst_file_manager");
{
rocksdb_sst_file_manager_t* sst_file_manager;
+2 -2
View File
@@ -1248,7 +1248,7 @@ Compaction* ColumnFamilyData::PickCompaction(
auto* result = compaction_picker_->PickCompaction(
GetName(), mutable_options, mutable_db_options, existing_snapshots,
snapshot_checker, current_->storage_info(), log_buffer,
require_max_output_level);
GetFullHistoryTsLow(), require_max_output_level);
if (result != nullptr) {
result->FinalizeInputInfo(current_);
}
@@ -1336,7 +1336,7 @@ Compaction* ColumnFamilyData::CompactRange(
GetName(), mutable_cf_options, mutable_db_options,
current_->storage_info(), input_level, output_level,
compact_range_options, begin, end, compaction_end, conflict,
max_file_num_to_ignore, trim_ts);
max_file_num_to_ignore, trim_ts, GetFullHistoryTsLow());
if (result != nullptr) {
result->FinalizeInputInfo(current_);
}
+3 -2
View File
@@ -433,8 +433,9 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
ROCKSDB_NAMESPACE::TablePropertiesCollection all_tables_props;
ASSERT_OK(db->GetPropertiesOfAllTables(&all_tables_props));
for (const auto& name_and_table_props : all_tables_props) {
ASSERT_EQ(CompressionTypeToString(CompressionType::kZlibCompression),
name_and_table_props.second->compression_name);
// As of format_version 7, more elaborate information is encoded into the
// compression_name property
ASSERT_EQ("BuiltinV2;02;", name_and_table_props.second->compression_name);
}
delete db;
}
+4
View File
@@ -180,6 +180,10 @@ class Compaction {
const std::vector<CompactionInputFiles>* inputs() { return &inputs_; }
// Returns the LevelFilesBrief of the specified compaction input level.
// Note that if the compaction includes standalone range deletion file,
// this function returns the result after filtering out input files covered
// by the range deletion file.
// Use inputs() if you want to get the original input files.
const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
return &input_levels_[compaction_input_level];
}
+13 -5
View File
@@ -1310,14 +1310,14 @@ void CompactionIterator::PrepareOutput() {
validity_info_.rep);
assert(false);
}
ikey_.sequence = 0;
last_key_seq_zeroed_ = true;
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput:ZeroingSeq",
&ikey_);
bool zeroed_seqno = false;
if (!timestamp_size_) {
current_key_.UpdateInternalKey(0, ikey_.type);
zeroed_seqno = true;
} else if (full_history_ts_low_ && cmp_with_history_ts_low_ < 0) {
// We can also zero out timestamp for better compression.
// For UDT, the seqno and timestamp could only be zeroed out after the
// key is below history_ts_low_.
// For the same user key (excluding timestamp), the timestamp-based
// history can be collapsed to save some space if the timestamp is
// older than *full_history_ts_low_.
@@ -1325,6 +1325,14 @@ void CompactionIterator::PrepareOutput() {
const Slice ts_slice = kTsMin;
ikey_.SetTimestamp(ts_slice);
current_key_.UpdateInternalKey(0, ikey_.type, &ts_slice);
zeroed_seqno = true;
}
if (zeroed_seqno) {
ikey_.sequence = 0;
last_key_seq_zeroed_ = true;
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput:ZeroingSeq",
&ikey_);
}
}
}
+61 -26
View File
@@ -1480,11 +1480,11 @@ CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact,
const CompactionFileCloseFunc close_file_func =
[this, sub_compact, start_user_key, end_user_key](
const Status& status,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const CompactionIterator* c_iter,
CompactionOutputs& outputs) {
return this->FinishCompactionOutputFile(
status, prev_table_last_internal_key, next_table_min_key,
status, prev_iter_output_internal_key, next_table_min_key,
start_user_key, end_user_key, c_iter, sub_compact, outputs);
};
@@ -1499,8 +1499,8 @@ Status CompactionJob::ProcessKeyValue(
const uint64_t kRecordStatsEvery = 1000;
[[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
IterKey last_output_key;
ParsedInternalKey last_output_ikey;
IterKey prev_iter_output_key;
ParsedInternalKey prev_iter_output_internal_key;
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::ProcessKeyValueCompaction()::Processing",
@@ -1551,9 +1551,9 @@ Status CompactionJob::ProcessKeyValue(
// and `close_file_func`.
// TODO: it would be better to have the compaction file open/close moved
// into `CompactionOutputs` which has the output file information.
status =
sub_compact->AddToOutput(*c_iter, use_proximal_output, open_file_func,
close_file_func, last_output_ikey);
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
open_file_func, close_file_func,
prev_iter_output_internal_key);
if (!status.ok()) {
break;
}
@@ -1562,9 +1562,10 @@ Status CompactionJob::ProcessKeyValue(
static_cast<void*>(const_cast<std::atomic<bool>*>(
&manual_compaction_canceled_)));
last_output_key.SetInternalKey(c_iter->key(), &last_output_ikey);
last_output_ikey.sequence = ikey.sequence;
last_output_ikey.type = ikey.type;
prev_iter_output_key.SetInternalKey(c_iter->key(),
&prev_iter_output_internal_key);
prev_iter_output_internal_key.sequence = ikey.sequence;
prev_iter_output_internal_key.type = ikey.type;
c_iter->Next();
#ifndef NDEBUG
@@ -1871,7 +1872,7 @@ void CompactionJob::RecordDroppedKeys(
Status CompactionJob::FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const Slice* comp_start_user_key,
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
SubcompactionState* sub_compact, CompactionOutputs& outputs) {
@@ -2049,7 +2050,7 @@ Status CompactionJob::FinishCompactionOutputFile(
}
if (s.ok() && ShouldUpdateSubcompactionProgress(sub_compact, c_iter,
prev_table_last_internal_key,
prev_iter_output_internal_key,
next_table_min_key, meta)) {
UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact);
s = PersistSubcompactionProgress(sub_compact);
@@ -2060,10 +2061,10 @@ Status CompactionJob::FinishCompactionOutputFile(
bool CompactionJob::ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const {
const auto* cfd = sub_compact->compaction->column_family_data();
// No need to update when the output will not get persisted
// No need to update when the progress will not get persisted
if (compaction_progress_writer_ == nullptr) {
return false;
}
@@ -2087,19 +2088,24 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
}
// LIMITATION: Compaction progress persistence disabled for file boundaries
// contaning range deletions. Range deletions can span file boundaries, making
// it difficult (but possible) to ensure adjacent output tables have different
// user keys. See the last check for why different users keys of adjacent
// output tables are needed
// containing range deletions. Range deletions can span file boundaries,
// making it difficult to ensure adjacent output tables have different user
// keys. See the last check for why different users keys of adjacent output
// tables are needed
const ValueType next_table_min_internal_key_type =
ExtractValueType(next_table_min_internal_key);
const ValueType prev_table_last_internal_key_type =
prev_table_last_internal_key.user_key.empty()
const ValueType prev_iter_output_internal_key_type =
prev_iter_output_internal_key.user_key.empty()
? ValueType::kTypeValue
: prev_table_last_internal_key.type;
: prev_iter_output_internal_key.type;
if (next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
prev_table_last_internal_key_type == ValueType::kTypeRangeDeletion) {
// Range deletes truncated to align with file boundaries may be output by the
// compaction iterator with `ValueType::kTypeMaxValid` instead of the original
// type.
if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
next_table_min_internal_key_type == ValueType::kTypeMaxValid) ||
(prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion ||
prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) {
return false;
}
@@ -2109,9 +2115,9 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
const Slice next_table_min_user_key =
ExtractUserKey(next_table_min_internal_key);
const Slice prev_table_last_user_key =
prev_table_last_internal_key.user_key.empty()
prev_iter_output_internal_key.user_key.empty()
? Slice()
: prev_table_last_internal_key.user_key;
: prev_iter_output_internal_key.user_key;
if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key,
prev_table_last_user_key)) {
@@ -2432,6 +2438,32 @@ bool CompactionJob::UpdateInternalStatsFromInputFiles(
bool has_error = false;
const ReadOptions read_options(Env::IOActivity::kCompaction);
const auto& input_table_properties = compaction->GetInputTableProperties();
// Check all input files for old block-based SST format_version. Why? Old
// block-based SST files from roughly version 5.0 to 5.18 could produce
// inaccurate num_entries counts due to the evolution of its handling along
// with num_range_deletions. We have to disable some paranoid checks when
// compacting files from such an old release. However, we don't have great
// information to identify those files, so we heuristically over-approximate
// that set of files using
// (a) format_version < 5, which will be true for any files from RocksDB <
// 6.6.0 and should not be true for any recent production files
// (b) to avoid including non-block-based SST files (which still use older
// format_version markers, and do not support DeleteRange), we also require
// the presence of the user property "rocksdb.block.based.table.index.type",
// which was added in RocksDB 2.8 and is always present in block-based tables.
for (const auto& tp_pair : input_table_properties) {
if (tp_pair.second && tp_pair.second->format_version < 5) {
// Check for block-based table by looking for its index type property
const auto& user_props = tp_pair.second->user_collected_properties;
if (user_props.find(BlockBasedTablePropertyNames::kIndexType) !=
user_props.end()) {
job_stats_->has_accurate_num_input_records = false;
break;
}
}
}
for (int input_level = 0;
input_level < static_cast<int>(compaction->num_input_levels());
++input_level) {
@@ -2676,7 +2708,10 @@ Status CompactionJob::ReadTablePropertiesDirectly(
std::shared_ptr<const TableProperties>* tp) {
std::unique_ptr<FSRandomAccessFile> file;
std::string file_name = GetTableFileName(file_meta->fd.GetNumber());
Status s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
FileOptions fopts = file_options_;
fopts.file_checksum = file_meta->file_checksum;
fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
Status s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file,
nullptr /* dbg */);
if (!s.ok()) {
return s;
+6 -2
View File
@@ -250,6 +250,10 @@ class CompactionJob {
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// If any input file has potentially unreliable num_entries count (old SST
// files - details in implementation),
// job_stats_->has_accurate_num_input_records is set to false.
//
// Returns true iff internal_stats_.output_level_stats.num_input_records and
// num_input_range_del are calculated successfully.
//
@@ -430,7 +434,7 @@ class CompactionJob {
Status FinishCompactionOutputFile(
const Status& input_status,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_key, const Slice* comp_start_user_key,
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
SubcompactionState* sub_compact, CompactionOutputs& outputs);
@@ -545,7 +549,7 @@ class CompactionJob {
bool ShouldUpdateSubcompactionProgress(
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
const ParsedInternalKey& prev_table_last_internal_key,
const ParsedInternalKey& prev_iter_output_internal_key,
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
+31 -23
View File
@@ -2424,6 +2424,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
bool enable_cancel_ = false;
std::atomic<int> stop_count_{0};
std::atomic<bool> cancel_{false};
SequenceNumber cancel_before_seqno = kMaxSequenceNumber;
void SetUp() override {
CompactionJobTestBase::SetUp();
@@ -2437,7 +2438,9 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
if (enable_cancel_) {
ParsedInternalKey parsed_key;
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
if (parsed_key.user_key == kCancelBeforeThisKey) {
if (parsed_key.user_key == kCancelBeforeThisKey &&
(cancel_before_seqno == kMaxSequenceNumber ||
parsed_key.sequence == cancel_before_seqno)) {
cancel_.store(true);
}
}
@@ -2665,7 +2668,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
const std::initializer_list<mock::KVPair>& input_file_2,
uint64_t last_sequence, const std::vector<uint64_t>& snapshots,
const std::string& expected_next_key_to_compact,
const std::vector<std::string>& expected_input_keys, bool exists_progress,
const std::vector<std::string>& expected_input_keys,
bool cancelled_past_mid_point = false) {
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
@@ -2701,7 +2704,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
// Resume compaction
CompactionProgress compaction_progress;
if (exists_progress) {
if (expected_next_key_to_compact != "") {
compaction_progress.push_back(
ReadAndParseProgress(compaction_progress_file));
}
@@ -2774,35 +2777,43 @@ TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
4U /* last_sequence */, {} /* snapshots */,
kCancelBeforeThisKey /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* exists_progress */, true /* cancelled_past_mid_point*/);
true /* cancelled_past_mid_point */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSameKey) {
NewDB();
// `cancel_before_seqno` is set to 0U to force cancellation after
// `kCancelBeforeThisKey@1` instead of `kCancelBeforeThisKey@2`.
// The seqno is 0 because `kCancelBeforeThisKey@1` will have its sequence
// number zeroed during compaction while `kCancelBeforeThisKey@2` won't be
cancel_before_seqno = 0U;
RunCancelAndResumeTest(
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
"val1"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue),
"val2"}} /* input_file_2 */,
2U /* last_sequence */, {1U} /* snapshots */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue), "val11"},
{KeyStr("d", 3U, kTypeValue), "val2"}} /* input_file_2 */,
3U /* last_sequence */, {1U} /* snapshots */,
"" /* expected_next_key_to_compact */,
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
false /* exists_progress */);
{kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
NewDB();
RunCancelAndResumeTest(
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
"val1"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeRangeDeletion),
"val2"}} /* input_file_2 */,
2U /* last_sequence */, {1U} /* snapshots */,
"" /* expected_next_key_to_compact */,
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
false /* exists_progress */);
{{KeyStr("a", 1U, kTypeValue), "val1"},
{KeyStr("b", 2U, kTypeValue), "val2"},
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
"val3"}} /* input_file_1 */,
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeRangeDeletion),
"range_deletion_end_key"},
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
@@ -2817,8 +2828,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
"val4"}} /* input_file_2 */,
4U /* last_sequence */, {} /* snapshots */,
"bb" /* expected_next_key_to_compact */,
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
true /* exists_progress */);
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
@@ -2834,8 +2844,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */,
true /* exists_progress */);
"d"} /* expected_input_keys */);
}
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
@@ -2851,8 +2860,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
5U /* last_sequence */, {3U} /* snapshots */,
"b" /* expected_next_key_to_compact */,
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
"d"} /* expected_input_keys */,
true /* exists_progress */);
"d"} /* expected_input_keys */);
}
} // namespace ROCKSDB_NAMESPACE
+12 -6
View File
@@ -49,8 +49,10 @@ Status CompactionOutputs::Finish(
meta->fd.file_size = current_bytes;
meta->tail_size = builder_->GetTailSize();
meta->marked_for_compaction = builder_->NeedCompact();
meta->user_defined_timestamps_persisted = static_cast<bool>(
builder_->GetTableProperties().user_defined_timestamps_persisted);
const TableProperties& tp = builder_->GetTableProperties();
meta->user_defined_timestamps_persisted =
static_cast<bool>(tp.user_defined_timestamps_persisted);
ExtractTimestampFromTableProperties(tp, meta);
}
current_output().finished = true;
stats_.bytes_written += current_bytes;
@@ -278,7 +280,11 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
}
// reach the max file size
if (current_output_file_size_ >= compaction_->max_output_file_size()) {
uint64_t estimated_file_size = current_output_file_size_;
if (compaction_->mutable_cf_options().target_file_size_is_upper_bound) {
estimated_file_size += builder_->EstimatedTailSize();
}
if (estimated_file_size >= compaction_->max_output_file_size()) {
return true;
}
@@ -360,7 +366,7 @@ Status CompactionOutputs::AddToOutput(
const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key) {
const ParsedInternalKey& prev_iter_output_internal_key) {
Status s;
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
if (is_range_del && compaction_->bottommost_level()) {
@@ -371,8 +377,8 @@ Status CompactionOutputs::AddToOutput(
}
const Slice& key = c_iter.key();
if (ShouldStopBefore(c_iter) && HasBuilder()) {
s = close_file_func(c_iter.InputStatus(), prev_table_last_internal_key, key,
&c_iter, *this);
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
key, &c_iter, *this);
if (!s.ok()) {
return s;
}
+1 -1
View File
@@ -262,7 +262,7 @@ class CompactionOutputs {
Status AddToOutput(const CompactionIterator& c_iter,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key);
const ParsedInternalKey& prev_iter_output_internal_key);
// Close the current output. `open_file_func` is needed for creating new file
// for range-dels only output file.
+17 -9
View File
@@ -242,7 +242,7 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
GetRange(*inputs, &smallest, &largest);
inputs->clear();
vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
hint_index, &hint_index, true,
hint_index, &hint_index, true, nullptr,
next_smallest);
} while (inputs->size() > old_size);
@@ -465,7 +465,8 @@ bool CompactionPicker::SetupOtherInputs(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs, int* parent_index,
int base_index, bool only_expand_towards_right) {
int base_index, bool only_expand_towards_right,
const FileMetaData* starting_l0_file) {
assert(!inputs->empty());
assert(output_level_inputs->empty());
const int input_level = inputs->level;
@@ -521,11 +522,11 @@ bool CompactionPicker::SetupOtherInputs(
// Round-robin compaction only allows expansion towards the larger side.
vstorage->GetOverlappingInputs(input_level, &smallest, &all_limit,
&expanded_inputs.files, base_index,
nullptr);
nullptr, true, starting_l0_file);
} else {
vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
&expanded_inputs.files, base_index,
nullptr);
nullptr, true, starting_l0_file);
}
uint64_t expanded_inputs_size = TotalFileSize(expanded_inputs.files);
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
@@ -610,7 +611,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
int input_level, int output_level,
const CompactRangeOptions& compact_range_options, const InternalKey* begin,
const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict,
uint64_t max_file_num_to_ignore, const std::string& trim_ts) {
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
const std::string& full_history_ts_low) {
// CompactionPickerFIFO has its own implementation of compact range
assert(ioptions_.compaction_style != kCompactionStyleFIFO);
@@ -689,7 +691,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
compact_range_options.blob_garbage_collection_age_cutoff);
RegisterCompaction(c);
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options,
full_history_ts_low);
return c;
}
@@ -886,7 +889,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
// takes running compactions into account (by skipping files that are already
// being compacted). Since we just changed compaction score, we recalculate it
// here
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options,
full_history_ts_low);
return compaction;
}
@@ -1231,7 +1235,7 @@ void CompactionPicker::PickFilesMarkedForCompaction(
bool CompactionPicker::GetOverlappingL0Files(
VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index) {
int output_level, int* parent_index, const FileMetaData* starting_l0_file) {
// 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());
@@ -1242,7 +1246,11 @@ bool CompactionPicker::GetOverlappingL0Files(
// which will include the picked file.
start_level_inputs->files.clear();
vstorage->GetOverlappingInputs(0, &smallest, &largest,
&(start_level_inputs->files));
&(start_level_inputs->files),
/*hint_index=*/-1,
/*file_index=*/nullptr,
/*expand_range=*/true,
/*starting_l0_file=*/starting_l0_file);
// If we include more L0 files in the same compaction run it can
// cause the 'smallest' and 'largest' key to get extended to a
+16 -7
View File
@@ -65,7 +65,8 @@ class CompactionPicker {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
LogBuffer* log_buffer, bool require_max_output_level) = 0;
LogBuffer* log_buffer, const std::string& full_history_ts_low,
bool require_max_output_level = false) = 0;
// The returned Compaction might not include the whole requested range.
// In that case, compaction_end will be set to the next key that needs
@@ -82,7 +83,8 @@ class CompactionPicker {
const CompactRangeOptions& compact_range_options,
const InternalKey* begin, const InternalKey* end,
InternalKey** compaction_end, bool* manual_conflict,
uint64_t max_file_num_to_ignore, const std::string& trim_ts);
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
const std::string& full_history_ts_low);
// The maximum allowed output level. Default value is NumberLevels() - 1.
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
@@ -203,13 +205,16 @@ class CompactionPicker {
const std::vector<CompactionInputFiles>& inputs, int level,
int proximal_level) const;
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool SetupOtherInputs(const std::string& cf_name,
const MutableCFOptions& mutable_cf_options,
VersionStorageInfo* vstorage,
CompactionInputFiles* inputs,
CompactionInputFiles* output_level_inputs,
int* parent_index, int base_index,
bool only_expand_towards_right = false);
bool only_expand_towards_right = false,
const FileMetaData* starting_l0_file = nullptr);
void GetGrandparents(VersionStorageInfo* vstorage,
const CompactionInputFiles& inputs,
@@ -222,9 +227,12 @@ class CompactionPicker {
CompactionInputFiles* start_level_inputs,
std::function<bool(const FileMetaData*)> skip_marked_file);
// @param starting_l0_file If not null, restricts L0 file selection to only
// include files at or older than starting_l0_file.
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
CompactionInputFiles* start_level_inputs,
int output_level, int* parent_index);
int output_level, int* parent_index,
const FileMetaData* starting_l0_file = nullptr);
// Register this compaction in the set of running compactions
void RegisterCompaction(Compaction* c);
@@ -278,7 +286,8 @@ class NullCompactionPicker : public CompactionPicker {
const std::vector<SequenceNumber>& /*existing_snapshots*/,
const SnapshotChecker* /*snapshot_checker*/,
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
bool /*require_max_output_level*/ = false) override {
const std::string& /*full_history_ts_low*/,
bool /*require_max_output_level*/) override {
return nullptr;
}
@@ -292,8 +301,8 @@ class NullCompactionPicker : public CompactionPicker {
const CompactRangeOptions& /*compact_range_options*/,
const InternalKey* /*begin*/, const InternalKey* /*end*/,
InternalKey** /*compaction_end*/, bool* /*manual_conflict*/,
uint64_t /*max_file_num_to_ignore*/,
const std::string& /*trim_ts*/) override {
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/,
const std::string& /*full_history_ts_low*/) override {
return nullptr;
}
+14 -6
View File
@@ -419,12 +419,19 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
return c;
}
// The full_history_ts_low parameter is used to control bottommost file marking
// for compaction when user-defined timestamps (UDT) are enabled.
// TODO leverage full_history_ts_low for FIFO compaction, by trigggerring
// compaction early for data that has already expired to achieve the goal of TTL
// enforced compliance.
Compaction* FIFOCompactionPicker::PickCompaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
LogBuffer* log_buffer, const std::string& /* full_history_ts_low */,
bool /* require_max_output_level*/) {
Compaction* c = nullptr;
if (mutable_cf_options.ttl > 0) {
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
@@ -449,7 +456,8 @@ Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
const CompactRangeOptions& /*compact_range_options*/,
const InternalKey* /*begin*/, const InternalKey* /*end*/,
InternalKey** compaction_end, bool* /*manual_conflict*/,
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/) {
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/,
const std::string& full_history_ts_low) {
#ifdef NDEBUG
(void)input_level;
(void)output_level;
@@ -458,10 +466,10 @@ Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
assert(output_level == 0);
*compaction_end = nullptr;
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.logger);
Compaction* c =
PickCompaction(cf_name, mutable_cf_options, mutable_db_options,
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr,
vstorage, &log_buffer);
Compaction* c = PickCompaction(
cf_name, mutable_cf_options, mutable_db_options,
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr, vstorage,
&log_buffer, full_history_ts_low, /* require_max_output_level */ false);
log_buffer.FlushBufferToLog();
return c;
}
+3 -1
View File
@@ -24,6 +24,7 @@ class FIFOCompactionPicker : public CompactionPicker {
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */,
VersionStorageInfo* version, LogBuffer* log_buffer,
const std::string& /* full_history_ts_low */,
bool /* require_max_output_level*/ = false) override;
Compaction* PickCompactionForCompactRange(
@@ -33,7 +34,8 @@ class FIFOCompactionPicker : public CompactionPicker {
const CompactRangeOptions& compact_range_options,
const InternalKey* begin, const InternalKey* end,
InternalKey** compaction_end, bool* manual_conflict,
uint64_t max_file_num_to_ignore, const std::string& trim_ts) override;
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
const std::string& full_history_ts_low) override;
// The maximum allowed output level. Always returns 0.
int MaxOutputLevel() const override { return 0; }
+10 -5
View File
@@ -61,14 +61,16 @@ class LevelCompactionBuilder {
LogBuffer* log_buffer,
const MutableCFOptions& mutable_cf_options,
const ImmutableOptions& ioptions,
const MutableDBOptions& mutable_db_options)
const MutableDBOptions& mutable_db_options,
const std::string& full_history_ts_low)
: cf_name_(cf_name),
vstorage_(vstorage),
compaction_picker_(compaction_picker),
log_buffer_(log_buffer),
mutable_cf_options_(mutable_cf_options),
ioptions_(ioptions),
mutable_db_options_(mutable_db_options) {}
mutable_db_options_(mutable_db_options),
full_history_ts_low_(full_history_ts_low) {}
// Pick and return a compaction.
Compaction* PickCompaction();
@@ -155,6 +157,7 @@ class LevelCompactionBuilder {
const MutableCFOptions& mutable_cf_options_;
const ImmutableOptions& ioptions_;
const MutableDBOptions& mutable_db_options_;
const std::string& full_history_ts_low_;
// Pick a path ID to place a newly generated file, with its level
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
const MutableCFOptions& mutable_cf_options,
@@ -571,7 +574,8 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
// takes running compactions into account (by skipping files that are already
// being compacted). Since we just changed compaction score, we recalculate it
// here
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_,
full_history_ts_low_);
return c;
}
@@ -976,10 +980,11 @@ Compaction* LevelCompactionPicker::PickCompaction(
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& /*existing_snapshots */,
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
LogBuffer* log_buffer, const std::string& full_history_ts_low,
bool /* require_max_output_level*/) {
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
mutable_cf_options, ioptions_,
mutable_db_options);
mutable_db_options, full_history_ts_low);
return builder.PickCompaction();
}
} // namespace ROCKSDB_NAMESPACE
+1
View File
@@ -26,6 +26,7 @@ class LevelCompactionPicker : public CompactionPicker {
const std::vector<SequenceNumber>& /* existing_snapshots */,
const SnapshotChecker* /* snapshot_checker */,
VersionStorageInfo* vstorage, LogBuffer* log_buffer,
const std::string& full_history_ts_low,
bool /*require_max_output_level*/ = false) override;
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
File diff suppressed because it is too large. Load diff
+24 -7
View File
@@ -39,7 +39,7 @@ class UniversalCompactionBuilder {
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
UniversalCompactionPicker* picker, LogBuffer* log_buffer,
bool require_max_output_level)
bool require_max_output_level, const std::string& full_history_ts_low)
: ioptions_(ioptions),
icmp_(icmp),
cf_name_(cf_name),
@@ -50,7 +50,8 @@ class UniversalCompactionBuilder {
log_buffer_(log_buffer),
require_max_output_level_(require_max_output_level),
allow_ingest_behind_(ioptions.cf_allow_ingest_behind ||
ioptions.allow_ingest_behind) {
ioptions.allow_ingest_behind),
full_history_ts_low_(full_history_ts_low) {
assert(icmp_);
const auto* ucmp = icmp_->user_comparator();
assert(ucmp);
@@ -450,6 +451,7 @@ class UniversalCompactionBuilder {
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
bool require_max_output_level_;
bool allow_ingest_behind_;
const std::string& full_history_ts_low_;
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
const VersionStorageInfo& vstorage, int last_level,
@@ -595,16 +597,20 @@ bool UniversalCompactionPicker::NeedsCompaction(
return false;
}
// TODO leverage full_history_ts_low in universal compaction picking. It could
// help reduce the same infinite compaction loop issue found in level
// compaction.
Compaction* UniversalCompactionPicker::PickCompaction(
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
LogBuffer* log_buffer, bool require_max_output_level) {
LogBuffer* log_buffer, const std::string& full_history_ts_low,
bool require_max_output_level) {
UniversalCompactionBuilder builder(
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
existing_snapshots, snapshot_checker, vstorage, this, log_buffer,
require_max_output_level);
require_max_output_level, full_history_ts_low);
return builder.PickCompaction();
}
@@ -825,7 +831,8 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION, num_files);
picker_->RegisterCompaction(c);
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_,
full_history_ts_low_);
TEST_SYNC_POINT_CALLBACK("UniversalCompactionBuilder::PickCompaction:Return",
c);
@@ -1544,9 +1551,18 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
}
if (output_level != 0) {
// For standalone range deletion, we don't want to compact it with newer
// L0 files that it doesn't cover.
const FileMetaData* starting_l0_file =
(start_level == 0 && start_level_inputs.size() == 1 &&
start_level_inputs.files[0]->FileIsStandAloneRangeTombstone())
? start_level_inputs.files[0]
: nullptr;
if (start_level == 0) {
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
output_level, nullptr)) {
output_level, nullptr,
starting_l0_file)) {
return nullptr;
}
}
@@ -1557,7 +1573,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
output_level_inputs.level = output_level;
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
&start_level_inputs, &output_level_inputs,
&parent_index, -1)) {
&parent_index, -1, false,
starting_l0_file)) {
return nullptr;
}
inputs.push_back(start_level_inputs);
+2 -1
View File
@@ -26,7 +26,8 @@ class UniversalCompactionPicker : public CompactionPicker {
const MutableDBOptions& mutable_db_options,
const std::vector<SequenceNumber>& existing_snapshots,
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
LogBuffer* log_buffer, bool require_max_output_level = false) override;
LogBuffer* log_buffer, const std::string& full_history_ts_low,
bool require_max_output_level = false) override;
int MaxOutputLevel() const override { return NumberLevels() - 1; }
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
+2 -2
View File
@@ -109,12 +109,12 @@ Status SubcompactionState::AddToOutput(
const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key) {
const ParsedInternalKey& prev_iter_output_internal_key) {
// update target output
current_outputs_ =
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func,
prev_table_last_internal_key);
prev_iter_output_internal_key);
}
} // namespace ROCKSDB_NAMESPACE
+1 -1
View File
@@ -221,7 +221,7 @@ class SubcompactionState {
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
const CompactionFileOpenFunc& open_file_func,
const CompactionFileCloseFunc& close_file_func,
const ParsedInternalKey& prev_table_last_internal_key);
const ParsedInternalKey& prev_iter_output_internal_key);
// Close all compaction output files, both output_to_proximal_level outputs
// and normal outputs.
+4 -4
View File
@@ -65,7 +65,7 @@ Status VerifySstFileChecksum(const Options& options,
}
Status VerifySstFileChecksumInternal(const Options& options,
const EnvOptions& env_options,
const FileOptions& file_options,
const ReadOptions& read_options,
const std::string& file_path,
const SequenceNumber& largest_seqno) {
@@ -74,8 +74,8 @@ Status VerifySstFileChecksumInternal(const Options& options,
InternalKeyComparator internal_comparator(options.comparator);
ImmutableOptions ioptions(options);
Status s = ioptions.fs->NewRandomAccessFile(
file_path, FileOptions(env_options), &file, nullptr);
Status s =
ioptions.fs->NewRandomAccessFile(file_path, file_options, &file, nullptr);
if (s.ok()) {
s = ioptions.fs->GetFileSize(file_path, IOOptions(), &file_size, nullptr);
} else {
@@ -94,7 +94,7 @@ Status VerifySstFileChecksumInternal(const Options& options,
const bool kImmortal = true;
auto reader_options = TableReaderOptions(
ioptions, options.prefix_extractor, options.compression_manager.get(),
env_options, internal_comparator, options.block_protection_bytes_per_key,
file_options, internal_comparator, options.block_protection_bytes_per_key,
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
-1 /* level */);
reader_options.largest_seqno = largest_seqno;
+2 -1
View File
@@ -5,10 +5,11 @@
#pragma once
#include "rocksdb/db.h"
#include "rocksdb/file_system.h"
namespace ROCKSDB_NAMESPACE {
Status VerifySstFileChecksumInternal(const Options& options,
const EnvOptions& env_options,
const FileOptions& file_options,
const ReadOptions& read_options,
const std::string& file_path,
const SequenceNumber& largest_seqno = 0);
+194
View File
@@ -242,6 +242,43 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
Status::Code::kNotSupported);
}
TEST_F(DBBasicTest, ReadOnlyDBFlushWAL) {
// Test that FlushWAL returns NotSupported on read-only DB, and that
// GetLiveFilesStorageInfo works correctly even with manual_wal_flush=true.
// This is a regression test for a bug where GetLiveFilesStorageInfo would
// crash on read-only DBs with manual_wal_flush=true because FlushWAL
// accessed logs_.back() on an empty deque.
auto options = CurrentOptions();
options.manual_wal_flush = true;
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("baz", "v3")); // Unflushed data in WAL
Close();
// Reopen as read-only
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_EQ("v1", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
ASSERT_EQ("v3", Get("baz"));
// FlushWAL should return NotSupported (not crash)
ASSERT_EQ(db_->FlushWAL(/*sync=*/false).code(), Status::Code::kNotSupported);
ASSERT_EQ(db_->FlushWAL(/*sync=*/true).code(), Status::Code::kNotSupported);
// GetLiveFilesStorageInfo should succeed (previously crashed with
// manual_wal_flush=true because it called FlushWAL which accessed
// logs_.back() on empty deque)
LiveFilesStorageInfoOptions lfsi_opts;
lfsi_opts.wal_size_for_flush = 0;
std::vector<LiveFileStorageInfo> files;
ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsi_opts, &files));
ASSERT_GT(files.size(), 0);
Close();
}
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
auto options = CurrentOptions();
options.write_dbid_to_manifest = false;
@@ -3868,6 +3905,75 @@ TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
ASSERT_OK(iter->status());
}
TEST_F(DBBasicTest, BestEffortRecoveryFailureWithTableCacheUseAfterFree) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.env = env_;
// Force multiple manifest files
options.max_manifest_file_size = 1;
options.max_manifest_space_amp_pct = 0;
DestroyAndReopen(options);
// Disable file deletions to preserve old manifest files for
// best-efforts recovery to succeed
ASSERT_OK(db_->DisableFileDeletions());
// Create multiple SST files to populate TableCache during
// best-efforts recovery
for (int i = 0; i < 10; i++) {
ASSERT_OK(Put("key" + std::to_string(i),
std::string(1000, static_cast<char>('a' + i))));
ASSERT_OK(Flush());
}
// Verify we have multiple manifest files
std::vector<std::string> files;
ASSERT_OK(env_->GetChildren(dbname_, &files));
int manifest_count = 0;
for (const auto& file : files) {
if (file.find("MANIFEST") != std::string::npos) {
manifest_count++;
}
}
ASSERT_GE(manifest_count, 2);
// Inject corruption after TableCache is populated (count > 3), but only once
// (injected flag) to allow best-effort recovery to trigger retry and succeed.
// This coerce the bug: first recovery caches SSTs with reference to column
// family's options in table cache and retry deletes column family so the
// reference becomes dangling.
int count = 0;
bool injected = false;
SyncPoint::GetInstance()->SetCallBack(
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
count++;
if (count > 3 && !injected) {
ASSERT_NE(nullptr, arg);
*(static_cast<Status*>(arg)) =
Status::Corruption("Injected corruption");
injected = true;
}
});
SyncPoint::GetInstance()->EnableProcessing();
options.best_efforts_recovery = true;
Status s = TryReopen(options);
ASSERT_OK(s);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
for (int i = 0; i < 10; i++) {
std::string value;
// Without the fix, ASAN detects use-after-free when accessing cached SST
// files that hold dangling references to deleted ioptions.
s = db_->Get(ReadOptions(), "key" + std::to_string(i), &value);
ASSERT_TRUE(s.ok() || s.IsNotFound());
}
}
TEST_F(DBBasicTest, DisableTrackWal) {
// If WAL tracking was enabled, and then disabled during reopen,
// the previously tracked WALs should be removed from MANIFEST.
@@ -5368,6 +5474,94 @@ INSTANTIATE_TEST_CASE_P(DBBasicTestDeadline, DBBasicTestDeadline,
::testing::Values(std::make_tuple(true, false),
std::make_tuple(false, true),
std::make_tuple(true, true)));
// FileSystemWrapper that captures FileOptions passed to NewRandomAccessFile
// for .sst files, so we can verify file_checksum fields are populated.
class ChecksumCapturingFS : public FileSystemWrapper {
public:
explicit ChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
: FileSystemWrapper(base) {}
static const char* kClassName() { return "ChecksumCapturingFS"; }
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(".sst") != std::string::npos) {
std::lock_guard<std::mutex> lock(mu_);
captured_file_checksum_ = opts.file_checksum;
captured_file_checksum_func_name_ = opts.file_checksum_func_name;
capture_count_++;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
std::string GetCapturedFileChecksum() {
std::lock_guard<std::mutex> lock(mu_);
return captured_file_checksum_;
}
std::string GetCapturedFileChecksumFuncName() {
std::lock_guard<std::mutex> lock(mu_);
return captured_file_checksum_func_name_;
}
int GetCaptureCount() {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
void Reset() {
std::lock_guard<std::mutex> lock(mu_);
captured_file_checksum_.clear();
captured_file_checksum_func_name_.clear();
capture_count_ = 0;
}
private:
std::mutex mu_;
std::string captured_file_checksum_;
std::string captured_file_checksum_func_name_;
int capture_count_ = 0;
};
TEST_F(DBBasicTest, FileChecksumInFileOptions) {
// Verify that file_checksum and file_checksum_func_name from FileMetaData
// are propagated through FileOptions when opening SST files.
auto capturing_fs =
std::make_shared<ChecksumCapturingFS>(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.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
DestroyAndReopen(options);
// Write data and flush to create an SST with a file checksum.
ASSERT_OK(Put("key1", "value1"));
ASSERT_OK(Flush());
// Reset captures, then reopen to trigger TableCache SST open.
capturing_fs->Reset();
Reopen(options);
// Read to trigger SST open through TableCache::GetTableReader.
ASSERT_EQ("value1", Get("key1"));
// Verify that checksum fields were populated.
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_FALSE(capturing_fs->GetCapturedFileChecksum().empty());
ASSERT_NE(capturing_fs->GetCapturedFileChecksumFuncName(),
capturing_fs->GetCapturedFileChecksum());
ASSERT_EQ(capturing_fs->GetCapturedFileChecksumFuncName(),
"FileChecksumCrc32c");
Close();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+9 -1
View File
@@ -710,12 +710,20 @@ class AlwaysTrueBitsBuilder : public FilterBitsBuilder {
count_ = 0;
// Interpreted as "always true" filter (0 probes over 1 byte of
// payload, 5 bytes metadata)
return Slice("\0\0\0\0\0\0", 6);
return Slice("\0\0\0\0\0\0", kAlwaysTrueFilterBytes);
}
using FilterBitsBuilder::Finish;
size_t ApproximateNumEntries(size_t) override { return SIZE_MAX; }
size_t CalculateSpace(size_t /* num_entries */) override {
return kAlwaysTrueFilterBytes;
}
double EstimatedFpRate(size_t /* num_entries */,
size_t /* bytes */) override {
return 1.0;
}
private:
static constexpr size_t kAlwaysTrueFilterBytes = 6;
size_t count_ = 0;
};
+65
View File
@@ -11541,6 +11541,71 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
}
// Test verifies compaction file cutting logic when using tail size estimation
// maintains output files at or below the target file size.
TEST_F(DBCompactionTest, CompactionRespectsTargetSizeWithTailEstimation) {
const int kInitialKeyCount = 10000; // 10k keys
const int kValueSize = 100; // 100 bytes per key
const int kSeed = 301;
Options options = CurrentOptions();
options.target_file_size_is_upper_bound = true;
options.target_file_size_base = 256 * 1024;
options.write_buffer_size = 2 * 1024 * 1024;
options.level0_file_num_compaction_trigger = 100; // Never trigger L0->L1
options.compression = kNoCompression;
BlockBasedTableOptions table_options;
table_options.partition_filters = true;
table_options.metadata_block_size = 4 * 1024;
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Generate 2 L0 files
// Generate first file with 10k keys (each ~100 bytes) approx 1.2MB total
Random rnd(kSeed);
for (int i = 0; i < kInitialKeyCount; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Generate second file with overlapping keys to force compaction (prevent
// trivial move)
for (int i = kInitialKeyCount / 2; i < kInitialKeyCount * 1.5; i++) {
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
}
ASSERT_OK(Flush());
// Capture file metadata and assert two L0 files
std::vector<LiveFileMetaData> file_metadata;
db_->GetLiveFilesMetaData(&file_metadata);
ASSERT_EQ(file_metadata.size(), 2);
for (const auto& file : file_metadata) {
ASSERT_EQ(file.level, 0);
};
// Manually compact LO files to L1
CompactRangeOptions cro;
cro.change_level = true;
cro.target_level = 1;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify that compacted output files are under target file size
for (const auto& file : file_metadata) {
if (file.level > 0) {
EXPECT_LE(file.size, options.target_file_size_base)
<< "Output file size exceeds target size: " << " File: " << file.name
<< " level: " << file.level << " File size: " << file.size
<< " Target size: " << options.target_file_size_base;
}
}
}
class PeriodicCompactionListener : public EventListener {
public:
explicit PeriodicCompactionListener() {}
+1
View File
@@ -109,6 +109,7 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
{
InstrumentedMutexLock l(&mutex_);
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
TEST_SYNC_POINT("DBImpl::GetSortedWalFilesImpl:WaitPurge");
bg_cv_.Wait();
}
+7 -7
View File
@@ -101,7 +101,7 @@ TEST_F(DBFlushTest, SyncFail) {
TEST_SYNC_POINT("DBFlushTest::SyncFail:2");
fault_injection_env->SetFilesystemActive(true);
// Now the background job will do the flush; wait for it.
// Returns the IO error happend during flush.
// Returns the IO error happened during flush.
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
ASSERT_EQ("", FilesPerLevel()); // flush failed.
Destroy(options);
@@ -518,11 +518,11 @@ TEST_F(DBFlushTest, StatisticsGarbageInsertAndDeletes) {
// Note : one set of delete for KEY1, KEY2, KEY3 is written to
// SSTable to propagate the delete operations to K-V pairs
// that could have been inserted into the database during past Flush
// opeartions.
// operations.
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t);
// Additional useful paylaod.
// Additional useful payload.
ASSERT_OK(Delete(KEY4));
ASSERT_OK(Delete(KEY5));
ASSERT_OK(Delete(KEY6));
@@ -614,7 +614,7 @@ TEST_F(DBFlushTest, StatisticsGarbageRangeDeletes) {
// Note : one set of deleteRange for (KEY1, KEY2) and (KEY2, KEY3) is written
// to SSTable to propagate the deleteRange operations to K-V pairs that could
// have been inserted into the database during past Flush opeartions.
// have been inserted into the database during past Flush operations.
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
(KEY1.size() + KEY2.size() + sizeof(uint64_t)) +
(KEY2.size() + KEY3.size() + sizeof(uint64_t));
@@ -842,7 +842,7 @@ TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) {
ASSERT_OK(Put(1, Key(idx), std::string(1, 'v')));
}
// To coerce a manual flush happenning in the middle of GetLiveFiles's flush,
// To coerce a manual flush happening in the middle of GetLiveFiles's flush,
// we need to pause background flush thread and enable it later.
std::shared_ptr<test::SleepingBackgroundTask> sleeping_task =
std::make_shared<test::SleepingBackgroundTask>();
@@ -851,7 +851,7 @@ TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) {
sleeping_task.get(), Env::Priority::HIGH);
sleeping_task->WaitUntilSleeping();
// Coerce a manual flush happenning in the middle of GetLiveFiles's flush
// Coerce a manual flush happening in the middle of GetLiveFiles's flush
bool get_live_files_paused_at_sync_point = false;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::AtomicFlushMemTables:AfterScheduleFlush", [&](void* /* arg */) {
@@ -1428,7 +1428,7 @@ TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
Close();
}
// Create a Compaction Fitler that will be invoked
// Create a Compaction Filter that will be invoked
// at flush time and will update the value of a KV pair
// if the key string is "lower" than the filter_key_ string.
class ConditionalUpdateFilter : public CompactionFilter {
+112 -52
View File
@@ -1177,23 +1177,38 @@ FSDirectory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
}
Status DBImpl::SetOptions(
ColumnFamilyHandle* column_family,
const std::unordered_map<std::string, std::string>& options_map) {
const std::unordered_map<ColumnFamilyHandle*,
std::unordered_map<std::string, std::string>>&
column_families_opts_map) {
// TODO: plumb Env::IOActivity, Env::IOPriority
const ReadOptions read_options;
const WriteOptions write_options;
auto* cfd =
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
if (options_map.empty()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"SetOptions() on column family [%s], empty input",
cfd->GetName().c_str());
return Status::InvalidArgument("empty input");
if (column_families_opts_map.empty()) {
return Status::OK();
}
for (const auto& cf_opts : column_families_opts_map) {
if (cf_opts.second.empty()) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"SetOptions() on column family [%s], empty input",
cf_opts.first->GetName().c_str());
return Status::InvalidArgument("empty input");
}
}
autovector<std::pair<ColumnFamilyData*,
const std::unordered_map<std::string, std::string>*>>
column_family_datas;
for (const auto& cf_opts : column_families_opts_map) {
column_family_datas.push_back(
{static_cast_with_check<ColumnFamilyHandleImpl>(cf_opts.first)->cfd(),
&cf_opts.second});
}
InstrumentedMutexLock ol(&options_mutex_);
MutableCFOptions new_options_copy; // For logging outside of DB mutex
autovector<MutableCFOptions>
new_options_copy; // For logging outside of DB mutex
Status s;
Status persist_options_status;
SuperVersionContext sv_context(/* create_superversion */ true);
@@ -1216,68 +1231,104 @@ Status DBImpl::SetOptions(
// Thus aren't releasing the DB mutex from LogAndApply calling pre_cb,
// through installing the new Version until the end of this block, after
// installing the new SuperVersion.
auto pre_cb = [&]() -> Status {
Status cb_s = cfd->SetOptions(db_options, options_map);
if (cb_s.ok()) {
new_options_copy = cfd->GetLatestMutableCFOptions();
}
return cb_s;
};
VersionEdit dummy_edit;
dummy_edit.MarkNoManifestWriteDummy();
TEST_SYNC_POINT_CALLBACK("DBImpl::SetOptions:dummy_edit", &dummy_edit);
s = versions_->LogAndApply(
cfd, read_options, write_options, &dummy_edit, &mutex_,
directories_.GetDbDir(), false /*new_descriptor_log=*/,
nullptr /*new_opts*/, {} /*manifest_wcb*/, pre_cb);
if (!versions_->io_status().ok()) {
assert(!s.ok());
error_handler_.SetBGError(versions_->io_status(),
BackgroundErrorReason::kManifestWrite);
for (const auto& cfd_opts : column_family_datas) {
auto* cfd = cfd_opts.first;
const auto* options_map_ptr = cfd_opts.second;
auto pre_cb = [&]() -> Status {
Status cb_s = cfd->SetOptions(db_options, *options_map_ptr);
if (cb_s.ok()) {
new_options_copy.emplace_back(cfd->GetLatestMutableCFOptions());
}
return cb_s;
};
s = versions_->LogAndApply(
cfd, read_options, write_options, &dummy_edit, &mutex_,
directories_.GetDbDir(), false /*new_descriptor_log=*/,
nullptr /*new_opts*/, {} /*manifest_wcb*/, pre_cb);
if (!versions_->io_status().ok()) {
assert(!s.ok());
error_handler_.SetBGError(versions_->io_status(),
BackgroundErrorReason::kManifestWrite);
}
if (!s.ok()) {
break;
}
}
if (s.ok()) {
// Trigger possible flush/compactions. This has to be before we persist
// options to file, otherwise there will be a deadlock with writer
// thread.
InstallSuperVersionForConfigChange(cfd, &sv_context);
for (const auto& cfd_opts : column_family_datas) {
InstallSuperVersionForConfigChange(cfd_opts.first, &sv_context);
}
persist_options_status =
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
bg_cv_.SignalAll();
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
assert(cfd->GetLatestMutableCFOptions() ==
cfd->GetCurrentMutableCFOptions());
assert(cfd->GetCurrentMutableCFOptions() ==
cfd->current()->GetMutableCFOptions());
#ifndef NDEBUG
for (size_t i = 0; i < column_family_datas.size(); ++i) {
auto* cfd = column_family_datas[i].first;
assert(new_options_copy[i] == cfd->GetLatestMutableCFOptions());
assert(cfd->GetLatestMutableCFOptions() ==
cfd->GetCurrentMutableCFOptions());
assert(cfd->GetCurrentMutableCFOptions() ==
cfd->current()->GetMutableCFOptions());
}
#endif
}
}
sv_context.Clean();
if (s.ok() && (options_map.count("preserve_internal_time_seconds") > 0 ||
options_map.count("preclude_last_level_data_seconds") > 0)) {
s = RegisterRecordSeqnoTimeWorker();
if (s.ok()) {
bool needs_seqno_worker = false;
for (const auto& cf_opts : column_families_opts_map) {
if (cf_opts.second.count("preserve_internal_time_seconds") > 0 ||
cf_opts.second.count("preclude_last_level_data_seconds") > 0) {
needs_seqno_worker = true;
break;
}
}
if (needs_seqno_worker) {
s = RegisterRecordSeqnoTimeWorker();
}
}
ROCKS_LOG_INFO(
immutable_db_options_.info_log,
"SetOptions() on column family [%s], inputs:", cfd->GetName().c_str());
for (const auto& o : options_map) {
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n", o.first.c_str(),
o.second.c_str());
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"SetOptions() on [%zu] column families, inputs:",
column_family_datas.size());
for (size_t i = 0; i < column_family_datas.size(); ++i) {
const auto* cfd = column_family_datas[i].first;
const auto* options_map_ptr = column_family_datas[i].second;
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Set options on column family [%s] (%zu/%zu), inputs:",
cfd->GetName().c_str(), i, column_family_datas.size());
for (const auto& o : *options_map_ptr) {
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n",
o.first.c_str(), o.second.c_str());
}
}
if (s.ok()) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"[%s] SetOptions() succeeded", cfd->GetName().c_str());
new_options_copy.Dump(immutable_db_options_.info_log.get());
for (size_t i = 0; i < column_family_datas.size(); ++i) {
const auto* cfd = column_family_datas[i].first;
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Set options on column family [%s] (%zu/%zu) succeeded, "
"updated CF options:",
cfd->GetName().c_str(), i, column_family_datas.size());
new_options_copy[i].Dump(immutable_db_options_.info_log.get());
}
if (!persist_options_status.ok()) {
// NOTE: WriteOptionsFile already logs on failure
s = persist_options_status;
}
} else {
persist_options_status.PermitUncheckedError(); // less important
ROCKS_LOG_WARN(immutable_db_options_.info_log, "[%s] SetOptions() failed",
cfd->GetName().c_str());
ROCKS_LOG_WARN(immutable_db_options_.info_log, "SetOptions() failed: %s",
s.ToString().c_str());
}
LogFlush(immutable_db_options_.info_log);
return s;
@@ -4351,7 +4402,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
for (auto* cfd : *versions_->GetColumnFamilySet()) {
if (!cfd->AllowIngestBehind()) {
cfd->current()->storage_info()->UpdateOldestSnapshot(
oldest_snapshot, /*allow_ingest_behind=*/false);
oldest_snapshot, /*allow_ingest_behind=*/false,
cfd->ioptions().user_comparator, cfd->GetFullHistoryTsLow());
if (!cfd->current()
->storage_info()
->BottommostFilesMarkedForCompaction()
@@ -4987,7 +5039,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
}
if (!deleted_files.empty()) {
vstorage->ComputeCompactionScore(cfd->ioptions(),
cfd->GetLatestMutableCFOptions());
cfd->GetLatestMutableCFOptions(),
cfd->GetFullHistoryTsLow());
}
if (edit.GetDeletedFiles().empty()) {
job_context.Clean();
@@ -5519,7 +5572,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
return s;
}
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
if (immutable_db_options_.enable_thread_tracking) {
@@ -5546,7 +5599,7 @@ void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
void DBImpl::EraseThreadStatusDbInfo() const {}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
//
// A global method that can dump out the build version
@@ -6418,8 +6471,11 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
fmeta->file_checksum_func_name, fname,
read_options);
} else {
FileOptions fopts = file_options_;
fopts.file_checksum = fmeta->file_checksum;
fopts.file_checksum_func_name = fmeta->file_checksum_func_name;
s = ROCKSDB_NAMESPACE::VerifySstFileChecksumInternal(
opts, file_options_, read_options, fname, fd.largest_seqno);
opts, fopts, read_options, fname, fd.largest_seqno);
}
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
IOSTATS(bytes_read) - prev_bytes_read);
@@ -6487,12 +6543,15 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
}
std::string file_checksum;
std::string func_name;
FileOptions fopts;
fopts.file_checksum = file_checksum_expected;
fopts.file_checksum_func_name = func_name_expected;
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
func_name_expected, &file_checksum, &func_name,
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
io_tracer_, immutable_db_options_.rate_limiter.get(), read_options,
immutable_db_options_.stats, immutable_db_options_.clock);
immutable_db_options_.stats, immutable_db_options_.clock, fopts);
if (s.ok()) {
assert(func_name_expected == func_name);
if (file_checksum != file_checksum_expected) {
@@ -6851,7 +6910,8 @@ void DBImpl::TriggerPeriodicCompaction() {
if (cfd->GetLatestCFOptions().periodic_compaction_seconds &&
!cfd->queued_for_compaction()) {
cfd->current()->storage_info()->ComputeCompactionScore(
cfd->ioptions(), cfd->GetLatestMutableCFOptions());
cfd->ioptions(), cfd->GetLatestMutableCFOptions(),
cfd->GetFullHistoryTsLow());
EnqueuePendingCompaction(cfd);
if (cfd->queued_for_compaction()) {
ROCKS_LOG_INFO(immutable_db_options_.info_log,
+3 -2
View File
@@ -458,8 +458,9 @@ class DBImpl : public DB {
using DB::SetOptions;
Status SetOptions(
ColumnFamilyHandle* column_family,
const std::unordered_map<std::string, std::string>& options_map) override;
const std::unordered_map<ColumnFamilyHandle*,
std::unordered_map<std::string, std::string>>&
column_families_opts_map) override;
Status SetDBOptions(
const std::unordered_map<std::string, std::string>& options_map) override;
+18 -19
View File
@@ -1449,7 +1449,8 @@ Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
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->tail_size, f->user_defined_timestamps_persisted,
f->min_timestamp, f->max_timestamp);
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
ROCKS_LOG_BUFFER(
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
@@ -1645,8 +1646,8 @@ Status DBImpl::CompactFilesImpl(
// takes running compactions into account (by skipping files that are already
// being compacted). Since we just changed compaction score, we recalculate it
// here.
version->storage_info()->ComputeCompactionScore(cfd->ioptions(),
c->mutable_cf_options());
version->storage_info()->ComputeCompactionScore(
cfd->ioptions(), c->mutable_cf_options(), cfd->GetFullHistoryTsLow());
compaction_job.Prepare(std::nullopt /*subcompact to be computed*/);
@@ -1971,7 +1972,8 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
f->oldest_ancester_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->user_defined_timestamps_persisted, f->min_timestamp,
f->max_timestamp);
}
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
"[%s] Apply version edit:\n%s", cfd->GetName().c_str(),
@@ -2889,16 +2891,8 @@ Status DBImpl::WaitForFlushMemTables(
Status DBImpl::EnableAutoCompaction(
const std::vector<ColumnFamilyHandle*>& column_family_handles) {
Status s;
for (auto cf_ptr : column_family_handles) {
Status status =
this->SetOptions(cf_ptr, {{"disable_auto_compactions", "false"}});
if (!status.ok()) {
s = status;
}
}
return s;
return SetOptions(column_family_handles,
{{"disable_auto_compactions", "false"}});
}
// NOTE: Calling DisableManualCompaction() may overwrite the
@@ -3839,7 +3833,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
->current()
->storage_info()
->ComputeCompactionScore(c->immutable_options(),
c->mutable_cf_options());
c->mutable_cf_options(),
cfd->GetFullHistoryTsLow());
EnqueuePendingCompaction(cfd);
c.reset();
@@ -4096,7 +4091,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
in_file->unique_id,
in_file->compensated_range_deletion_size,
in_file->tail_size,
in_file->user_defined_timestamps_persisted};
in_file->user_defined_timestamps_persisted,
in_file->min_timestamp,
in_file->max_timestamp};
out_files.push_back(std::move(out_file_metadata));
}
@@ -4401,7 +4398,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
->current()
->storage_info()
->ComputeCompactionScore(c->immutable_options(),
c->mutable_cf_options());
c->mutable_cf_options(),
cfd->GetFullHistoryTsLow());
EnqueuePendingCompaction(cfd);
}
}
@@ -4507,7 +4505,7 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
nullptr /* snapshot_checker */, c->compaction_reason());
cfd->compaction_picker()->RegisterCompaction(intended_compaction);
vstorage->ComputeCompactionScore(io, mo);
vstorage->ComputeCompactionScore(io, mo, cfd->GetFullHistoryTsLow());
intended_compaction->FinalizeInputInfo(cfd->current());
return intended_compaction;
@@ -4879,7 +4877,8 @@ void DBImpl::ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
std::unique_ptr<Compaction>& c) {
c->ReleaseCompactionFiles(Status::OK());
cfd->current()->storage_info()->ComputeCompactionScore(
c->immutable_options(), c->mutable_cf_options());
c->immutable_options(), c->mutable_cf_options(),
cfd->GetFullHistoryTsLow());
c.reset();
}
+4 -2
View File
@@ -46,7 +46,8 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
// Since we have some more files to compact, we should also recompute
// compaction score
vstorage->ComputeCompactionScore(cfd->ioptions(),
cfd->GetLatestMutableCFOptions());
cfd->GetLatestMutableCFOptions(),
cfd->GetFullHistoryTsLow());
EnqueuePendingCompaction(cfd);
MaybeScheduleFlushOrCompaction();
}
@@ -143,7 +144,8 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
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->user_defined_timestamps_persisted, f->min_timestamp,
f->max_timestamp);
}
status = versions_->LogAndApply(cfd, read_options, write_options, &edit,
+3
View File
@@ -267,6 +267,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
if (!job_context->HaveSomethingToDelete()) {
mutex_.AssertHeld();
--pending_purge_obsolete_files_;
if (pending_purge_obsolete_files_ == 0) {
bg_cv_.SignalAll();
}
}
});
+2 -1
View File
@@ -657,7 +657,8 @@ Status DBImpl::Recover(
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->tail_size, f->user_defined_timestamps_persisted,
f->min_timestamp, f->max_timestamp);
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"[%s] Moving #%" PRIu64
" from from_level-%d to from_level-%d %" PRIu64
+5
View File
@@ -121,6 +121,11 @@ class DBImplReadOnly : public DBImpl {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DBImpl::FlushWAL;
Status FlushWAL(const FlushWALOptions& /*options*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::IngestExternalFile;
Status IngestExternalFile(
ColumnFamilyHandle* /*column_family*/,
+38 -24
View File
@@ -1102,11 +1102,6 @@ Status DBImplSecondary::InitializeCompactionWorkspace(
return s;
}
ROCKS_LOG_INFO(immutable_db_options_.info_log,
"Initialized compaction workspace with %zu subcompaction "
"progress to resume",
compaction_progress_.size());
return Status::OK();
}
@@ -1219,6 +1214,11 @@ Status DBImplSecondary::PrepareCompactionProgressState() {
return HandleInvalidOrNoCompactionProgress(compaction_progress_file_path,
scan_result);
}
ROCKS_LOG_DEBUG(
immutable_db_options_.info_log,
"Loaded compaction progress with %zu subcompaction(s) from %s",
compaction_progress_.size(), compaction_progress_file_path.c_str());
return s;
} else {
return HandleInvalidOrNoCompactionProgress(
@@ -1299,24 +1299,33 @@ Status DBImplSecondary::CompactWithoutInstallation(
}
Status s;
// TODO(hx235): Resuming compaction is currently incompatible with
// paranoid_file_checks=true because OutputValidator hash verification would
// fail during compaction resumption. Before interruption, resuming
// compaction needs to persist the hash of each output file to enable
// validation after resumption. Alternatively and preferably, we could move
// the output verification to happen immediately after each output file is
// created. This workaround currently disables resuming compaction when
// paranoid_file_checks is enabled. Note that paranoid_file_checks is
// disabled by default.
bool allow_resumption =
options.allow_resumption &&
!cfd->GetLatestMutableCFOptions().paranoid_file_checks;
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
if (options.allow_resumption &&
cfd->GetLatestMutableCFOptions().paranoid_file_checks) {
// TODO(hx235): Resuming compaction is currently incompatible with
// output hash verification (enabled via paranoid_file_checks=true or
// verify_output_flags containing kVerifyIteration) because resumed compaction
// will lose the hash computed before interruption.
// Potential solutions:
// 1. Persist the hash state: Before interruption, save the current hash value
// of each output file to disk, allowing validation to continue correctly
// after resumption.
// 2. Immediate verification: Move output verification to happen
// immediately after each output file is created and closed, eliminating
// the need to maintain hash state across resumption boundaries.
bool output_hash_verification_enabled =
mutable_cf_options.paranoid_file_checks ||
!!(mutable_cf_options.verify_output_flags &
VerifyOutputFlags::kVerifyIteration);
bool allow_resumption =
options.allow_resumption && !output_hash_verification_enabled;
if (options.allow_resumption && output_hash_verification_enabled) {
ROCKS_LOG_WARN(immutable_db_options_.info_log,
"Resume compaction configured but disabled due to "
"incompatible with paranoid_file_checks=true");
"incompatibility with output hash verification "
"(paranoid_file_checks=true or verify_output_flags "
"containing kVerifyIteration)");
}
mutex_.Unlock();
@@ -1345,8 +1354,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
CompactionOptions comp_options;
comp_options.compression = kDisableCompressionOption;
comp_options.output_file_size_limit = MaxFileSizeForLevel(
cfd->GetLatestMutableCFOptions(), input.output_level,
cfd->ioptions().compaction_style, vstorage->base_level(),
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
vstorage->base_level(),
cfd->ioptions().level_compaction_dynamic_level_bytes);
std::vector<CompactionInputFiles> input_files;
@@ -1384,8 +1393,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
}
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
comp_options, input_files, input.output_level, vstorage,
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
earliest_snapshot, job_context.snapshot_checker));
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
job_context.snapshot_checker));
assert(c != nullptr);
c->FinalizeInputInfo(version);
@@ -1731,6 +1740,11 @@ Status DBImplSecondary::FinalizeCompactionProgressWriter(
return HandleCompactionProgressWriterCreationFailure(
"" /* temp_file_path */, final_file_path, compaction_progress_writer);
}
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
"Finalized compaction progress writer onto %s",
final_file_path.c_str());
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+3 -3
View File
@@ -216,9 +216,9 @@ class DBImplSecondary : public DBImpl {
using DBImpl::SetOptions;
Status SetOptions(
ColumnFamilyHandle* /*cfd*/,
const std::unordered_map<std::string, std::string>& /*options_map*/)
override {
const std::unordered_map<ColumnFamilyHandle*,
std::unordered_map<std::string, std::string>>&
/*column_families_opts_map*/) override {
// Currently not supported because changing certain options may cause
// flush/compaction and/or write to MANIFEST.
return Status::NotSupported("Not supported operation in secondary mode.");
+507
View File
@@ -4154,6 +4154,9 @@ INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
::testing::Bool());
TEST_P(DBMultiScanIteratorTest, BasicTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4196,6 +4199,8 @@ TEST_P(DBMultiScanIteratorTest, BasicTest) {
}
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
auto options = CurrentOptions();
DestroyAndReopen(options);
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4602,6 +4607,11 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
ASSERT_OK(s);
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ColumnFamilyMetaData cf_meta;
dbfull()->GetColumnFamilyMetaData(cfh, &cf_meta);
// Only the L0 with range deletion is compacted.
ASSERT_EQ(1, cf_meta.levels[0].files.size());
ASSERT_EQ(0, cf_meta.levels[0].files[0].num_deletions);
// The first scan range overlaps the DB key range, while the second extends
// beyond but overlaps the delete range
@@ -4666,6 +4676,503 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, ReseekAcrossBlocksSameUserKey) {
// This test exposes a bug where multiscan reseeks backwards when
// max_sequential_skip_in_iterations is triggered with the same user key
// spanning multiple data blocks.
auto options = CurrentOptions();
options.max_sequential_skip_in_iterations = 3;
options.compression = kNoCompression;
// Force each internal key into its own block
BlockBasedTableOptions table_options;
table_options.flush_block_policy_factory =
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Taking a snapshot after each Put to preserve all versions during flush.
std::vector<const Snapshot*> snapshots;
for (int i = 0; i < 7; ++i) {
ASSERT_OK(Put("key_a", "value_" + std::to_string(i)));
snapshots.push_back(db_->GetSnapshot());
}
ASSERT_OK(Put("key_b", "value_b"));
ASSERT_OK(Flush());
ASSERT_EQ(1, NumTableFilesAtLevel(0));
// Setup multiscan range covering both keys
std::vector<std::string> key_ranges({"key_a", "key_c"});
ReadOptions ro;
Slice ub = key_ranges[1];
ro.iterate_upper_bound = &ub;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
iter->Prepare(scan_options);
std::vector<std::string> seen_keys;
std::vector<std::string> seen_values;
iter->Seek(key_ranges[0]);
while (iter->status().ok() && iter->Valid()) {
seen_keys.push_back(iter->key().ToString());
seen_values.push_back(iter->value().ToString());
iter->Next();
}
ASSERT_OK(iter->status()) << iter->status().ToString();
ASSERT_EQ(seen_keys.size(), 2) << "Should see key_a and key_b";
ASSERT_EQ(seen_keys[0], "key_a");
ASSERT_EQ(seen_keys[1], "key_b");
ASSERT_EQ(seen_values[0], "value_6");
ASSERT_EQ(seen_values[1], "value_b");
for (auto* snapshot : snapshots) {
db_->ReleaseSnapshot(snapshot);
}
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchAcrossMultipleFiles) {
// Test async prefetch with multiple ranges within a single file
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(303);
// Create a single large file with many keys
// ~1MiB of data
// Should be lots of files now
for (int i = 0; i < 1000; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
// 1KiB values
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 3);
// Set up multiple non-overlapping ranges in the same file
// Every 32 values should be a file or so
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00500", "k00600", "k00800", "k00900"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
auto read_count_before =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
auto read_count_after =
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
ASSERT_EQ(read_count_after, read_count_before);
// Verify all three ranges can be scanned successfully
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
}
}
} catch (MultiScanException& ex) {
// Make sure exception contains the status
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchMultipleLevels) {
// Test async prefetch with files in L0 and non-L0 levels
// Similar setup to AsyncPrefetchAcrossMultipleFiles but with L0 files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
options.statistics = CreateDBStatistics();
DestroyAndReopen(options);
Random rnd(304);
// Create base files and compact to bottom level - ~500KiB of data
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Verify we have files at bottom level
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Create additional L0 files with overlapping key ranges
for (int i = 100; i < 150; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Verify we now have files in both L0 and bottom level
ASSERT_GT(NumTableFilesAtLevel(0), 0);
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up multiple non-overlapping ranges
std::vector<std::string> key_ranges(
{"k00000", "k00100", "k00200", "k00300", "k00400", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
scan_options.insert(key_ranges[4], key_ranges[5]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all three ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys from all three ranges
ASSERT_GT(total_keys, 0);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithDeleteRange) {
// Test async prefetch with delete ranges
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(305);
// Create base data - ~500KiB
for (int i = 0; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
// Add delete ranges
ASSERT_OK(db_->DeleteRange(WriteOptions(), dbfull()->DefaultColumnFamily(),
"k00100", "k00200"));
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
ASSERT_GT(NumTableFilesAtLevel(49), 0);
// Set up scan ranges that interact with delete ranges
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
std::string key = it.first.ToString();
// Verify deleted keys are not returned
ASSERT_TRUE((key < "k00100" || key >= "k00200"));
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
// Should have keys excluding deleted ranges
ASSERT_EQ(total_keys, 400);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
// Test async prefetch with externally ingested files
auto options = CurrentOptions();
options.target_file_size_base = 1 << 15; // 32KiB
options.compaction_style = kCompactionStyleUniversal;
options.num_levels = 50;
options.compression = kNoCompression;
DestroyAndReopen(options);
Random rnd(306);
// Create base data - ~200KiB
for (int i = 0; i < 200; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(Flush());
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
// Create and ingest external SST file with new data
std::string ingest_file = dbname_ + "/test_ingest.sst";
{
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(ingest_file));
for (int i = 300; i < 500; ++i) {
std::stringstream ss;
ss << "k" << std::setw(5) << std::setfill('0') << i;
ASSERT_OK(writer->Put(ss.str(), rnd.RandomString(1 << 10)));
}
ASSERT_OK(writer->Finish());
}
IngestExternalFileOptions ifo;
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
ASSERT_OK(dbfull()->IngestExternalFile(cfh, {ingest_file}, ifo));
// Set up scan ranges that span both regular and ingested files
std::vector<std::string> key_ranges({"k00000", "k00500"});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.use_async_io = true;
scan_options.insert(key_ranges[0], key_ranges[1]);
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
ASSERT_NE(iter, nullptr);
// Verify all ranges can be scanned successfully
int total_keys = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
it.first.ToString();
total_keys++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
} catch (std::logic_error& ex) {
std::cerr << "Iterator returned logic error " << ex.what();
abort();
}
ASSERT_EQ(total_keys, 400);
iter.reset();
}
TEST_P(DBMultiScanIteratorTest, StatisticsTest) {
// Test that multi scan statistics are properly recorded
auto options = CurrentOptions();
options.statistics = CreateDBStatistics();
// Use small block size to ensure multiple blocks
BlockBasedTableOptions table_options;
table_options.block_size = 256;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Create data across multiple blocks
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << std::setw(3) << std::setfill('0') << i;
// Use larger values to ensure multiple blocks
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
}
ASSERT_OK(Flush());
// Reset stats before multi scan
ASSERT_OK(options.statistics->Reset());
// Set up two scan ranges
std::vector<std::string> key_ranges({"k010", "k030", "k060", "k080"});
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert(key_ranges[0], key_ranges[1]);
scan_options.insert(key_ranges[2], key_ranges[3]);
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
// Iterate through all ranges
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
(void)it;
count++;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
}
ASSERT_EQ(count, 40); // 20 keys per range
iter.reset();
// Check statistics
// MULTISCAN_PREPARE_CALLS should be at least 1
ASSERT_GE(TestGetTickerCount(options, MULTISCAN_PREPARE_CALLS), 1);
// MULTISCAN_PREPARE_ERRORS should be 0
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREPARE_ERRORS), 0);
// MULTISCAN_SEEK_ERRORS should be 0
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_SEEK_ERRORS), 0);
// Blocks should be prefetched or from cache
uint64_t blocks_prefetched =
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
uint64_t blocks_from_cache =
TestGetTickerCount(options, MULTISCAN_BLOCKS_FROM_CACHE);
ASSERT_GT(blocks_prefetched + blocks_from_cache, 0);
// If blocks were prefetched, prefetch bytes and IO requests should be > 0
if (blocks_prefetched > 0) {
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BYTES), 0);
uint64_t io_requests = TestGetTickerCount(options, MULTISCAN_IO_REQUESTS);
ASSERT_GT(io_requests, 0);
ASSERT_LE(io_requests, blocks_prefetched);
}
// Wasted blocks should be 0 since we iterated through everything
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
}
TEST_P(DBMultiScanIteratorTest, StatisticsWastedBlocksTest) {
// Test that wasted blocks are tracked when iteration is abandoned early
auto options = CurrentOptions();
options.statistics = CreateDBStatistics();
// Use small block size to ensure multiple blocks
BlockBasedTableOptions table_options;
table_options.block_size = 256;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
// Create data across multiple blocks
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
ss << std::setw(3) << std::setfill('0') << i;
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
}
ASSERT_OK(Flush());
// Reset stats before multi scan
ASSERT_OK(options.statistics->Reset());
// Set up a large scan range
ReadOptions ro;
ro.fill_cache = GetParam();
MultiScanArgs scan_options(BytewiseComparator());
scan_options.insert("k000", "k099");
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
std::unique_ptr<MultiScan> iter =
dbfull()->NewMultiScan(ro, cfh, scan_options);
// Only iterate through a few keys, then abandon
int count = 0;
try {
for (auto range : *iter) {
for (auto it : range) {
(void)it;
count++;
if (count >= 5) {
break; // Abandon iteration early
}
}
if (count >= 5) {
break;
}
}
} catch (MultiScanException& ex) {
ASSERT_NOK(ex.status());
std::cerr << "Iterator returned status " << ex.what();
abort();
}
ASSERT_EQ(count, 5);
// Destroy iterator to trigger wasted blocks counting
iter.reset();
uint64_t blocks_prefetched =
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
// If blocks were prefetched, some should be wasted since we abandoned early
if (blocks_prefetched > 1) {
// We only read a few keys, so there should be wasted blocks
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+40
View File
@@ -1689,6 +1689,46 @@ TEST_F(DBOptionsTest, SetOptionsNoManifestWrite) {
ASSERT_EQ(Get("x"), "x");
}
TEST_F(DBOptionsTest, SetOptionsMultipleColumnFamilies) {
Options options;
options.create_if_missing = true;
options.env = CurrentOptions().env;
options.disable_auto_compactions = true;
Reopen(options);
// Create two additional column families
CreateColumnFamilies({"cf1", "cf2"}, options);
ReopenWithColumnFamilies({"default", "cf1", "cf2"}, options);
// Verify initial state - auto compaction should be disabled
ASSERT_TRUE(dbfull()->GetOptions(handles_[0]).disable_auto_compactions);
ASSERT_TRUE(dbfull()->GetOptions(handles_[1]).disable_auto_compactions);
ASSERT_TRUE(dbfull()->GetOptions(handles_[2]).disable_auto_compactions);
// Set options on multiple column families at once
ASSERT_OK(dbfull()->SetOptions({handles_[1], handles_[2]},
{{"disable_auto_compactions", "false"}}));
ASSERT_TRUE(
dbfull()->GetOptions(handles_[0]).disable_auto_compactions); // unchanged
ASSERT_FALSE(
dbfull()->GetOptions(handles_[1]).disable_auto_compactions); // changed
ASSERT_FALSE(
dbfull()->GetOptions(handles_[2]).disable_auto_compactions); // changed
std::unordered_map<ColumnFamilyHandle*,
std::unordered_map<std::string, std::string>>
options_map;
options_map[handles_[0]] = {{"disable_auto_compactions", "false"}};
options_map[handles_[1]] = {{"disable_auto_compactions", "true"}};
options_map[handles_[2]] = {{"disable_auto_compactions", "true"}};
ASSERT_OK(dbfull()->SetOptions(options_map));
ASSERT_FALSE(dbfull()->GetOptions(handles_[0]).disable_auto_compactions);
ASSERT_TRUE(dbfull()->GetOptions(handles_[1]).disable_auto_compactions);
ASSERT_TRUE(dbfull()->GetOptions(handles_[2]).disable_auto_compactions);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+29 -10
View File
@@ -1519,16 +1519,14 @@ TEST_F(DBPropertiesTest, NeedCompactHintPersistentTest) {
// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage.
TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
// Sampled compression requires at least one of the following four types.
if (!Snappy_Supported() && !Zlib_Supported() && !LZ4_Supported() &&
!ZSTD_Supported()) {
return;
}
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.table_properties_collector_factories.emplace_back(
std::make_shared<BlockCountingTablePropertiesCollectorFactory>());
options.compression = kNoCompression;
bool fast_sampling_supported = Snappy_Supported() || LZ4_Supported();
bool slow_sampling_supported = ZSTD_Supported() || Zlib_Supported();
for (bool sample_for_compression : {false, true}) {
// For simplicity/determinism, sample 100% when enabled, or 0% when disabled
@@ -1542,10 +1540,11 @@ TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
// L1_0 ["a", "b"]
//
// L0_0 was created by flush. L1_0 was created by compaction. Each file
// contains one data block.
// contains one data block with enough data to be compressible.
for (int i = 0; i < 3; ++i) {
ASSERT_OK(Put("a", "val"));
ASSERT_OK(Put("b", "val"));
for (int j = 0; j < 50; ++j) {
ASSERT_OK(Put(std::to_string(j), "thisismyvalue"));
}
ASSERT_OK(Flush());
if (i == 1) {
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
@@ -1558,13 +1557,33 @@ TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props));
ASSERT_EQ(2, file_to_props.size());
for (const auto& file_and_props : file_to_props) {
auto& user_props = file_and_props.second->user_collected_properties;
auto& props = *file_and_props.second;
auto& user_props = props.user_collected_properties;
ASSERT_TRUE(user_props.find(BlockCountingTablePropertiesCollector::
kNumSampledBlocksPropertyName) !=
user_props.end());
ASSERT_EQ(user_props.at(BlockCountingTablePropertiesCollector::
kNumSampledBlocksPropertyName),
std::to_string(sample_for_compression ? 1 : 0));
if (sample_for_compression) {
EXPECT_GT(props.fast_compression_estimated_data_size, 0);
EXPECT_GT(props.slow_compression_estimated_data_size, 0);
if (fast_sampling_supported) {
EXPECT_LT(props.fast_compression_estimated_data_size,
props.data_size);
if (slow_sampling_supported) {
EXPECT_LT(props.slow_compression_estimated_data_size,
props.fast_compression_estimated_data_size);
}
}
if (slow_sampling_supported) {
EXPECT_LT(props.slow_compression_estimated_data_size,
props.data_size);
}
} else {
EXPECT_EQ(props.fast_compression_estimated_data_size, 0);
EXPECT_EQ(props.slow_compression_estimated_data_size, 0);
}
}
}
}
+83
View File
@@ -3825,6 +3825,89 @@ TEST_F(DBRangeDelTest, RowCache) {
// and should not turn db into read-only mdoe.
ASSERT_OK(Put(Key(5), "foo"));
}
TEST_F(DBRangeDelTest, SeekForPrevTest) {
// open db
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.compaction_style = kCompactionStyleUniversal;
// add SST partitioner, split sst file with prefix length 2
options.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(2);
Reopen(options);
// File uses SST partitioner, so it will be split into 3 files
// SST file 1: ka1, ka2
// SST file 2: kb1
// SST file 3: kc1, kc2
// Delete range covers from ka2 to kc2, which means record ka2 and kb1, kc1
// are covered by the delete range
std::vector<std::pair<std::string, std::string>> kv = {{"ka1", "value_1"},
{"ka2", "value_2"},
{"kb1", "value_3"},
{"kc1", "value_4"},
{"kc2", "value_5"}};
for (auto& p : kv) {
ASSERT_OK(Put(p.first, p.second));
}
ASSERT_OK(Flush());
// Compact to Lmax, it should have seq 0 now.
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Open an iterator and create a snapshot, so that keys are not deleted
// completely by delete range in SST
ReadOptions read_opts;
read_opts.snapshot = db_->GetSnapshot();
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
iter->SeekToFirst();
// iterate all the keys and validate the value
for (int i = 0; iter->Valid(); iter->Next()) {
ASSERT_EQ(kv[i].first, iter->key().ToString());
ASSERT_EQ(kv[i].second, iter->value().ToString());
i++;
}
// use delete range to delete the record
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "ka2",
"kc2"));
// Flush
ASSERT_OK(Flush());
// Compact to Lmax
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Close the iterator and release the snapshot.
ASSERT_OK(iter->status());
iter.reset();
db_->ReleaseSnapshot(read_opts.snapshot);
// create second iterator, seek each key and validate result
std::unique_ptr<Iterator> iter2(db_->NewIterator(ReadOptions()));
// Validate keys are deleted
iter2->SeekToFirst();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("ka1", iter2->key().ToString());
iter2->Next();
ASSERT_TRUE(iter2->Valid());
ASSERT_EQ("kc2", iter2->key().ToString());
iter2->Next();
ASSERT_FALSE(iter2->Valid());
// Validate seek for prev result
for (auto& p : kv) {
iter2->SeekForPrev(p.first);
ASSERT_TRUE(iter2->Valid());
if (p.first == "kc2") {
ASSERT_EQ("kc2", iter2->key().ToString());
} else {
ASSERT_EQ("ka1", iter2->key().ToString());
}
}
ASSERT_OK(iter2->status());
iter2.reset();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+4 -4
View File
@@ -5179,7 +5179,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
}
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
namespace {
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
int expected_count) {
@@ -5637,7 +5637,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
}
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
TEST_F(DBTest, FlushOnDestroy) {
WriteOptions wo;
@@ -6372,9 +6372,9 @@ TEST_F(DBTest, MergeTestTime) {
ASSERT_EQ(1, count);
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
}
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
+81
View File
@@ -6544,6 +6544,9 @@ TEST_F(DBTest2, LastLevelStatistics) {
DestroyAndReopen(options);
get_iostats_context()->Reset();
IOStatsContext* iostats = get_iostats_context();
// generate 1 sst on level 0
ASSERT_OK(Put("foo1", "bar"));
ASSERT_OK(Put("bar", "bar"));
@@ -6644,9 +6647,87 @@ TEST_F(DBTest2, LastLevelStatistics) {
// Control
ASSERT_NE(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT),
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT));
// Control: unknown temperature iostats should be zero since files have
// explicit temperatures (mapped or written)
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
0);
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
0);
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read, 0);
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_last_level_read_count, 0);
}
}
// Test the iostats for files with Temperature::kUnknown that is not mapped
// to another temperature. These stats are used to indicate which non-tiered
// workloads are most promising for tiering (so this test doesn't set
// temperatures).
TEST_F(DBTest2, UnknownLastLevelStatistics) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
BlockBasedTableOptions bbto;
bbto.no_block_cache = true;
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
DestroyAndReopen(options);
get_iostats_context()->Reset();
IOStatsContext* iostats = get_iostats_context();
// Generate 1 sst file on level 0 with kUnknown temperature
ASSERT_OK(Put("foo", "bar"));
ASSERT_OK(Flush());
// Read from the kUnknown file on non-last level
ASSERT_EQ("bar", Get("foo"));
// Verify unknown_non_last_level stats are populated
EXPECT_GT(
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
0);
EXPECT_GT(
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
0);
// No reads from last level yet
EXPECT_EQ(iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read,
0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.unknown_last_level_read_count,
0);
// Compact to the last level (level 6) explicitly using MoveFilesToLevel
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
MoveFilesToLevel(6);
// Reopen DB to ensure table cache is cleared and files are re-opened
// with correct is_last_level flag
Reopen(options);
// Reset iostats to measure only the following reads
get_iostats_context()->Reset();
// Read from the file now on last level (still kUnknown since
// last_level_temperature is not set)
ASSERT_EQ("bar", Get("foo"));
// Verify unknown_last_level stats are populated
EXPECT_GT(iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read,
0);
EXPECT_GT(iostats->file_io_stats_by_temperature.unknown_last_level_read_count,
0);
// No new reads from non-last level
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
0);
EXPECT_EQ(
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
0);
}
TEST_F(DBTest2, CheckpointFileTemperature) {
class NoLinkTestFS : public FileTemperatureTestFS {
using FileTemperatureTestFS::FileTemperatureTestFS;
+1 -1
View File
@@ -1613,7 +1613,7 @@ TEST_F(DBWALTest, SyncWalPartialFailure) {
return s;
}
AcqRelAtomic<uint32_t> syncs_before_failure_{UINT32_MAX};
Atomic<uint32_t> syncs_before_failure_{UINT32_MAX};
protected:
class MyTestWritableFile : public FSWritableFileOwnerWrapper {
+499
View File
@@ -7,9 +7,13 @@
// 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 <set>
#include "db/column_family.h"
#include "db/compaction/compaction.h"
#include "db/db_test_util.h"
#include "port/stack_trace.h"
#include "rocksdb/sst_file_reader.h"
#include "test_util/testutil.h"
namespace ROCKSDB_NAMESPACE {
@@ -48,6 +52,122 @@ class TimestampCompatibleCompactionTest : public DBTestBase {
}
return value;
}
// Helper to get all files with their level and timestamps
std::vector<std::tuple<int, std::string, std::string>>
GetAllFileTimestamps() {
std::vector<std::tuple<int, std::string, std::string>> results;
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
auto* vstorage = cfd->current()->storage_info();
for (int level = 0; level < cfd->NumberLevels(); level++) {
for (auto* file : vstorage->LevelFiles(level)) {
results.emplace_back(level, file->min_timestamp, file->max_timestamp);
}
}
return results;
}
// Helper to compute overall min/max timestamps across all files
// Returns {min_ts, max_ts} as uint64_t values
// Asserts that all files have non-empty timestamps
std::pair<uint64_t, uint64_t> GetOverallTimestampRange() {
auto files = GetAllFileTimestamps();
EXPECT_GE(files.size(), 1U);
uint64_t overall_min = UINT64_MAX;
uint64_t overall_max = 0;
for (const auto& [level, min_ts, max_ts] : files) {
EXPECT_FALSE(min_ts.empty()) << "min_timestamp empty at level " << level;
EXPECT_FALSE(max_ts.empty()) << "max_timestamp empty at level " << level;
if (!min_ts.empty() && !max_ts.empty()) {
uint64_t file_min = DecodeFixed64(min_ts.data());
uint64_t file_max = DecodeFixed64(max_ts.data());
overall_min = std::min(overall_min, file_min);
overall_max = std::max(overall_max, file_max);
}
}
return {overall_min, overall_max};
}
// Helper to verify timestamp range matches expected values, including after
// reopen
void VerifyTimestampRangeWithPersistence(const Options& options,
uint64_t expected_min,
uint64_t expected_max) {
// Verify before reopen
auto [min_ts, max_ts] = GetOverallTimestampRange();
ASSERT_EQ(expected_min, min_ts);
ASSERT_EQ(expected_max, max_ts);
size_t file_count_before = GetAllFileTimestamps().size();
// Verify manifest persistence by reopening
Reopen(options);
// Verify after reopen
auto [reopened_min_ts, reopened_max_ts] = GetOverallTimestampRange();
ASSERT_EQ(expected_min, reopened_min_ts);
ASSERT_EQ(expected_max, reopened_max_ts);
ASSERT_EQ(file_count_before, GetAllFileTimestamps().size());
}
// Helper to create common options for UDT tests with level compaction
Options CreateTimestampOptions(bool disable_auto_compactions = false) {
Options options = CurrentOptions();
options.env = env_;
options.compaction_style = kCompactionStyleLevel;
options.num_levels = 4;
options.persist_user_defined_timestamps = true;
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
options.disable_auto_compactions = disable_auto_compactions;
return options;
}
// Helper to write test data with alternating timestamps in a range
// Writes keys [start_key, end_key) with timestamps alternating between
// min_ts and max_ts
void WriteDataWithTimestampRange(int start_key, int end_key, uint64_t min_ts,
uint64_t max_ts) {
std::string ts_buf;
for (int i = start_key; i < end_key; i++) {
ts_buf.clear();
uint64_t ts = (i % 2 == 0) ? min_ts : max_ts;
PutFixed64(&ts_buf, ts);
ASSERT_OK(db_->Put(WriteOptions(), Key(i), ts_buf,
"value" + std::to_string(i)));
}
}
// Helper to check if any file has the expected timestamp range
bool HasFileWithTimestampRange(uint64_t expected_min, uint64_t expected_max) {
auto file_timestamps = GetAllFileTimestamps();
for (const auto& [level, min_ts, max_ts] : file_timestamps) {
if (!min_ts.empty() && !max_ts.empty()) {
uint64_t file_min = DecodeFixed64(min_ts.data());
uint64_t file_max = DecodeFixed64(max_ts.data());
if (file_min == expected_min && file_max == expected_max) {
return true;
}
}
}
return false;
}
// Helper to verify data is readable with a given timestamp
void VerifyDataReadable(int key, const std::string& expected_value,
uint64_t read_ts) {
std::string value;
std::string ts_buf;
PutFixed64(&ts_buf, read_ts);
ReadOptions read_opts;
Slice ts_slice(ts_buf);
read_opts.timestamp = &ts_slice;
ASSERT_OK(db_->Get(read_opts, Key(key), &value));
ASSERT_EQ(expected_value, value);
}
};
TEST_F(TimestampCompatibleCompactionTest, UserKeyCrossFileBoundary) {
@@ -344,6 +464,385 @@ TEST_F(TimestampCompatibleCompactionTest, EmptyCompactionOutput) {
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
}
TEST_F(TimestampCompatibleCompactionTest, SeqnoZeroingWithUDT) {
// This test validates that seqno is only zeroed when the timestamp is older
// than full_history_ts_low_. Before the fix, seqno was incorrectly zeroed
// even when UDT was enabled but timestamp wasn't old enough.
Options options = CurrentOptions();
options.env = env_;
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
// Track seqno zeroing events and which keys are zeroed
std::set<std::string> zeroed_keys;
SyncPoint::GetInstance()->SetCallBack(
"CompactionIterator::PrepareOutput:ZeroingSeq", [&](void* arg) {
auto* ikey = static_cast<ParsedInternalKey*>(arg);
ASSERT_EQ(0, ikey->sequence);
// Extract user key without timestamp (last 8 bytes)
Slice user_key_with_ts = ikey->user_key;
std::string user_key =
user_key_with_ts.ToString().substr(0, user_key_with_ts.size() - 8);
zeroed_keys.insert(user_key);
});
SyncPoint::GetInstance()->EnableProcessing();
// Case 1: Test that seqno is NOT zeroed when full_history_ts_low is not set
// Write a key with timestamp 100
std::string ts_str = Timestamp(100);
ASSERT_OK(db_->Put(WriteOptions(), "key1", ts_str, "value1"));
ASSERT_OK(Flush());
zeroed_keys.clear();
{
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
}
// With UDT enabled and no full_history_ts_low, seqno should NOT be zeroed
ASSERT_TRUE(zeroed_keys.empty());
// Case 2: Test that seqno IS zeroed when timestamp < full_history_ts_low
// Write a new key with timestamp 200
ts_str = Timestamp(200);
ASSERT_OK(db_->Put(WriteOptions(), "key2", ts_str, "value2"));
ASSERT_OK(Flush());
zeroed_keys.clear();
{
// Set full_history_ts_low to 300, so ts < 300 should be zeroed
std::string full_history_ts_low = Timestamp(300);
Slice ts_slice = full_history_ts_low;
CompactRangeOptions cro;
cro.full_history_ts_low = &ts_slice;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
}
// key1 (ts=100) and key2 (ts=200) both have ts < 300, so both should be
// zeroed
ASSERT_EQ(2u, zeroed_keys.size());
ASSERT_TRUE(zeroed_keys.count("key1") > 0);
ASSERT_TRUE(zeroed_keys.count("key2") > 0);
// Case 3: Write a new key with timestamp >= full_history_ts_low
// and verify it is NOT zeroed while old keys are re-zeroed
ts_str = Timestamp(500);
ASSERT_OK(db_->Put(WriteOptions(), "key3", ts_str, "value3"));
ASSERT_OK(Flush());
zeroed_keys.clear();
{
// Set full_history_ts_low to 400
// key1 (ts=100) and key2 (ts=200) have ts < 400, will be re-processed
// key3 (ts=500) has ts >= 400, should NOT be zeroed
std::string full_history_ts_low = Timestamp(400);
Slice ts_slice = full_history_ts_low;
CompactRangeOptions cro;
cro.full_history_ts_low = &ts_slice;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
}
// key3 should NOT appear in zeroed_keys since ts=500 >= 400
ASSERT_TRUE(zeroed_keys.count("key3") == 0);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Verify data is still readable
std::string value;
ts_str = Timestamp(600);
Slice read_ts = ts_str;
ReadOptions read_opts;
read_opts.timestamp = &read_ts;
ASSERT_OK(db_->Get(read_opts, "key1", &value));
ASSERT_EQ("value1", value);
ASSERT_OK(db_->Get(read_opts, "key2", &value));
ASSERT_EQ("value2", value);
ASSERT_OK(db_->Get(read_opts, "key3", &value));
ASSERT_EQ("value3", value);
}
// Test that files with max_timestamp >= full_history_ts_low are not marked
// for bottommost compaction, which prevents infinite compaction loops.
TEST_F(TimestampCompatibleCompactionTest,
BottommostCompactionRespectsFullHistoryTsLow) {
Options options = CreateTimestampOptions();
options.level0_file_num_compaction_trigger = 4;
DestroyAndReopen(options);
// Write some data with timestamps 100-199
std::string ts_buf;
for (int i = 0; i < 100; i++) {
ts_buf.clear();
PutFixed64(&ts_buf, 100 + i);
ASSERT_OK(
db_->Put(WriteOptions(), Key(i), ts_buf, "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
// Compact to the bottommost level
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// Set full_history_ts_low to 150 - files with max_ts >= 150 should NOT be
// marked for bottommost compaction since seqno cannot be zeroed
ts_buf.clear();
PutFixed64(&ts_buf, 150);
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
// Release a snapshot to potentially trigger bottommost file marking
// but files should NOT be marked because max_ts (199) >= full_history_ts_low
// (150)
const Snapshot* snap = db_->GetSnapshot();
db_->ReleaseSnapshot(snap);
// Wait for any scheduled compactions - should complete without infinite loop
// Use a reasonable timeout to detect infinite loops
WaitForCompactOptions wfc_options;
wfc_options.timeout = std::chrono::microseconds(5000000); // 5 seconds
Status s = dbfull()->WaitForCompact(wfc_options);
// Should succeed without timeout (no infinite compaction loop)
ASSERT_TRUE(s.ok() || s.IsTimedOut());
if (s.IsTimedOut()) {
// If timeout, the fix is not working - this should not happen
FAIL() << "WaitForCompact timed out - possible infinite compaction loop";
}
// Now set full_history_ts_low beyond max timestamp in the file (200+)
// This should allow the file to be properly marked and compacted
ts_buf.clear();
PutFixed64(&ts_buf, 300);
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
// Trigger another snapshot release to potentially mark files
snap = db_->GetSnapshot();
db_->ReleaseSnapshot(snap);
// Now compaction should clean up the file.
ASSERT_OK(dbfull()->TEST_WaitForCompact());
}
// Test that files are NOT marked for bottommost compaction when UDT is enabled
// and full_history_ts_low has never been set (empty).
TEST_F(TimestampCompatibleCompactionTest,
BottommostCompactionSkipsWhenFullHistoryTsLowNotSet) {
Options options = CreateTimestampOptions();
DestroyAndReopen(options);
// Write some data with timestamps 100-199
std::string ts_buf;
for (int i = 0; i < 100; i++) {
ts_buf.clear();
PutFixed64(&ts_buf, 100 + i);
ASSERT_OK(
db_->Put(WriteOptions(), Key(i), ts_buf, "value" + std::to_string(i)));
}
ASSERT_OK(Flush());
// Compact to the bottommost level without setting full_history_ts_low
CompactRangeOptions cro;
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
// Verify files have valid max_timestamp
auto file_timestamps = GetAllFileTimestamps();
ASSERT_GE(file_timestamps.size(), 1U);
for (const auto& [level, min_ts, max_ts] : file_timestamps) {
ASSERT_FALSE(max_ts.empty()) << "max_timestamp should not be empty";
}
// full_history_ts_low is NOT set (empty), so files should NOT be marked
// for bottommost compaction even after releasing a snapshot.
// This tests the branch: if (full_history_ts_low.empty()) { continue; }
const Snapshot* snap = db_->GetSnapshot();
db_->ReleaseSnapshot(snap);
// Wait for any scheduled compactions
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Now set full_history_ts_low to a value > max_timestamp (199) in the file
// This should allow the file to be properly marked and compacted
ts_buf.clear();
PutFixed64(&ts_buf, 300);
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
// Trigger another snapshot release to potentially mark files
snap = db_->GetSnapshot();
db_->ReleaseSnapshot(snap);
// Now compaction should be able to proceed since full_history_ts_low is set
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify data is still readable
VerifyDataReadable(0, "value0", 250);
}
// Test that ingested SST files created with UDT have their min/max timestamps
// properly extracted from table properties and populated in FileMetaData.
// This verifies the fix in external_sst_file_ingestion_job.cc that calls
// ExtractTimestampFromTableProperties after creating FileMetaData.
TEST_F(TimestampCompatibleCompactionTest,
IngestedFileTimestampsExtractedFromTableProperties) {
Options options = CreateTimestampOptions();
DestroyAndReopen(options);
// Create an SST file WITH timestamps using SstFileWriter
std::string sst_file = dbname_ + "/ingested_udt_file.sst";
const uint64_t kMinTs = 100;
const uint64_t kMaxTs = 200;
{
SstFileWriter sst_file_writer(EnvOptions(), options);
ASSERT_OK(sst_file_writer.Open(sst_file));
std::string ts_buf;
for (int i = 0; i < 10; i++) {
// Alternate between min and max timestamps
uint64_t ts = (i % 2 == 0) ? kMinTs : kMaxTs;
ts_buf.clear();
PutFixed64(&ts_buf, ts);
// SstFileWriter with UDT comparator requires key with timestamp
ASSERT_OK(
sst_file_writer.Put(Key(i), ts_buf, "value" + std::to_string(i)));
}
ASSERT_OK(sst_file_writer.Finish());
}
// Verify the SST file has timestamp properties before ingestion
{
std::unique_ptr<SstFileReader> reader(new SstFileReader(options));
ASSERT_OK(reader->Open(sst_file));
auto props = reader->GetTableProperties();
auto& user_collected = props->user_collected_properties;
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") !=
user_collected.end())
<< "SST file should have rocksdb.timestamp_min property";
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") !=
user_collected.end())
<< "SST file should have rocksdb.timestamp_max property";
}
// Ingest the SST file
IngestExternalFileOptions ifo;
ifo.move_files = false;
ASSERT_OK(db_->IngestExternalFile({sst_file}, ifo));
// Verify the ingested file has proper timestamps in FileMetaData
ASSERT_TRUE(HasFileWithTimestampRange(kMinTs, kMaxTs))
<< "Ingested file should have min_timestamp=" << kMinTs
<< " and max_timestamp=" << kMaxTs << " in FileMetaData";
// Verify timestamps persist after reopen
Reopen(options);
ASSERT_TRUE(HasFileWithTimestampRange(kMinTs, kMaxTs))
<< "Ingested file timestamps should persist after reopen";
// Verify data is readable
VerifyDataReadable(0, "value0", kMaxTs);
// Clean up
ASSERT_OK(env_->DeleteFile(sst_file));
}
// Test that min/max timestamps are correctly tracked in FileMetaData and
// persisted in the manifest during flush.
TEST_F(TimestampCompatibleCompactionTest, TimestampRangePersistenceFlush) {
Options options = CreateTimestampOptions();
DestroyAndReopen(options);
// Expected timestamp range
const uint64_t kMinTs = 100;
const uint64_t kMaxTs = 200;
// Write data with specific timestamp range
WriteDataWithTimestampRange(0, 50, kMinTs, kMaxTs);
ASSERT_OK(Flush());
// First verify table properties have the timestamps
// (this confirms TimestampTablePropertiesCollector is working)
TablePropertiesCollection props;
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
ASSERT_EQ(1U, props.size());
for (const auto& item : props) {
auto& user_collected = item.second->user_collected_properties;
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") !=
user_collected.end());
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") !=
user_collected.end());
// Verify the collected timestamps match expected values
std::string collected_min_ts = user_collected.at("rocksdb.timestamp_min");
std::string collected_max_ts = user_collected.at("rocksdb.timestamp_max");
ASSERT_EQ(kMinTs, DecodeFixed64(collected_min_ts.data()));
ASSERT_EQ(kMaxTs, DecodeFixed64(collected_max_ts.data()));
}
// Verify FileMetaData timestamps and persistence through reopen
VerifyTimestampRangeWithPersistence(options, kMinTs, kMaxTs);
// Verify we can still read the data
VerifyDataReadable(0, "value0", kMaxTs);
}
// Test that min/max timestamps are correctly merged during compaction
// and persisted in the manifest.
TEST_F(TimestampCompatibleCompactionTest, TimestampRangePersistenceCompaction) {
Options options = CreateTimestampOptions(true /* disable_auto_compactions */);
DestroyAndReopen(options);
// Create multiple L0 files with different timestamp ranges
// File 1: timestamps 100-150
const uint64_t kFile1MinTs = 100;
const uint64_t kFile1MaxTs = 150;
WriteDataWithTimestampRange(0, 10, kFile1MinTs, kFile1MaxTs);
ASSERT_OK(Flush());
// File 2: timestamps 50-80 (earlier range)
const uint64_t kFile2MinTs = 50;
const uint64_t kFile2MaxTs = 80;
WriteDataWithTimestampRange(10, 20, kFile2MinTs, kFile2MaxTs);
ASSERT_OK(Flush());
// File 3: timestamps 200-300 (later range)
const uint64_t kFile3MinTs = 200;
const uint64_t kFile3MaxTs = 300;
WriteDataWithTimestampRange(20, 30, kFile3MinTs, kFile3MaxTs);
ASSERT_OK(Flush());
// Expected combined range: min=50, max=300
const uint64_t kExpectedMinTs = 50;
const uint64_t kExpectedMaxTs = 300;
// Verify we have 3 L0 files before compaction with valid timestamps
auto files_before = GetAllFileTimestamps();
ASSERT_EQ(3U, files_before.size());
for (const auto& [level, min_ts, max_ts] : files_before) {
ASSERT_EQ(0, level); // All files should be in L0
ASSERT_FALSE(min_ts.empty());
ASSERT_FALSE(max_ts.empty());
}
// Trigger compaction
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Verify timestamp range and persistence through reopen
VerifyTimestampRangeWithPersistence(options, kExpectedMinTs, kExpectedMaxTs);
// Verify data is still readable
VerifyDataReadable(0, "value0", kExpectedMaxTs);
VerifyDataReadable(15, "value15", kExpectedMaxTs);
VerifyDataReadable(25, "value25", kExpectedMaxTs);
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+3 -3
View File
@@ -741,7 +741,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
ExternalSstFileInfo external_info;
ASSERT_OK(sst_file_writer.Finish(&external_info));
}
AcqRelAtomic<bool> parallel_ingest_completed{false};
Atomic<bool> parallel_ingest_completed{false};
port::Thread parallel_ingest{[&]() {
IngestExternalFileOptions ingest_opts;
ingest_opts.move_files = true; // faster than copy
@@ -750,7 +750,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
parallel_ingest_completed.Store(true);
}};
AcqRelAtomic<bool> flush_completed{false};
Atomic<bool> flush_completed{false};
port::Thread parallel_flush{[&]() {
FlushOptions flush_opts;
// NB: Flush with wait=false case is tested above in LockWALInEffect
@@ -762,7 +762,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
flush_completed.Store(true);
}};
AcqRelAtomic<bool> parallel_put_completed{false};
Atomic<bool> parallel_put_completed{false};
port::Thread parallel_put{[&]() {
// This can make certain failure scenarios more likely:
// sleep(1);
+11 -9
View File
@@ -158,15 +158,17 @@ Status UpdateManifestForFilesState(
// Current state inconsistent with manifest
++files_updated;
edit.DeleteFile(level, number);
edit.AddFile(
level, number, lf->fd.GetPathId(), lf->fd.GetFileSize(),
lf->smallest, lf->largest, lf->fd.smallest_seqno,
lf->fd.largest_seqno, lf->marked_for_compaction, temp,
lf->oldest_blob_file_number, lf->oldest_ancester_time,
lf->file_creation_time, lf->epoch_number, lf->file_checksum,
lf->file_checksum_func_name, lf->unique_id,
lf->compensated_range_deletion_size, lf->tail_size,
lf->user_defined_timestamps_persisted);
edit.AddFile(level, lf->fd.GetNumber(), lf->fd.GetPathId(),
lf->fd.GetFileSize(), lf->smallest, lf->largest,
lf->fd.smallest_seqno, lf->fd.largest_seqno,
lf->marked_for_compaction, temp,
lf->oldest_blob_file_number,
lf->oldest_ancester_time, lf->file_creation_time,
lf->epoch_number, lf->file_checksum,
lf->file_checksum_func_name, lf->unique_id,
lf->compensated_range_deletion_size, lf->tail_size,
lf->user_defined_timestamps_persisted,
lf->min_timestamp, lf->max_timestamp);
}
}
} else {
+22 -3
View File
@@ -11,6 +11,7 @@
#include <unordered_set>
#include <vector>
#include "db/builder.h"
#include "db/db_impl/db_impl.h"
#include "db/version_edit.h"
#include "file/file_util.h"
@@ -299,6 +300,18 @@ Status ExternalSstFileIngestionJob::Prepare(
// ingestion.
// TODO: plumb Env::IOActivity
ReadOptions ro;
// Pass user-provided checksums through FileOptions when available.
// The caller may not have provided checksums at all (empty vectors),
// so we guard with a bounds check.
FileOptions fopts;
if (i < files_checksums.size()) {
fopts.file_checksum = files_checksums[i];
}
if (i < files_checksum_func_names.size()) {
fopts.file_checksum_func_name = files_checksum_func_names[i];
} else {
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
}
IOStatus io_s = GenerateOneFileChecksum(
fs_.get(), files_to_ingest_[i].internal_file_path,
db_options_.file_checksum_gen_factory.get(),
@@ -307,7 +320,7 @@ Status ExternalSstFileIngestionJob::Prepare(
ingestion_options_.verify_checksums_readahead_size,
db_options_.allow_mmap_reads, io_tracer_,
db_options_.rate_limiter.get(), ro, db_options_.stats,
db_options_.clock);
db_options_.clock, fopts);
if (!io_s.ok()) {
status = io_s;
ROCKS_LOG_WARN(db_options_.info_log,
@@ -699,9 +712,13 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
? kReservedEpochNumberForFileIngestedBehind
: cfd_->NewEpochNumber(), // orders files ingested to L0
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
tail_size, file->user_defined_timestamps_persisted);
tail_size, file->user_defined_timestamps_persisted, "", "");
f_metadata.temperature = file->file_temperature;
f_metadata.marked_for_compaction = marked_for_compaction;
// Extract min/max timestamps from table properties for UDT support.
// This ensures ingested files have proper timestamp ranges in FileMetaData,
// similar to files created by flush and compaction.
ExtractTimestampFromTableProperties(file->table_properties, &f_metadata);
edit_.AddFile(file->picked_level, f_metadata);
*batch_uppermost_level =
@@ -1477,13 +1494,15 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
// TODO: rate limit file reads for checksum calculation during file ingestion.
// TODO: plumb Env::IOActivity
ReadOptions ro;
FileOptions gen_fopts;
gen_fopts.file_checksum_func_name = kNoFileChecksumFuncName;
IOStatus io_s = GenerateOneFileChecksum(
fs_.get(), file_to_ingest->internal_file_path,
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
&file_checksum, &file_checksum_func_name,
ingestion_options_.verify_checksums_readahead_size,
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
ro, db_options_.stats, db_options_.clock);
ro, db_options_.stats, db_options_.clock, gen_fopts);
if (!io_s.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log, "Failed to generate checksum for %s: %s",
+2 -1
View File
@@ -1095,7 +1095,8 @@ Status FlushJob::WriteLevel0Table() {
meta_.file_creation_time, meta_.epoch_number,
meta_.file_checksum, meta_.file_checksum_func_name,
meta_.unique_id, meta_.compensated_range_deletion_size,
meta_.tail_size, meta_.user_defined_timestamps_persisted);
meta_.tail_size, meta_.user_defined_timestamps_persisted,
meta_.min_timestamp, meta_.max_timestamp);
edit_->SetBlobFileAdditions(std::move(blob_file_additions));
}
// Piggyback FlushJobInfo on the first first flushed memtable.
+4 -2
View File
@@ -310,8 +310,10 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
std::unique_ptr<FSRandomAccessFile> sst_file;
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
status =
fs_->NewRandomAccessFile(external_file, env_options_, &sst_file, nullptr);
FileOptions fo{env_options_};
fo.file_checksum = file_meta.file_checksum;
fo.file_checksum_func_name = file_meta.file_checksum_func_name;
status = fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
if (!status.ok()) {
return status;
}
+7 -17
View File
@@ -163,9 +163,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
options.max_bytes_for_level_base = options.target_file_size_base * 2;
options.max_bytes_for_level_multiplier = 2;
options.compression = kNoCompression;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.level0_file_num_compaction_trigger = kNumL0Files;
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
@@ -229,7 +227,7 @@ class TestFlushListener : public EventListener {
ASSERT_EQ(info.file_checksum, kUnknownFileChecksum);
ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName);
#ifdef ROCKSDB_USING_THREAD_STATUS
#ifndef NROCKSDB_THREAD_STATUS
// Verify the id of the current thread that created this table
// file matches the id of any active flush or compaction thread.
uint64_t thread_id = env_->GetThreadID();
@@ -246,7 +244,7 @@ class TestFlushListener : public EventListener {
}
}
ASSERT_TRUE(found_match);
#endif // ROCKSDB_USING_THREAD_STATUS
#endif // !NROCKSDB_THREAD_STATUS
}
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
@@ -310,9 +308,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
TestFlushListener* listener = new TestFlushListener(options.env, this);
options.listeners.emplace_back(listener);
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
@@ -357,9 +353,7 @@ TEST_F(EventListenerTest, MultiCF) {
Options options;
options.env = CurrentOptions().env;
options.write_buffer_size = k110KB;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.atomic_flush = atomic_flush;
options.create_if_missing = true;
DestroyAndReopen(options);
@@ -407,9 +401,7 @@ TEST_F(EventListenerTest, MultiCF) {
TEST_F(EventListenerTest, MultiDBMultiListeners) {
Options options;
options.env = CurrentOptions().env;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
options.table_properties_collector_factories.push_back(
std::make_shared<TestPropertiesCollectorFactory>());
std::vector<TestFlushListener*> listeners;
@@ -497,9 +489,7 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
TEST_F(EventListenerTest, DisableBGCompaction) {
Options options;
options.env = CurrentOptions().env;
#ifdef ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = true;
#endif // ROCKSDB_USING_THREAD_STATUS
options.enable_thread_tracking = ThreadStatus::kEnabled;
TestFlushListener* listener = new TestFlushListener(options.env, this);
const int kCompactionTrigger = 1;
const int kSlowdownTrigger = 5;
+41
View File
@@ -303,6 +303,47 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
ASSERT_EQ(deleted_files, expected_deleted_files);
}
TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
// This test used to trigger a hang in `DB::GetSortedWalFiles()`, where it
// would wait for a no-op purge that did not signal the CV upon completion.
// Grab an iterator and flush to switch the super version. That way, when the
// iterator is destroyed, it will go through the purge path.
DB* db = db_; // Only using `db` makes it clear we only use DB-level APIs.
ASSERT_OK(db->Put(WriteOptions(), "key", "value"));
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions()));
ASSERT_OK(db->Flush(FlushOptions()));
// Sync points ensure `GetSortedWalFiles()` waits for a purge after
// `FindObsoleteFiles()` releases the mutex but before its corresponding purge
// completes.
SyncPoint::GetInstance()->SetCallBack(
"FindObsoleteFiles::PostMutexUnlock", [&](void* /* arg */) {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:1");
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
"InCallback:2");
});
SyncPoint::GetInstance()->LoadDependency({
{"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:1",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin"},
{"DBImpl::GetSortedWalFilesImpl:WaitPurge",
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:2"},
});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread get_sorted_wal_files_thread([db]() {
TEST_SYNC_POINT(
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin");
VectorWalPtr files;
ASSERT_OK(db->GetSortedWalFiles(files));
});
iter.reset();
get_sorted_wal_files_thread.join();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+10 -10
View File
@@ -704,17 +704,17 @@ class Repairer {
VersionEdit dummy_edit;
for (const auto* table : cf_id_and_tables.second) {
// TODO(opt): separate out into multiple levels
const auto& meta = table->meta;
dummy_edit.AddFile(
0, table->meta.fd.GetNumber(), table->meta.fd.GetPathId(),
table->meta.fd.GetFileSize(), table->meta.smallest,
table->meta.largest, table->meta.fd.smallest_seqno,
table->meta.fd.largest_seqno, table->meta.marked_for_compaction,
table->meta.temperature, table->meta.oldest_blob_file_number,
table->meta.oldest_ancester_time, table->meta.file_creation_time,
table->meta.epoch_number, table->meta.file_checksum,
table->meta.file_checksum_func_name, table->meta.unique_id,
table->meta.compensated_range_deletion_size, table->meta.tail_size,
table->meta.user_defined_timestamps_persisted);
0, meta.fd.GetNumber(), meta.fd.GetPathId(), meta.fd.GetFileSize(),
meta.smallest, meta.largest, meta.fd.smallest_seqno,
meta.fd.largest_seqno, meta.marked_for_compaction, meta.temperature,
meta.oldest_blob_file_number, meta.oldest_ancester_time,
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
meta.file_checksum_func_name, meta.unique_id,
meta.compensated_range_deletion_size, meta.tail_size,
meta.user_defined_timestamps_persisted, meta.min_timestamp,
meta.max_timestamp);
}
s = dummy_version_builder.Apply(&dummy_edit);
if (s.ok()) {
+3 -2
View File
@@ -98,6 +98,8 @@ Status TableCache::GetTableReader(
std::unique_ptr<FSRandomAccessFile> file;
FileOptions fopts = file_options;
fopts.temperature = file_temperature;
fopts.file_checksum = file_meta.file_checksum;
fopts.file_checksum_func_name = file_meta.file_checksum_func_name;
Status s = PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
TEST_SYNC_POINT_CALLBACK("TableCache::GetTableReader:BeforeOpenFile",
const_cast<Status*>(&s));
@@ -113,8 +115,7 @@ Status TableCache::GetTableReader(
Status temp_s =
PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
if (temp_s.ok()) {
temp_s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
nullptr);
temp_s = ioptions_.fs->NewRandomAccessFile(fname, fopts, &file, nullptr);
}
if (temp_s.ok()) {
RecordTick(ioptions_.stats, NO_FILE_OPENS);
+2 -1
View File
@@ -76,7 +76,8 @@ class VersionBuilderTest : public testing::Test {
oldest_blob_file_number, kUnknownOldestAncesterTime,
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
f->compensated_file_size = file_size;
f->num_entries = num_entries;
f->num_deletions = num_deletions;
+15
View File
@@ -304,6 +304,15 @@ void VersionEdit::EncodeToNewFile4(const FileMetaData& f, int level,
char p = static_cast<char>(0);
PutLengthPrefixedSlice(dst, Slice(&p, 1));
}
// Encode min/max timestamp if they are non-empty
if (!f.min_timestamp.empty()) {
PutVarint32(dst, NewFileCustomTag::kMinTimestamp);
PutLengthPrefixedSlice(dst, Slice(f.min_timestamp));
}
if (!f.max_timestamp.empty()) {
PutVarint32(dst, NewFileCustomTag::kMaxTimestamp);
PutLengthPrefixedSlice(dst, Slice(f.max_timestamp));
}
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
dst);
@@ -443,6 +452,12 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
}
f.user_defined_timestamps_persisted = (field[0] == 1);
break;
case kMinTimestamp:
f.min_timestamp = field.ToString();
break;
case kMaxTimestamp:
f.max_timestamp = field.ToString();
break;
default:
if ((custom_tag & kCustomTagNonSafeIgnoreMask) != 0) {
// Should not proceed if cannot understand it
+33 -15
View File
@@ -276,6 +276,14 @@ struct FileMetaData {
// false, it's explicitly written to Manifest.
bool user_defined_timestamps_persisted = true;
// Minimum user-defined timestamp in the file. Empty if no UDT or unknown.
// This is populated from the table properties "rocksdb.timestamp_min".
std::string min_timestamp;
// Maximum user-defined timestamp in the file. Empty if no UDT or unknown.
// This is populated from the table properties "rocksdb.timestamp_max".
std::string max_timestamp;
FileMetaData() = default;
FileMetaData(uint64_t file, uint32_t file_path_id, uint64_t file_size,
@@ -288,7 +296,9 @@ struct FileMetaData {
const std::string& _file_checksum_func_name,
UniqueId64x2 _unique_id,
const uint64_t _compensated_range_deletion_size,
uint64_t _tail_size, bool _user_defined_timestamps_persisted)
uint64_t _tail_size, bool _user_defined_timestamps_persisted,
const std::string& _min_timestamp,
const std::string& _max_timestamp)
: fd(file, file_path_id, file_size, smallest_seq, largest_seq),
smallest(smallest_key),
largest(largest_key),
@@ -303,7 +313,9 @@ struct FileMetaData {
file_checksum_func_name(_file_checksum_func_name),
unique_id(std::move(_unique_id)),
tail_size(_tail_size),
user_defined_timestamps_persisted(_user_defined_timestamps_persisted) {
user_defined_timestamps_persisted(_user_defined_timestamps_persisted),
min_timestamp(_min_timestamp),
max_timestamp(_max_timestamp) {
TEST_SYNC_POINT_CALLBACK("FileMetaData::FileMetaData", this);
}
@@ -386,7 +398,8 @@ struct FileMetaData {
usage += sizeof(*this);
#endif // ROCKSDB_MALLOC_USABLE_SIZE
usage += smallest.size() + largest.size() + file_checksum.size() +
file_checksum_func_name.size();
file_checksum_func_name.size() + min_timestamp.size() +
max_timestamp.size();
return usage;
}
@@ -557,16 +570,19 @@ struct SubcompactionProgress {
Slice key_slice(next_internal_key_to_compact);
if (ParseInternalKey(key_slice, &parsed_key, false /* log_err_key */)
.ok()) {
oss << "user_key=\"" << parsed_key.user_key.ToString(false /* hex */)
<< "\" (hex:" << parsed_key.user_key.ToString(true /* hex */)
<< ")";
oss << "user_key(hex)=" << parsed_key.user_key.ToString(true /* hex */);
oss << ", seq=";
if (parsed_key.sequence == kMaxSequenceNumber) {
oss << "kMaxSequenceNumber";
} else {
oss << parsed_key.sequence;
}
oss << ", type=" << static_cast<int>(parsed_key.type);
oss << ", type=";
if (parsed_key.type == kValueTypeForSeek) {
oss << "kValueTypeForSeek";
} else {
oss << static_cast<int>(parsed_key.type);
}
} else {
oss << "raw=" << key_slice.ToString(true /* hex */);
}
@@ -734,17 +750,19 @@ class VersionEdit {
const std::string& file_checksum_func_name,
const UniqueId64x2& unique_id,
const uint64_t compensated_range_deletion_size,
uint64_t tail_size, bool user_defined_timestamps_persisted) {
uint64_t tail_size, bool user_defined_timestamps_persisted,
const std::string& min_timestamp = "",
const std::string& max_timestamp = "") {
assert(smallest_seqno <= largest_seqno);
new_files_.emplace_back(
level,
FileMetaData(file, file_path_id, file_size, smallest, largest,
smallest_seqno, largest_seqno, marked_for_compaction,
temperature, oldest_blob_file_number, oldest_ancester_time,
file_creation_time, epoch_number, file_checksum,
file_checksum_func_name, unique_id,
compensated_range_deletion_size, tail_size,
user_defined_timestamps_persisted));
FileMetaData(
file, file_path_id, file_size, smallest, largest, smallest_seqno,
largest_seqno, marked_for_compaction, temperature,
oldest_blob_file_number, oldest_ancester_time, file_creation_time,
epoch_number, file_checksum, file_checksum_func_name, unique_id,
compensated_range_deletion_size, tail_size,
user_defined_timestamps_persisted, min_timestamp, max_timestamp));
files_to_quarantine_.push_back(file);
if (!HasLastSequence() || largest_seqno > GetLastSequence()) {
SetLastSequence(largest_seqno);
+222 -91
View File
@@ -16,6 +16,7 @@
#include <list>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
@@ -980,7 +981,8 @@ class LevelIterator final : public InternalIterator {
nullptr,
bool allow_unprepared_value = false,
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
nullptr)
nullptr,
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
: table_cache_(table_cache),
read_options_(read_options),
file_options_(file_options),
@@ -1005,7 +1007,9 @@ class LevelIterator final : public InternalIterator {
allow_unprepared_value_(allow_unprepared_value),
is_next_read_sequential_(false),
to_return_sentinel_(false),
scan_opts_(nullptr) {
scan_opts_(nullptr),
db_statistics_(db_statistics),
clock_(clock) {
// Empty level is not supported.
assert(flevel_ != nullptr && flevel_->num_files > 0);
if (range_tombstone_iter_ptr_) {
@@ -1013,7 +1017,15 @@ class LevelIterator final : public InternalIterator {
}
}
~LevelIterator() override { delete file_iter_.Set(nullptr); }
~LevelIterator() override {
delete file_iter_.Set(nullptr);
// Clean up any prepared iterators that weren't used
for (auto& entry : prepared_iters_) {
delete entry.second;
}
prepared_iters_.clear();
assert(prepared_iters_.size() == 0);
}
// Seek to the first file with a key >= target.
// If range_tombstone_iter_ is not nullptr, then we pretend that file
@@ -1124,10 +1136,12 @@ class LevelIterator final : public InternalIterator {
void Prepare(const MultiScanArgs* so) override {
// We assume here that scan_opts is sorted such that
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
// scan_opts[0].range.start < scan_opts[1].range.start, and non
// overlapping
if (so == nullptr) {
return;
}
scan_opts_ = so;
// Verify comparator is consistent
@@ -1188,8 +1202,8 @@ class LevelIterator final : public InternalIterator {
continue;
}
auto const metadata = flevel_->files[i].file_metadata;
if (metadata->num_entries == metadata->num_range_deletions) {
// Skip range deletion only files.
if (metadata->FileIsStandAloneRangeTombstone()) {
// Skip stand alone range deletion files.
continue;
}
auto& args = GetMultiScanArgForFile(i);
@@ -1197,9 +1211,35 @@ class LevelIterator final : public InternalIterator {
}
}
}
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
// Propagate multiscan configs
for (auto& file_to_arg : *file_to_scan_opts_) {
file_to_arg.second.CopyConfigFrom(*so);
assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(),
file_to_arg.first) &&
OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(),
file_to_arg.first));
}
if (so->use_async_io) {
auto before = file_index_;
// Pre-create and prepare only relevant file iterators
for (auto& file_to_arg : *file_to_scan_opts_) {
size_t file_index = file_to_arg.first;
file_index_ = file_index;
// Create iterator for this file
auto iter = NewFileIterator();
if (iter != nullptr) {
// If we have async enabled, lets prepare all our iterators.
iter->Prepare(&file_to_arg.second);
// Store the prepared iterator
prepared_iters_[file_index] = iter;
}
}
file_index_ = before;
}
}
@@ -1276,7 +1316,7 @@ class LevelIterator final : public InternalIterator {
}
#ifndef NDEBUG
bool OverlapRange(const ScanOptions& opts);
bool OverlapRange(const ScanOptions& opts, size_t file_index);
#endif
TableCache* table_cache_;
@@ -1334,9 +1374,15 @@ class LevelIterator final : public InternalIterator {
bool to_return_sentinel_ = false;
const MultiScanArgs* scan_opts_ = nullptr;
Statistics* db_statistics_ = nullptr;
SystemClock* clock_ = nullptr;
// Our stored scan_opts for each prefix
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
// Map to store pre-created iterators by file index
std::unordered_map<size_t, InternalIterator*> prepared_iters_;
// Sets flags for if we should return the sentinel key next.
// The condition for returning sentinel is reaching the end of current
// file_iter_: !Valid() && status.().ok().
@@ -1613,8 +1659,19 @@ bool LevelIterator::SkipEmptyFileForward() {
const ScanOptions& opts =
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
if (opts.range.start.has_value()) {
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
kValueTypeForSeek);
InternalKey target;
const size_t ts_size =
user_comparator_.user_comparator()->timestamp_size();
if (ts_size == 0) {
target = InternalKey(opts.range.start.value(), kMaxSequenceNumber,
kValueTypeForSeek);
} else {
std::string seek_key;
AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(),
ts_size);
target =
InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek);
}
file_iter_.Seek(target.Encode());
}
} else {
@@ -1662,14 +1719,14 @@ void LevelIterator::SkipEmptyFileBackward() {
}
#ifndef NDEBUG
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
return (user_comparator_.CompareWithoutTimestamp(
opts.range.start.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index_].largest_key),
ExtractUserKey(flevel_->files[file_index].largest_key),
/*b_has_ts=*/true) <= 0 &&
user_comparator_.CompareWithoutTimestamp(
opts.range.limit.value(), /*a_has_ts=*/false,
ExtractUserKey(flevel_->files[file_index_].smallest_key),
ExtractUserKey(flevel_->files[file_index].smallest_key),
/*b_has_ts=*/true) > 0);
}
#endif
@@ -1680,15 +1737,6 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
}
InternalIterator* old_iter = file_iter_.Set(iter);
if (iter && scan_opts_) {
if (FileHasMultiScanArg(file_index_)) {
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
OverlapRange(*new_opts.GetScanRanges().rbegin()));
file_iter_.Prepare(&new_opts);
}
}
// Update the read pattern for PrefetchBuffer.
if (is_next_read_sequential_) {
file_iter_.UpdateReadaheadState(old_iter);
@@ -1718,7 +1766,24 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
// no need to change anything
} else {
file_index_ = new_file_index;
if (!prepared_iters_.empty()) {
auto prepared_it = prepared_iters_.find(file_index_);
if (prepared_it != prepared_iters_.end()) {
InternalIterator* iter = prepared_it->second;
prepared_iters_.erase(prepared_it);
SetFileIterator(iter);
return;
}
}
InternalIterator* iter = NewFileIterator();
if (FileHasMultiScanArg(file_index_)) {
auto& args = GetMultiScanArgForFile(file_index_);
assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) &&
OverlapRange(*args.GetScanRanges().rbegin(), file_index_));
iter->Prepare(&args);
}
SetFileIterator(iter);
}
}
@@ -1755,8 +1820,10 @@ Status Version::GetTableProperties(const ReadOptions& read_options,
file_name = TableFileName(ioptions.cf_paths, file_meta->fd.GetNumber(),
file_meta->fd.GetPathId());
}
s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
nullptr);
FileOptions fopts = file_options_;
fopts.file_checksum = file_meta->file_checksum;
fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file, nullptr);
if (!s.ok()) {
return s;
}
@@ -2181,7 +2248,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
allow_unprepared_value, &tombstone_iter_ptr);
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2321,7 +2388,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
/*range_del_agg=*/nullptr,
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
&tombstone_iter_ptr);
&tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2378,7 +2445,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
mutable_cf_options_, should_sample_file_read(),
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
&range_del_agg, nullptr, false));
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
iter.get(), overlap);
}
@@ -3379,7 +3446,7 @@ bool Version::MaybeInitializeFileMetaData(const ReadOptions& read_options,
// Ensure new invariants on old files
file_meta->num_deletions =
std::max(tp->num_deletions, tp->num_range_deletions);
file_meta->num_entries = std::max(tp->num_entries, tp->num_deletions);
file_meta->num_entries = std::max(tp->num_entries, file_meta->num_deletions);
return true;
}
@@ -3672,7 +3739,8 @@ bool ShouldChangeFileTemperature(const ImmutableOptions& ioptions,
void VersionStorageInfo::ComputeCompactionScore(
const ImmutableOptions& immutable_options,
const MutableCFOptions& mutable_cf_options) {
const MutableCFOptions& mutable_cf_options,
const std::string& full_history_ts_low) {
double total_downcompact_bytes = 0.0;
// Historically, score is defined as actual bytes in a level divided by
// the level's target size, and 1.0 is the threshold for triggering
@@ -3871,7 +3939,8 @@ void VersionStorageInfo::ComputeCompactionScore(
ComputeFilesMarkedForCompaction(max_output_level);
ComputeBottommostFilesMarkedForCompaction(
immutable_options.cf_allow_ingest_behind ||
immutable_options.allow_ingest_behind);
immutable_options.allow_ingest_behind,
immutable_options.user_comparator, full_history_ts_low);
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
ComputeFilesMarkedForPeriodicCompaction(
immutable_options, mutable_cf_options.periodic_compaction_seconds,
@@ -4462,17 +4531,20 @@ void VersionStorageInfo::GenerateFileLocationIndex() {
}
}
void VersionStorageInfo::UpdateOldestSnapshot(SequenceNumber seqnum,
bool allow_ingest_behind) {
void VersionStorageInfo::UpdateOldestSnapshot(
SequenceNumber seqnum, bool allow_ingest_behind, const Comparator* ucmp,
const std::string& full_history_ts_low) {
assert(seqnum >= oldest_snapshot_seqnum_);
oldest_snapshot_seqnum_ = seqnum;
if (oldest_snapshot_seqnum_ > bottommost_files_mark_threshold_) {
ComputeBottommostFilesMarkedForCompaction(allow_ingest_behind);
ComputeBottommostFilesMarkedForCompaction(allow_ingest_behind, ucmp,
full_history_ts_low);
}
}
void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction(
bool allow_ingest_behind) {
bool allow_ingest_behind, const Comparator* ucmp,
const std::string& full_history_ts_low) {
bottommost_files_marked_for_compaction_.clear();
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
if (allow_ingest_behind) {
@@ -4493,12 +4565,39 @@ void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction(
current_time - static_cast<int64_t>(bottommost_file_compaction_delay_);
}
// For UDT, we need to check if the file's max timestamp is below
// full_history_ts_low. If not, the compaction won't be able to collapse the
// timestamp to clean up the tombstone , so marking the file would be futile
// and could cause an infinite compaction loop.
const bool has_udt = ucmp && ucmp->timestamp_size() > 0;
for (auto& level_and_file : bottommost_files_) {
if (!level_and_file.second->being_compacted &&
level_and_file.second->fd.largest_seqno != 0) {
// largest_seqno might be nonzero due to containing the final key in an
// earlier compaction, whose seqnum we didn't zero out.
if (level_and_file.second->fd.largest_seqno < oldest_snapshot_seqnum_) {
if (has_udt) {
const std::string& max_ts = level_and_file.second->max_timestamp;
// If max_timestamp is empty, the file could come from very old
// version which does not have timestamp. In that case, we should pick
// the file for compaction. After compaction, the file will have
// max_timestamp set propertly.
if (!max_ts.empty()) {
// If full_history_ts_low is empty, it means it was never set, which
// means its value is 0. Therefore, it would be always smaller than
// max_timestamp
if (full_history_ts_low.empty()) {
continue;
}
// If max timestamp >= full_history_ts_low, skip this file
if (ucmp->CompareTimestamp(Slice(max_ts), full_history_ts_low) >=
0) {
continue;
}
}
}
if (!needs_delay) {
bottommost_files_marked_for_compaction_.push_back(level_and_file);
} else if (creation_time_ub > 0) {
@@ -4560,7 +4659,8 @@ bool VersionStorageInfo::OverlapInLevel(int level,
void VersionStorageInfo::GetOverlappingInputs(
int level, const InternalKey* begin, const InternalKey* end,
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
bool expand_range, InternalKey** next_smallest) const {
bool expand_range, const FileMetaData* starting_l0_file,
InternalKey** next_smallest) const {
if (level >= num_non_empty_levels_) {
// this level is empty, no overlapping inputs
return;
@@ -4593,7 +4693,19 @@ void VersionStorageInfo::GetOverlappingInputs(
// index stores the file index need to check.
std::list<size_t> index;
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
size_t start_index = 0;
if (starting_l0_file != nullptr) {
uint64_t starting_file_number = starting_l0_file->fd.GetNumber();
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
if (level_files_brief_[level].files[i].fd.GetNumber() ==
starting_file_number) {
start_index = i;
break;
}
}
assert(start_index < level_files_brief_[level].num_files);
}
for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) {
index.emplace_back(i);
}
@@ -5493,6 +5605,15 @@ void VersionSet::Reset() {
if (column_family_set_) {
WriteBufferManager* wbm = column_family_set_->write_buffer_manager();
WriteController* wc = column_family_set_->write_controller();
// Clear TableCache to prevent use-after-free: Reset() deletes old
// ColumnFamilySet but reuses table_cache_, which may contain
// BlockBasedTable entries with dangling references to deleted CFD's
// ioptions.
if (table_cache_) {
table_cache_->EraseUnRefEntries();
}
// db_id becomes the source of truth after DBImpl::Recover():
// https://github.com/facebook/rocksdb/blob/v7.3.1/db/db_impl/db_impl_open.cc#L527
// Note: we may not be able to recover db_id from MANIFEST if
@@ -5525,8 +5646,11 @@ void VersionSet::Reset() {
void VersionSet::UpdatedMutableDbOptions(
const MutableDBOptions& updated_options, InstrumentedMutex* mu) {
// Must be holding mutex if not called during initialization
if (manifest_file_size_ > 0) {
if (mu) {
mu->AssertHeld();
} else {
// manifest_file_size_ must be 0 if called from the constructor
assert(manifest_file_size_ == 0);
}
file_options_.writable_file_max_buffer_size =
updated_options.writable_file_max_buffer_size;
@@ -5549,7 +5673,8 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
// compute new compaction score
v->storage_info()->ComputeCompactionScore(
column_family_data->ioptions(),
column_family_data->GetLatestMutableCFOptions());
column_family_data->GetLatestMutableCFOptions(),
column_family_data->GetFullHistoryTsLow());
// Mark v finalized
v->storage_info_.SetFinalized();
@@ -5626,8 +5751,8 @@ Status VersionSet::ProcessManifestWrites(
// the preceding version edits in the same atomic group, and update
// their `remaining_entries_` member variable because we are NOT going
// to write the version edits' of dropped CF to the MANIFEST. If we
// don't update, then Recover can report corrupted atomic group because
// the `remaining_entries_` do not match.
// don't update, then Recover can report corrupted atomic group
// because the `remaining_entries_` do not match.
if (!batch_edits.empty()) {
if (batch_edits.back()->IsInAtomicGroup() &&
batch_edits.back()->GetRemainingEntries() > 0) {
@@ -5932,10 +6057,12 @@ Status VersionSet::ProcessManifestWrites(
#ifndef NDEBUG
if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
TEST_SYNC_POINT_CALLBACK(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"0",
nullptr);
TEST_SYNC_POINT(
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
"1");
}
++idx;
#endif /* !NDEBUG */
@@ -5972,8 +6099,8 @@ Status VersionSet::ProcessManifestWrites(
file_options_.temperature, dir_contains_current_file);
if (!io_s.ok()) {
s = io_s;
// Quarantine old manifest file in case new manifest file's CURRENT file
// wasn't created successfully and the old manifest is needed.
// Quarantine old manifest file in case new manifest file's CURRENT
// file wasn't created successfully and the old manifest is needed.
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
files_to_quarantine_if_commit_fail.push_back(
&limbo_descriptor_log_file_number);
@@ -6122,21 +6249,21 @@ Status VersionSet::ProcessManifestWrites(
// that renaming tmp file to CURRENT failed.
//
// On local POSIX-compliant FS, the CURRENT must point to the original
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
// process not to crash and continue using the db. Any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT, still ignoring
// this one.
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can
// also keep it. Future recovery will ignore this MANIFEST. It's also ok
// for the process not to crash and continue using the db. Any future
// LogAndApply() call will switch to a new MANIFEST and update CURRENT,
// still ignoring this one.
//
// On non-local FS, it is
// possible that the rename operation succeeded on the server (remote)
// side, but the client somehow returns a non-ok status to RocksDB. Note
// that this does not violate atomicity. Should we delete the new MANIFEST
// successfully, a subsequent recovery attempt will likely see the CURRENT
// pointing to the new MANIFEST, thus fail. We will not be able to open the
// DB again. Therefore, if manifest operations succeed, we should keep the
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
// will switch to a new MANIFEST and update CURRENT. If user tries to
// pointing to the new MANIFEST, thus fail. We will not be able to open
// the DB again. Therefore, if manifest operations succeed, we should keep
// the the new MANIFEST. If the process proceeds, any future LogAndApply()
// call will switch to a new MANIFEST and update CURRENT. If user tries to
// re-open the DB,
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
@@ -6265,9 +6392,9 @@ Status VersionSet::LogAndApply(
first_writer.cv.Wait();
}
if (first_writer.done) {
// All non-CF-manipulation operations can be grouped together and committed
// to MANIFEST. They should all have finished. The status code is stored in
// the first manifest writer.
// All non-CF-manipulation operations can be grouped together and
// committed to MANIFEST. They should all have finished. The status code
// is stored in the first manifest writer.
#ifndef NDEBUG
for (const auto& writer : writers) {
assert(writer.done);
@@ -6321,8 +6448,8 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
assert(!edit->HasLastSequence());
edit->SetLastSequence(*max_last_sequence);
if (edit->IsColumnFamilyDrop()) {
// if we drop column family, we have to make sure to save max column family,
// so that we don't reuse existing ID
// if we drop column family, we have to make sure to save max column
// family, so that we don't reuse existing ID
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
}
}
@@ -6611,7 +6738,8 @@ void VersionSet::RecoverEpochNumbers() {
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
const std::string& dbname,
FileSystem* fs) {
// Read "CURRENT" file, which contains a pointer to the current manifest file
// Read "CURRENT" file, which contains a pointer to the current manifest
// file
std::string manifest_path;
uint64_t manifest_file_number;
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
@@ -6768,9 +6896,9 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
// Get the checksum information including the checksum and checksum function
// name of all SST and blob files in VersionSet. Store the information in
// FileChecksumList which contains a map from file number to its checksum info.
// If DB is not running, make sure call VersionSet::Recover() to load the file
// metadata from Manifest to VersionSet before calling this function.
// FileChecksumList which contains a map from file number to its checksum
// info. If DB is not running, make sure call VersionSet::Recover() to load
// the file metadata from Manifest to VersionSet before calling this function.
Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
// Clean the previously stored checksum information if any.
Status s;
@@ -6912,8 +7040,8 @@ Status VersionSet::WriteCurrentStateToManifest(
// WARNING: This method doesn't hold a mutex!!
// This is done without DB mutex lock held, but only within single-threaded
// LogAndApply. Column family manipulations can only happen within LogAndApply
// (the same single thread), so we're safe to iterate.
// LogAndApply. Column family manipulations can only happen within
// LogAndApply (the same single thread), so we're safe to iterate.
assert(io_s.ok());
if (db_options_->write_dbid_to_manifest) {
@@ -6947,9 +7075,9 @@ Status VersionSet::WriteCurrentStateToManifest(
}
// New manifest should rollover the WAL deletion record from previous
// manifest. Otherwise, when an addition record of a deleted WAL gets added to
// this new manifest later (which can happens in e.g, SyncWAL()), this new
// manifest creates an illusion that such WAL hasn't been deleted.
// manifest. Otherwise, when an addition record of a deleted WAL gets added
// to this new manifest later (which can happens in e.g, SyncWAL()), this
// new manifest creates an illusion that such WAL hasn't been deleted.
VersionEdit wal_deletions;
wal_deletions.DeleteWalsBefore(min_log_number_to_keep());
std::string wal_deletions_record;
@@ -7009,7 +7137,6 @@ Status VersionSet::WriteCurrentStateToManifest(
for (const auto& f : level_files) {
assert(f);
edit.AddFile(level, f->fd.GetNumber(), f->fd.GetPathId(),
f->fd.GetFileSize(), f->smallest, f->largest,
f->fd.smallest_seqno, f->fd.largest_seqno,
@@ -7018,7 +7145,8 @@ Status VersionSet::WriteCurrentStateToManifest(
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->user_defined_timestamps_persisted, f->min_timestamp,
f->max_timestamp);
}
}
@@ -7081,9 +7209,9 @@ Status VersionSet::WriteCurrentStateToManifest(
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
// function is called repeatedly with consecutive pairs of slices. For example
// if the slice list is [a, b, c, d] this function is called with arguments
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
// we avoid doing binary search for the keys b and c twice and instead somehow
// maintain state of where they first appear in the files.
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible
// where we avoid doing binary search for the keys b and c twice and instead
// somehow maintain state of where they first appear in the files.
uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
const ReadOptions& read_options,
Version* v, const Slice& start,
@@ -7104,19 +7232,20 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
}
// Outline of the optimization that uses options.files_size_error_margin.
// When approximating the files total size that is used to store a keys range,
// we first sum up the sizes of the files that fully fall into the range.
// Then we sum up the sizes of all the files that may intersect with the range
// (this includes all files in L0 as well). Then, if total_intersecting_size
// is smaller than total_full_size * options.files_size_error_margin - we can
// infer that the intersecting files have a sufficiently negligible
// contribution to the total size, and we can approximate the storage required
// for the keys in range as just half of the intersecting_files_size.
// E.g., if the value of files_size_error_margin is 0.1, then the error of the
// approximation is limited to only ~10% of the total size of files that fully
// fall into the keys range. In such case, this helps to avoid a costly
// process of binary searching the intersecting files that is required only
// for a more precise calculation of the total size.
// When approximating the files total size that is used to store a keys
// range, we first sum up the sizes of the files that fully fall into the
// range. Then we sum up the sizes of all the files that may intersect with
// the range (this includes all files in L0 as well). Then, if
// total_intersecting_size is smaller than total_full_size *
// options.files_size_error_margin - we can infer that the intersecting
// files have a sufficiently negligible contribution to the total size, and
// we can approximate the storage required for the keys in range as just
// half of the intersecting_files_size. E.g., if the value of
// files_size_error_margin is 0.1, then the error of the approximation is
// limited to only ~10% of the total size of files that fully fall into the
// keys range. In such case, this helps to avoid a costly process of binary
// searching the intersecting files that is required only for a more precise
// calculation of the total size.
autovector<FdWithKeyRange*, 32> first_files;
autovector<FdWithKeyRange*, 16> last_files;
@@ -7188,10 +7317,11 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
total_intersecting_size += file_ptr->fd.GetFileSize();
}
// Now scan all the first & last files at each level, and estimate their size.
// If the total_intersecting_size is less than X% of the total_full_size - we
// want to approximate the result in order to avoid the costly binary search
// inside ApproximateSize. We use half of file size as an approximation below.
// Now scan all the first & last files at each level, and estimate their
// size. If the total_intersecting_size is less than X% of the
// total_full_size - we want to approximate the result in order to avoid the
// costly binary search inside ApproximateSize. We use half of file size as
// an approximation below.
const double margin = options.files_size_error_margin;
if (margin > 0 && total_intersecting_size <
@@ -7459,7 +7589,8 @@ InternalIterator* VersionSet::MakeInputIterator(
/*no per level latency histogram=*/nullptr,
TableReaderCaller::kCompaction, /*skip_filters=*/false,
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
c->boundaries(which), false, &tombstone_iter_ptr);
c->boundaries(which), false, &tombstone_iter_ptr,
db_options_->statistics.get(), clock_);
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
}
}
@@ -7864,8 +7995,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
}
} else if (s.IsPathNotFound()) {
// This can happen if the primary switches to a new MANIFEST after the
// secondary reads the CURRENT file but before the secondary actually tries
// to open the MANIFEST.
// secondary reads the CURRENT file but before the secondary actually
// tries to open the MANIFEST.
s = Status::TryAgain(
"The primary may have switched to a new MANIFEST and deleted the old "
"one.");
+23 -7
View File
@@ -200,7 +200,8 @@ class VersionStorageInfo {
// REQUIRES: db_mutex held!!
// TODO find a better way to pass compaction_options_fifo.
void ComputeCompactionScore(const ImmutableOptions& immutable_options,
const MutableCFOptions& mutable_cf_options);
const MutableCFOptions& mutable_cf_options,
const std::string& full_history_ts_low);
// Estimate est_comp_needed_bytes_
void EstimateCompactionBytesNeeded(
@@ -230,8 +231,15 @@ class VersionStorageInfo {
// oldest snapshot changes as that is when bottom-level files can become
// eligible for compaction.
//
// For columns with User Defined Timestamps (UDT), also checks that the
// file's largest timestamp is below full_history_ts_low before marking,
// since compaction can only collapse timestamp when it is below this
// threshold.
//
// REQUIRES: DB mutex held
void ComputeBottommostFilesMarkedForCompaction(bool allow_ingest_behind);
void ComputeBottommostFilesMarkedForCompaction(
bool allow_ingest_behind, const Comparator* ucmp,
const std::string& full_history_ts_low);
// This computes files_marked_for_forced_blob_gc_ and is called by
// ComputeCompactionScore()
@@ -248,7 +256,8 @@ class VersionStorageInfo {
// files marked for compaction.
// REQUIRES: DB mutex held
void UpdateOldestSnapshot(SequenceNumber oldest_snapshot_seqnum,
bool allow_ingest_behind);
bool allow_ingest_behind, const Comparator* ucmp,
const std::string& full_history_ts_low);
int MaxInputLevel() const;
int MaxOutputLevel(bool allow_ingest_behind) const;
@@ -268,8 +277,13 @@ class VersionStorageInfo {
bool expand_range = true, // if set, returns files which overlap the
// range and overlap each other. If false,
// then just files intersecting the range
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
const FileMetaData* starting_l0_file =
nullptr, // If not null, restricts L0 file selection to only include
// files at or older than starting_l0_file.
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
void GetCleanInputsWithinInterval(
int level, const InternalKey* begin, // nullptr means before all keys
const InternalKey* end, // nullptr means after all keys
@@ -286,8 +300,10 @@ class VersionStorageInfo {
int hint_index, // index of overlap file
int* file_index, // return index of overlap file
bool within_interval = false, // if set, force the inputs within interval
InternalKey** next_smallest = nullptr) // if non-null, returns the
const; // smallest key of next file not included
InternalKey** next_smallest =
nullptr // if non-null, returns the
// smallest key of next file not included
) const;
// Returns true iff some file in the specified level overlaps
// some part of [*smallest_user_key,*largest_user_key].
+18 -9
View File
@@ -56,7 +56,8 @@ class GenerateLevelFilesBriefTest : public testing::Test {
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
kUnknownFileCreationTime, kUnknownEpochNumber, kUnknownFileChecksum,
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
files_.push_back(f);
}
@@ -172,7 +173,8 @@ class VersionStorageInfoTestBase : public testing::Test {
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
kUnknownEpochNumber, kUnknownFileChecksum, kUnknownFileChecksumFuncName,
kNullUniqueId64x2, compensated_range_deletion_size, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
vstorage_.AddFile(level, f);
}
@@ -391,7 +393,8 @@ TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_1) {
ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3));
ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4));
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_,
/*full_history_ts_low=*/"");
// Only L0 hits compaction.
ASSERT_EQ(vstorage_.CompactionScoreLevel(0), 0);
}
@@ -421,7 +424,8 @@ TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_2) {
ASSERT_EQ(51450U, vstorage_.MaxBytesForLevel(3));
ASSERT_EQ(257250U, vstorage_.MaxBytesForLevel(4));
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_,
/*full_history_ts_low=*/"");
// Although L2 and l3 have higher unadjusted compaction score, considering
// a relatively large L0 being compacted down soon, L4 is picked up for
// compaction.
@@ -453,7 +457,8 @@ TEST_F(VersionStorageInfoTest, MaxBytesForLevelDynamicWithLargeL0_3) {
ASSERT_EQ(2, vstorage_.base_level());
ASSERT_EQ(20000U, vstorage_.MaxBytesForLevel(2));
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_,
/*full_history_ts_low=*/"");
// Although L2 has higher unadjusted compaction score, considering
// a relatively large L0 being compacted down soon, L3 is picked up for
// compaction.
@@ -483,7 +488,8 @@ TEST_F(VersionStorageInfoTest, DrainUnnecessaryLevel) {
ASSERT_EQ(1, vstorage_.base_level());
ASSERT_EQ(1000, vstorage_.MaxBytesForLevel(1));
ASSERT_EQ(10100, vstorage_.MaxBytesForLevel(3));
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_);
vstorage_.ComputeCompactionScore(ioptions_, mutable_cf_options_,
/*full_history_ts_low=*/"");
// Tests that levels 1 and 3 are eligible for compaction.
// Levels 1 and 3 are much smaller than target size,
@@ -1346,7 +1352,8 @@ class VersionSetTestBase {
Temperature::kUnknown, info.oldest_blob_file_number, 0, 0,
info.epoch_number, kUnknownFileChecksum, kUnknownFileChecksumFuncName,
kNullUniqueId64x2, 0, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
if (info.file_missing) {
ASSERT_OK(fs_->DeleteFile(fname, IOOptions(), nullptr));
}
@@ -3940,7 +3947,8 @@ TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0,
file_num /* epoch_number */, kUnknownFileChecksum,
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
added_files.emplace_back(0, meta);
}
WriteFileAdditionAndDeletionToManifest(
@@ -4001,7 +4009,8 @@ TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
largest_ikey, 0, 0, false, Temperature::kUnknown, 0, 0, 0,
file_num /* epoch_number */, kUnknownFileChecksum,
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
/* user_defined_timestamps_persisted */ true);
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
/* max timestamp */ "");
added_files.emplace_back(0, meta);
}
WriteFileAdditionAndDeletionToManifest(
+7 -1
View File
@@ -192,7 +192,13 @@ void WalManager::PurgeObsoleteWALFiles() {
s.ToString().c_str());
continue;
}
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
// Avoid expression `now_seconds - file_m_time` when
// `file_m_time > now_seconds` to prevent unsigned underflow in case
// system clock goes backwards. Both timestamps are based on wall clock
// time, which is not guaranteed to be monotonic.
if (file_m_time <= now_seconds &&
now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
/*force_fg=*/!wal_in_db_path_);
if (!s.ok()) {
+46 -8
View File
@@ -19,6 +19,7 @@
#include "rocksdb/write_batch.h"
#include "rocksdb/write_buffer_manager.h"
#include "table/mock_table.h"
#include "test_util/mock_time_env.h"
#include "test_util/testharness.h"
#include "test_util/testutil.h"
#include "util/string_util.h"
@@ -39,7 +40,7 @@ class WalManagerTest : public testing::Test {
EXPECT_OK(DestroyDB(dbname_, Options()));
}
void Init() {
void Init(SystemClock* clock_override) {
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_)));
db_options_.db_paths.emplace_back(dbname_,
@@ -47,7 +48,11 @@ class WalManagerTest : public testing::Test {
db_options_.wal_dir = dbname_;
db_options_.env = env_.get();
db_options_.fs = env_->GetFileSystem();
db_options_.clock = env_->GetSystemClock().get();
if (clock_override == nullptr) {
db_options_.clock = env_->GetSystemClock().get();
} else {
db_options_.clock = clock_override;
}
versions_.reset(new VersionSet(
dbname_, &db_options_, MutableDBOptions{}, env_options_,
@@ -124,7 +129,7 @@ class WalManagerTest : public testing::Test {
};
TEST_F(WalManagerTest, ReadFirstRecordCache) {
Init();
Init(nullptr /* clock_override */);
std::string path = dbname_ + "/000001.log";
std::unique_ptr<FSWritableFile> file;
ASSERT_OK(env_->GetFileSystem()->NewWritableFile(path, FileOptions(), &file,
@@ -221,7 +226,7 @@ int CountRecords(TransactionLogIterator* iter) {
TEST_F(WalManagerTest, WALArchivalSizeLimit) {
db_options_.WAL_ttl_seconds = 0;
db_options_.WAL_size_limit_MB = 1000;
Init();
Init(nullptr /* clock_override */);
// TEST : Create WalManager with huge size limit and no ttl.
// Create some archived files and call PurgeObsoleteWALFiles().
@@ -258,7 +263,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
TEST_F(WalManagerTest, WALArchivalTtl) {
db_options_.WAL_ttl_seconds = 1000;
Init();
Init(nullptr /* clock_override */);
// TEST : Create WalManager with a ttl and no size limit.
// Create some archived log files and call PurgeObsoleteWALFiles().
@@ -282,8 +287,41 @@ TEST_F(WalManagerTest, WALArchivalTtl) {
ASSERT_TRUE(log_files.empty());
}
TEST_F(WalManagerTest, WALArchivalTtlClockGoesBackwards) {
// This test used to trigger an unsigned underflow bug, where WAL files were
// incorrectly deleted when the system time moved backwards between writing
// to a WAL and running `WalManager::PurgeObsoleteWALFiles()`.
constexpr int kNumLogs = 5;
constexpr int kEntriesPerLog = 100;
db_options_.WAL_ttl_seconds = 86400; // One day
// Configure mock clock to lag one second behind system time. That way, the
// WAL file's mtime will appear to be in the future when
// `WalManager::PurgeObsoleteWALFiles()` runs.
int64_t now_seconds;
ASSERT_OK(env_->GetSystemClock()->GetCurrentTime(&now_seconds));
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
mock_clock->SetCurrentTime(static_cast<uint64_t>(now_seconds - 1));
db_options_.clock = mock_clock.get();
Init(mock_clock.get() /* clock */);
CreateArchiveLogs(kNumLogs, kEntriesPerLog);
const std::string archive_dir = ArchivalDirectory(dbname_);
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
wal_manager_->PurgeObsoleteWALFiles();
// All files must still be present because TTL has not elapsed.
ASSERT_EQ(kNumLogs,
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
}
TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
Init();
Init(nullptr /* clock_override */);
RollTheLog(false);
Put("key1", std::string(1024, 'a'));
// Create a zero record WAL file.
@@ -297,7 +335,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
}
TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
Init();
Init(nullptr /* clock_override */);
RollTheLog(false);
auto iter = OpenTransactionLogIter(0);
// Check that an empty iterator is returned
@@ -305,7 +343,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
}
TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) {
Init();
Init(nullptr /* clock_override */);
CreateArchiveLogs(2, 100);
auto iter = OpenTransactionLogIter(0);
CreateArchiveLogs(1, 100);
+19
View File
@@ -877,5 +877,24 @@ Status DestroyUnverifiedSubdir(const std::string& dirname) {
return s;
}
Status DbStressDestroyDb(const std::string& db_path) {
Status s;
Options options;
// NOTE: using db_stress_listener_env in order to see obsolete MANIFEST files
options.env = db_stress_listener_env;
// Remove DB files in a principled way to avoid issues
if (FLAGS_use_blob_db) {
s = blob_db::DestroyBlobDB(db_path, options, blob_db::BlobDBOptions());
} else {
s = DestroyDB(db_path, options);
}
if (!s.ok()) {
return s;
}
// Remove everything else recursively, only reporting success if able to
// delete everything
return DestroyDir(db_stress_listener_env, db_path);
}
} // namespace ROCKSDB_NAMESPACE
#endif // GFLAGS
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff. Show more