Compare commits

..
6 Commits
Author SHA1 Message Date
Jay Huh e9d2ad782e Update version and HISTORY.md for 11.4.3 release 2026-06-25 13:10:48 -07:00
Josh Kang b0001a9ca7 Do not allow read only DBs to delete obsolete files (#14881)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14881

Read-only DB instances can share a directory with a live writer, so their view of live files is only a snapshot from the time they opened. After read-only DB open began setting `opened_successfully_` for its intended open-success semantics, that state had an unintended side effect: the common close path treated a successful read-only open like a read-write open and ran obsolete-file cleanup. If the writer created or made files live after the read-only handle opened, that cleanup could use the stale read-only live set and delete files still needed by the writer.

This change records whether a `DBImpl` was opened read-only and skips close-time obsolete-file cleanup for read-only DBs instead of simply keeping `opened_successfully_=false`, which could be confusing and easily mistaken in the future again.

For completeness I also updated other callsites of `opened_successfully_`, but those should not be real bugs as ReadOnly DBs do not run flushes/compactions or write to WALs.

Reviewed By: xingbowang

Differential Revision: D109622445

fbshipit-source-id: d7be4b20fce86ccb218a63ac6f5b707b316aac79
2026-06-25 13:05:38 -07:00
anand76 cebb2ba1bf Update HISTORY and version for 11.4.2 2026-06-10 14:43:26 -07:00
Xingbo Wang f39e9a94f0 Add read-scoped block buffers for scan reads (#14806)
Summary:
Add read-scoped block buffers for scan reads. Introduce an experimental read-scoped block buffer provider API, configured through ReadOptions::read_scoped_block_buffer_provider, so supported block-based table iterator scans and MultiScan data-block reads can use caller-provided read-scoped storage for final data-block contents.

When configured, supported provider-backed scan data-block reads bypass the data-block cache while preserving normal index/filter block-cache behavior. Known-uncompressed reads can attach provider cleanup to provider-backed read buffers without copying. Compressed reads decompress directly into provider-backed output, while maybe-compressed reads that turn out to be uncompressed copy once into provider-backed final contents. mmap reads ignore the provider.

Extend AlignedBuffer and RandomAccessFileReader direct-I/O paths to support external aligned allocations, then use that support for read-scoped iterator, async I/O, and MultiRead scratch buffers. Centralize read-scoped I/O policy, keep coalesced async reads safe when blocks are released before completion, and validate provider lease contracts.

Add focused coverage for read-scoped ownership, compressed and uncompressed blocks, direct I/O, data-block cache bypass behavior, invalid provider leases, async release handling, and stress-test provider invariants. Add public API release notes for read-scoped block buffers.

Bonus change: Fixed a flaky test in ReserveThread

## Testing

- CI

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

Reviewed By: anand1976

Differential Revision: D106999951

Pulled By: xingbowang

fbshipit-source-id: b1f23d4bab6318b6373ba2ca99a5c4d6a842dc5a
2026-06-10 14:03:51 -07:00
Josh Kang 1805da4fec No-op 11.4.1 patch 2026-06-05 15:57:56 -07:00
anand1976 b5e3c7056d Update HISTORY.md for 11.4.0 release 2026-06-04 15:51:59 -07:00
140 changed files with 1052 additions and 9376 deletions

No files matched your search

-54
View File
@@ -149,26 +149,6 @@ Cache management is critical for RocksDB's performance.
When reviewing RocksDB code (or preparing code for review), use this checklist:
### Contract Boundaries
- [ ] Is each behavior owned by the right layer? High-level policy (for example,
"compaction wants this I/O mode") should live at the caller/policy layer, while
lower layers should expose generic mechanisms (for example, "open a fresh
reader", "skip shared cache insertion", or "use these FileOptions").
- [ ] Do comments and names describe local contracts rather than leaking a
specific caller's rationale into reusable APIs? Generic code should not need to
know about one current use case unless the API itself is intentionally
use-case-specific.
- [ ] Does each flag or parameter control one coherent behavior? If one boolean
starts implying ownership, cache policy, I/O mode, prefetching, and caller
identity, split it into explicit flags or an options struct.
- [ ] Could a future caller use this lower-level API without accidentally
inheriting assumptions from compaction, backup, user reads, or a particular
table format? If not, tighten the contract with assertions, clearer names, or
a narrower API.
- [ ] Are implementation details not being used as policy signals? Prefer an
explicit contract over inferring behavior from incidental fields such as file
options, cache handles, or current table-reader state.
### Correctness
- [ ] Does the change preserve database semantics (e.g., snapshot isolation, key ordering)?
- [ ] Are all error cases handled appropriately?
@@ -224,14 +204,6 @@ The following patterns emerged as frequent sources of review feedback:
10. **Platform Compatibility:** Ensure changes work correctly on all supported platforms (Linux, Windows, macOS) and with all supported compilers (GCC, Clang, MSVC).
11. **Contract Boundary Leaks:** When a change plumbs a new option or use-case
specific behavior through multiple subsystems, review the call chain for
contract leaks. Caller-specific rationale belongs at the call site or public API
documentation; reusable layers should expose precise, layer-local capabilities.
Watch especially for comments mentioning one caller in generic code, booleans
that silently bundle several behaviors, and downstream code inferring policy
from an implementation detail instead of an explicit option.
---
## Important tips
@@ -279,29 +251,6 @@ phantom bug.
`dynamic_cast` in debug builds, plain `static_cast` in release).
* Unit tests (`*_test.cc`) are built in debug mode with RTTI enabled.
### Cross-platform / portability
Local `make` only exercises Linux with GCC/Clang, but CI
(`.github/workflows/pr-jobs.yml` and `nightly.yml`) gates on a much wider
matrix, so portability breaks are invisible locally until CI fails. Code must
build (and where noted, run tests) across:
| Axis | Must support |
|------|--------------|
| OS | Linux (x86_64 + ARM), macOS, Windows |
| Compiler | GCC, Clang (libstdc++ **and** libc++), AppleClang, **MSVC (VS2022)**, MinGW (Linux cross-compile, build-only, no gflags) |
| Build system | Make, CMake, and BUCK (internal) -- keep all in sync (see "Build system" above) |
| Config | release (`-fno-rtti`), `ASSERT_STATUS_CHECKED`, ASAN/UBSAN/TSAN, folly, unity build, JNI/Java |
Treat these as constraints to satisfy and infer the specifics from them before
adding any system header, libc call, or compiler-specific construct. The most
common trap: anything that compiles under GCC/Clang on Linux but not under
**MSVC/MinGW** -- e.g. unguarded POSIX-only headers/functions (`<unistd.h>`,
`<sys/*.h>`, `getpid`, `_exit`, ...) or GCC/Clang extensions
(`__attribute__`, `__builtin_*`, VLAs, `alloca`). Prefer the `port::`/`Env`
abstractions; otherwise guard with `#ifdef OS_WIN` (POSIX `<unistd.h>` ->
Windows `<process.h>`). Because libc++ is also tested, include what you use
rather than relying on libstdc++ transitive includes.
### Unit Test
* After all of the unit tests are added, review them and try to extract common
reusable utility functions to reduce code duplication due to copy past between
@@ -317,9 +266,6 @@ rather than relying on libstdc++ transitive includes.
COERCE_CONTEXT_SWITCH=1 make {test_binary}
./{test_binary} --gtest_filter="*YourTestName*" --gtest_repeat=5
```
* For CI-style flaky tests that do not reproduce with `gtest_parallel.py`,
`--gtest_repeat`, or normal coerce-mode runs, inspect
`tools/gtest_parallel_repro.py --help`.
### Unit test dedup guidelines
* Extract helper functions for repeated patterns such as object
+5 -28
View File
@@ -1,41 +1,19 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 11.5.2 (06/25/2026)
### Bug Fixes
* Reverted PR14831 that made range_lock_manager aware of reverse-order CF
## 11.5.1 (06/24/2026)
## 11.4.3 (06/24/2026)
### Bug Fixes
* Fixed a bug where closing a read-only DB instance could delete live SST files created by a concurrent read-write DB sharing the same directory.
## 11.5.0 (06/16/2026)
### New Features
* External table readers that open files through `ExternalTableOptions::fs` now update RocksDB SST/file-read statistics and file IO listener callbacks, making external file IO activity visible in existing read metrics.
* Added `DB::PrepareFileIngestion()`, a two-phase form of `IngestExternalFile`/`IngestExternalFiles`. `PrepareFileIngestion()` performs all of the work that does not require the DB mutex (validating the arguments, reading each external file's metadata, and linking/copying the files into the DB) and returns an opaque `FileIngestionHandle`. `DB::CommitFileIngestionHandle()` (or `DB::CommitFileIngestionHandles()` for several handles at once) then makes the prepared files visible; multiple handles are committed atomically in a single MANIFEST write, or none are. `FileIngestionHandle::Abort()` (or simply destroying the handle) cancels a prepared ingestion and rolls it back. This gives flexibility to the application to Prepare a file separately from when its committed, and may shorten an applications critical path on `IngestExternalFile`.
* Added two stats histograms reporting the per-call latency (in microseconds) of each successful `IngestExternalFile`/`IngestExternalFiles` call, split by phase: `rocksdb.ingest.external.file.prepare.micros` (argument validation and reading/validating/linking the external files, which does not block live writes) and `rocksdb.ingest.external.file.run.micros` (the ingestion performed under the DB mutex while live writes are blocked). Failed ingestions are not recorded.
* Added `DBOptions::use_direct_io_for_compaction_reads` (default false). When enabled, compaction-input SST reads use `O_DIRECT` while user reads remain buffered, avoiding page-cache eviction of hot user-read data by sequential compaction scans. Pair with `use_direct_io_for_flush_and_compaction = true` on write-heavy workloads for direct I/O on both compaction inputs and outputs. Rejected at Open when combined with `allow_mmap_reads = true`. No-op when `use_direct_reads = true` is already set (since all reads are already direct).
## 11.4.2 (06/08/2026)
### Public API Changes
* `DB::GetPreparedFileInfoForExternalSstIngestion()` can now prepare metadata for a live DB-generated SST file so it can be passed to `IngestExternalFileArg::file_infos`. The input path must exactly match a live table file owned by the source DB, and the returned metadata handle must outlive the ingestion.
* `ExternalSstFileInfo` gained a `prepared_file_info` member produced by `SstFileWriter::Finish`, and `IngestExternalFileArg` gained a `file_infos` field: a vector of borrowed `const PreparedFileInfo*` pointers, parallel to `external_files`. The owning `ExternalSstFileInfo::prepared_file_info` handle must outlive the ingestion. When set, `IngestExternalFiles()` reuses the supplied per-file metadata instead of re-opening and scanning each file to recompute it, avoiding that extra I/O.
* Corrected the public C++ option spelling from `memtable_veirfy_per_key_checksum_on_seek` to `memtable_verify_per_key_checksum_on_seek`. RocksDB continues to accept the old misspelled OPTIONS-file key for compatibility, while newly serialized OPTIONS files use the corrected key.
* Added experimental read-scoped block buffer provider API, configured through `ReadOptions::read_scoped_block_buffer_provider`, for supported block-based table iterator scans and MultiScan reads to use caller-provided read-scoped storage for final data-block contents. When configured, supported provider-backed scan data-block reads bypass the data-block cache. The provider is ignored when mmap reads are enabled. RocksDB may still use ordinary temporary scratch for serialized block bytes, such as when a block may be compressed. Get and MultiGet do not currently provide API guarantees for this provider.
* Added `FileSystem::SyncFile()` and `Env::SyncFile()` public APIs for syncing or fsyncing a file by name without requiring callers to reopen it as writable.
### Behavior Changes
* The default `compression` for column families is now `kLZ4Compression` rather than `kSnappyCompression`. This affects only column families that do not explicitly set `compression`, and only newly-written SST files. The change is fully compatible: existing data remains readable with no migration needed, as RocksDB selects the decompressor per block. LZ4 offers slightly better compression ratios and decompression CPU efficiency vs. Snappy and similar compression CPU, tested across various server CPUs. When support is not compiled in, the fallback path for default compression is LZ4 -> Snappy -> NoCompression.
* Disable parallel compression (ignore `CompressionOptions::parallel_threads`) for fast built-in compressors: Snappy, LZ4 (accelerated, not LZ4HC), and accelerated levels of ZSTD (level < 0). These do not generally benefit from parallel compression. This behavior can be overridden with a custom Compressor from a custom CompressionManager.
* `PosixFileSystem::OptimizeForCompactionTableRead` now delegates to the base `FileSystem::OptimizeForCompactionTableRead` implementation before applying its Linux-only compaction-readahead clamp (added for #12038). The returned `FileOptions::use_direct_reads` therefore reflects `DBOptions::use_direct_reads`, consistent with the base class and with other `FileSystem` implementations. Previously the override ignored the base implementation and keyed both the returned options and the Linux readahead clamp off the incoming `FileOptions` rather than `DBOptions`. At the in-tree call site the incoming `FileOptions::use_direct_reads` already matches `DBOptions::use_direct_reads`, so this is effectively a no-op for existing configurations; it removes a latent inconsistency where a caller passing `FileOptions` whose `use_direct_reads` disagreed with `DBOptions` would have had the global flag silently ignored for compaction-input reads on Linux. (Note: the `#12038` readahead clamp still keys off the global `use_direct_reads`; it is not skipped for the compaction-only `use_direct_io_for_compaction_reads` path, since that flag enables O_DIRECT after this hook runs.)
* Brought more unity to `kLZ4Compression` and `kLZ4HCCompression` by giving each access to the other's compression levels. Negative (fast) values previously only available to LZ4 and positive (slow) values previously only available to LZ4HC are now available to both, so configuring a non-default `compression_opts.level` now selects the LZ4 compressor variant. This is a behavior change for previously tolerated but dubious configurations such as positive compression level with `kLZ4Compression`. `kLZ4Compression` and `kLZ4HCCompression` keep their effective default levels, equivalent to -1 and 9 respectively.
* Removed a discontinuity in `kZSTD` compression levels at `compression_opts.level == 0` by mapping that setting to `level = -1` rather than to `level = 3` as the ZSTD library does internally. This improves the compression auto-tuning landscape.
### Bug Fixes
* Fixed a bug where the range lock manager would crash with an assertion failure when using a reverse comparator column family.
* Fixed a use after free bug in Multiscan when async IO is used
### Performance Improvements
* Reduced commit latency when ingesting many external files by allowing `IngestExternalFileOptions::file_opening_threads` to open table readers for committed ingested files using multiple threads.
* Reduced commit latency for large external file ingestions into the last level by adding `IngestExternalFileOptions::prefetch_lmax_index_and_filter_blocks`, which can skip commit-time index and filter block prefetching for cache-backed table metadata.
## 11.4.1 (06/05/2026)
* No-op patch used to sync with internal fb version.
## 11.4.0 (06/02/2026)
### Public API Changes
@@ -58,7 +36,6 @@
### Performance Improvements
* Reduce the likelihood of user-facing metadata operations blocking on MANIFEST rotation when file creation is slow, such as on remote storage. MANIFEST write batches containing foreground edits from `CreateColumnFamily()`, `DropColumnFamily()`, `CreateColumnFamilyWithImport()`, `IngestExternalFile()`, and `DeleteFilesInRanges()` now get a relaxed file size threshold for triggering MANIFEST rotation; background-only MANIFEST write batches, such as compaction and flush, continue to use the normal threshold. This change should make auto-tuning manifest file size more attractive (see `max_manifest_file_size` and `max_manifest_space_amp_pct` options).
## 11.3.0 (05/15/2026)
### New Features
* Add experimental DB option `async_wal_precreate` to precreate the next WAL file in a background thread and reduce foreground WAL rotation latency. The option is sanitized to false when WAL recycling is enabled.
+26 -26
View File
@@ -46,7 +46,7 @@ This document provides guidance on how to add new options to RocksDB's public AP
## Pattern 1: Adding a Standard Column Family Option
Example reference: commit `94e65a2e0b4f817aa4bfa4c96cdf867e7980d7bc` (memtable_verify_per_key_checksum_on_seek)
Example reference: commit `94e65a2e0b4f817aa4bfa4c96cdf867e7980d7bc` (memtable_veirfy_per_key_checksum_on_seek)
### Step 1: Define the Option in Public Header
@@ -63,7 +63,7 @@ Add the option with documentation in `AdvancedColumnFamilyOptions` struct:
// operation.
// This option depends on memtable_protection_bytes_per_key to be non zero.
// If memtable_protection_bytes_per_key is zero, no validation is performed.
bool memtable_verify_per_key_checksum_on_seek = false;
bool memtable_veirfy_per_key_checksum_on_seek = false;
```
### Step 2: Add to Internal Options Structs
@@ -74,14 +74,14 @@ Add to `MutableCFOptions` struct (or `ImmutableCFOptions` for immutable options)
```cpp
// In MutableCFOptions constructor from Options:
memtable_verify_per_key_checksum_on_seek(
options.memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek(
options.memtable_veirfy_per_key_checksum_on_seek),
// In MutableCFOptions default constructor:
memtable_verify_per_key_checksum_on_seek(false),
memtable_veirfy_per_key_checksum_on_seek(false),
// In MutableCFOptions struct member declarations:
bool memtable_verify_per_key_checksum_on_seek;
bool memtable_veirfy_per_key_checksum_on_seek;
```
### Step 3: Register for Serialization/Deserialization
@@ -91,9 +91,9 @@ bool memtable_verify_per_key_checksum_on_seek;
Add to the options type info map for serialization:
```cpp
{"memtable_verify_per_key_checksum_on_seek",
{"memtable_veirfy_per_key_checksum_on_seek",
{offsetof(struct MutableCFOptions,
memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
```
@@ -101,8 +101,8 @@ Add to the options type info map for serialization:
Add logging in `MutableCFOptions::Dump()`:
```cpp
ROCKS_LOG_INFO(log, "memtable_verify_per_key_checksum_on_seek: %d",
memtable_verify_per_key_checksum_on_seek);
ROCKS_LOG_INFO(log, "memtable_veirfy_per_key_checksum_on_seek: %d",
memtable_veirfy_per_key_checksum_on_seek);
```
### Step 4: Update Options Helper
@@ -112,8 +112,8 @@ ROCKS_LOG_INFO(log, "memtable_verify_per_key_checksum_on_seek: %d",
Add to `UpdateColumnFamilyOptions()`:
```cpp
cf_opts->memtable_verify_per_key_checksum_on_seek =
moptions.memtable_verify_per_key_checksum_on_seek;
cf_opts->memtable_veirfy_per_key_checksum_on_seek =
moptions.memtable_veirfy_per_key_checksum_on_seek;
```
### Step 5: Add to Options Settable Test
@@ -123,7 +123,7 @@ cf_opts->memtable_verify_per_key_checksum_on_seek =
Add to the test string in `ColumnFamilyOptionsAllFieldsSettable`:
```cpp
"memtable_verify_per_key_checksum_on_seek=1;"
"memtable_veirfy_per_key_checksum_on_seek=1;"
```
### Step 6: Add db_stress Support
@@ -131,23 +131,23 @@ Add to the test string in `ColumnFamilyOptionsAllFieldsSettable`:
**File: `db_stress_tool/db_stress_common.h`**
```cpp
DECLARE_bool(memtable_verify_per_key_checksum_on_seek);
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
```
**File: `db_stress_tool/db_stress_gflags.cc`**
```cpp
DEFINE_bool(
memtable_verify_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_verify_per_key_checksum_on_seek,
"Sets CF option memtable_verify_per_key_checksum_on_seek.");
memtable_veirfy_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_veirfy_per_key_checksum_on_seek,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek.");
```
**File: `db_stress_tool/db_stress_test_base.cc`**
```cpp
options.memtable_verify_per_key_checksum_on_seek =
FLAGS_memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 7: Add db_bench Support
@@ -156,12 +156,12 @@ options.memtable_verify_per_key_checksum_on_seek =
```cpp
// Flag definition (near related flags):
DEFINE_bool(memtable_verify_per_key_checksum_on_seek, false,
"Sets CF option memtable_verify_per_key_checksum_on_seek");
DEFINE_bool(memtable_veirfy_per_key_checksum_on_seek, false,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek");
// Apply flag to options (in InitializeOptionsFromFlags or similar):
options.memtable_verify_per_key_checksum_on_seek =
FLAGS_memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
```
### Step 8: Add Crash Test Support
@@ -169,7 +169,7 @@ options.memtable_verify_per_key_checksum_on_seek =
**File: `tools/db_crashtest.py`**
```python
"memtable_verify_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
"memtable_veirfy_per_key_checksum_on_seek": lambda: random.choice([0] * 7 + [1]),
```
Also add constraint handling in `finalize_and_sanitize()` if needed:
@@ -177,7 +177,7 @@ Also add constraint handling in `finalize_and_sanitize()` if needed:
```python
# only skip list memtable representation supports paranoid memory checks
if dest_params.get("memtablerep") != "skip_list":
dest_params["memtable_verify_per_key_checksum_on_seek"] = 0
dest_params["memtable_veirfy_per_key_checksum_on_seek"] = 0
```
### Step 9: Add Release Note
@@ -185,7 +185,7 @@ if dest_params.get("memtablerep") != "skip_list":
**File: `unreleased_history/new_features/<descriptive_name>.md`**
```markdown
A new flag memtable_verify_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
A new flag memtable_veirfy_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
```
---
+26 -132
View File
@@ -30,78 +30,13 @@ inline static SequenceNumber GetSeqNum(const DBImpl* db, const Snapshot* s) {
Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
std::string* prop) {
if (prop_name == "rocksdb.iterator.super-version-number") {
if (!internal_iter_initialized_) {
*prop = std::to_string(sv_number_);
return Status::OK();
}
// First try to pass the value returned from inner iterator.
if (!db_iter_->GetProperty(prop_name, prop).ok()) {
*prop = std::to_string(sv_number_);
}
return Status::OK();
}
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return db_iter_->status();
}
return db_iter_->GetProperty(std::move(prop_name), prop);
}
void ArenaWrappedDBIter::CleanupDeferredSuperVersion() {
if (deferred_sv_ != nullptr) {
assert(db_impl_ != nullptr);
db_impl_->CleanupIteratorSuperVersion(
deferred_sv_, read_options_.background_purge_on_iterator_cleanup);
deferred_sv_ = nullptr;
}
}
void ArenaWrappedDBIter::DestroyDBIter() {
db_iter_->~DBIter();
CleanupDeferredSuperVersion();
}
void ArenaWrappedDBIter::ColumnFamilyDataUnrefDeleter::operator()(
ColumnFamilyData* cfd) const {
if (cfd == nullptr) {
return;
}
assert(db_impl != nullptr);
InstrumentedMutexLock lock(db_impl->mutex());
cfd->UnrefAndTryDelete();
}
void ArenaWrappedDBIter::DestroyDBIterAndArena() {
DestroyDBIter();
arena_.~Arena();
}
Status ArenaWrappedDBIter::EnsureInternalIteratorInitialized(
const MultiScanArgs* scan_opts) {
if (internal_iter_initialized_) {
return Status::OK();
}
if (db_impl_ == nullptr || deferred_cfd_ == nullptr ||
deferred_sv_ == nullptr) {
Status s = Status::InvalidArgument(
"Internal iterator cannot be initialized without deferred DB state");
db_iter_->set_status(s);
db_iter_->set_valid(false);
return s;
}
const MultiScanArgs* pruning_scan_opts =
scan_opts != nullptr && scan_opts->HasBoundedScanRanges() ? scan_opts
: nullptr;
child_read_options_ = read_options_;
child_read_options_.snapshot = nullptr;
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
child_read_options_, deferred_cfd_, deferred_sv_, &arena_, sequence_,
/*allow_unprepared_value=*/true, this, pruning_scan_opts);
deferred_cfd_ = nullptr;
deferred_sv_ = nullptr;
SetIterUnderDBIterImpl(internal_iter);
return Status::OK();
return db_iter_->GetProperty(prop_name, prop);
}
void ArenaWrappedDBIter::Init(
@@ -109,26 +44,18 @@ void ArenaWrappedDBIter::Init(
const MutableCFOptions& mutable_cf_options, const Version* version,
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem,
DBImpl* db_impl, ColumnFamilyData* cfd) {
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem) {
read_options_ = read_options;
child_read_options_ = read_options;
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
FSSupportedOps::kAsyncIO)) {
read_options_.async_io = false;
}
read_options_.total_order_seek |= ioptions.prefix_seek_opt_in_only;
if (cfh != nullptr) {
db_impl = cfh->db();
cfd = cfh->cfd();
}
db_iter_ = DBIter::NewIter(env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator,
/*internal_iter=*/nullptr, version, sequence,
read_callback, active_mem, /*cfh=*/nullptr,
expose_blob_index, &arena_, db_impl, cfd);
db_iter_ = DBIter::NewIter(
env, read_options_, ioptions, mutable_cf_options,
ioptions.user_comparator, /*internal_iter=*/nullptr, version, sequence,
read_callback, active_mem, cfh, expose_blob_index, &arena_);
sv_number_ = version_number;
allow_refresh_ = allow_refresh;
@@ -138,13 +65,13 @@ void ArenaWrappedDBIter::Init(
void ArenaWrappedDBIter::MaybeAutoRefresh(bool is_seek,
DBIter::Direction direction) {
if (cfd_ref_ != nullptr && read_options_.snapshot != nullptr &&
allow_refresh_ && read_options_.auto_refresh_iterator_with_snapshot) {
if (cfh_ != nullptr && read_options_.snapshot != nullptr && allow_refresh_ &&
read_options_.auto_refresh_iterator_with_snapshot) {
// The intent here is to capture the superversion number change
// reasonably soon from the time it actually happened. As such,
// we're fine with weaker synchronization / ordering guarantees
// provided by relaxed atomic (in favor of less CPU / mem overhead).
uint64_t cur_sv_number = cfd_ref_->GetSuperVersionNumberRelaxed();
uint64_t cur_sv_number = cfh_->cfd()->GetSuperVersionNumberRelaxed();
if ((sv_number_ != cur_sv_number) && status().ok()) {
// Changing iterators' direction is pretty heavy-weight operation and
// could have unintended consequences when it comes to prefix seek.
@@ -223,13 +150,13 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
// present in the error log, but won't be reflected in the iterator status.
// This is by design as we expect compaction to clean up those obsolete files
// eventually.
DestroyDBIterAndArena();
db_iter_->~DBIter();
arena_.~Arena();
new (&arena_) Arena();
auto cfd = cfd_ref_.get();
auto db_impl = db_impl_;
assert(cfd != nullptr);
assert(db_impl != nullptr);
auto cfd = cfh_->cfd();
auto db_impl = cfh_->db();
SuperVersion* sv = cfd->GetReferencedSuperVersion(db_impl);
assert(sv->version_number >= sv_number);
@@ -237,27 +164,23 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
if (read_callback_) {
read_callback_->Refresh(read_seq);
}
// TODO: Preserve Prepare() scan options across Refresh() so a refreshed
// MultiScan iterator can rebuild the same pruned tree.
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options, sv->current,
read_seq, sv->version_number, read_callback_, nullptr,
expose_blob_index_, allow_refresh_,
allow_mark_memtable_for_flush_ ? sv->mem : nullptr, db_impl, cfd);
read_seq, sv->version_number, read_callback_, cfh_, expose_blob_index_,
allow_refresh_, allow_mark_memtable_for_flush_ ? sv->mem : nullptr);
InternalIterator* internal_iter = db_impl->NewInternalIterator(
read_options_, cfd, sv, &arena_, read_seq,
/* allow_unprepared_value */ true, /* db_iter */ this);
SetIterUnderDBIter(internal_iter);
internal_iter_initialized_ = true;
}
Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
if (cfd_ref_ == nullptr || db_impl_ == nullptr || !allow_refresh_) {
if (cfh_ == nullptr || !allow_refresh_) {
return Status::NotSupported("Creating renew iterator is not allowed.");
}
assert(db_iter_ != nullptr);
auto cfd = cfd_ref_.get();
auto db_impl = db_impl_;
auto cfd = cfh_->cfd();
auto db_impl = cfh_->db();
// TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the
// correct behavior. Will be corrected automatically when we take a snapshot
@@ -270,13 +193,6 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
if (!internal_iter_initialized_) {
Status s = EnsureInternalIteratorInitialized(nullptr);
if (!s.ok()) {
return s;
}
}
while (true) {
if (sv_number_ != cur_sv_number) {
DoRefresh(snapshot, cur_sv_number);
@@ -334,47 +250,25 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
return Status::OK();
}
void ArenaWrappedDBIter::Prepare(const MultiScanArgs& scan_opts) {
if (prepare_called_) {
db_iter_->set_status(Status::InvalidArgument(
"Prepare called more than once on the same iterator"));
db_iter_->set_valid(false);
return;
}
prepare_called_ = true;
Status s = db_iter_->SetScanOptionsForPrepare(scan_opts);
if (!s.ok()) {
return;
}
const MultiScanArgs* pruning_scan_opts =
scan_opts.HasBoundedScanRanges() ? &scan_opts : nullptr;
s = EnsureInternalIteratorInitialized(pruning_scan_opts);
if (!s.ok()) {
return;
}
db_iter_->PrepareInternalChildren();
}
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
SuperVersion* sv, const SequenceNumber& sequence,
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
bool allow_refresh, bool allow_mark_memtable_for_flush) {
ArenaWrappedDBIter* db_iter = new ArenaWrappedDBIter();
ColumnFamilyData* cfd = cfh->cfd();
db_iter->Init(env, read_options, cfh->cfd()->ioptions(),
sv->mutable_cf_options, sv->current, sequence,
sv->version_number, read_callback, cfh, expose_blob_index,
allow_refresh,
allow_mark_memtable_for_flush ? sv->mem : nullptr);
if (allow_refresh && cfd != nullptr) {
db_iter->StoreRefreshInfo(read_callback, expose_blob_index);
if (cfh != nullptr && allow_refresh) {
db_iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
}
db_iter->StoreDeferredInitInfo(db_impl, cfd, sv, sequence,
allow_mark_memtable_for_flush);
InternalIterator* internal_iter = db_impl->NewInternalIterator(
db_iter->GetReadOptions(), cfh->cfd(), sv, db_iter->GetArena(), sequence,
/*allow_unprepared_value=*/true, db_iter);
db_iter->SetIterUnderDBIter(internal_iter);
return db_iter;
}
+17 -74
View File
@@ -10,7 +10,6 @@
#pragma once
#include <stdint.h>
#include <memory>
#include <string>
#include "db/db_impl/db_impl.h"
@@ -34,17 +33,10 @@ class Version;
// When using the class's Iterator interface, the behavior is exactly
// the same as the inner DBIter.
class ArenaWrappedDBIter : public Iterator {
struct ColumnFamilyDataUnrefDeleter {
DBImpl* db_impl = nullptr;
void operator()(ColumnFamilyData* cfd) const;
};
using ColumnFamilyDataRef =
std::unique_ptr<ColumnFamilyData, ColumnFamilyDataUnrefDeleter>;
public:
~ArenaWrappedDBIter() override {
if (db_iter_ != nullptr) {
DestroyDBIter();
db_iter_->~DBIter();
} else {
assert(false);
}
@@ -59,7 +51,7 @@ class ArenaWrappedDBIter : public Iterator {
// Set the internal iterator wrapped inside the DB Iterator. Usually it is
// a merging iterator.
virtual void SetIterUnderDBIter(InternalIterator* iter) {
SetIterUnderDBIterImpl(iter);
db_iter_->SetIter(iter);
}
void SetMemtableRangetombstoneIter(
@@ -68,48 +60,26 @@ class ArenaWrappedDBIter : public Iterator {
}
bool Valid() const override { return db_iter_->Valid(); }
void SeekToFirst() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->SeekToFirst();
}
void SeekToLast() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->SeekToLast();
}
void SeekToFirst() override { db_iter_->SeekToFirst(); }
void SeekToLast() override { db_iter_->SeekToLast(); }
// 'target' does not contain timestamp, even if user timestamp feature is
// enabled.
void Seek(const Slice& target) override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
MaybeAutoRefresh(true /* is_seek */, DBIter::kForward);
db_iter_->Seek(target);
}
void SeekForPrev(const Slice& target) override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
MaybeAutoRefresh(true /* is_seek */, DBIter::kReverse);
db_iter_->SeekForPrev(target);
}
void Next() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->Next();
MaybeAutoRefresh(false /* is_seek */, DBIter::kForward);
}
void Prev() override {
if (!EnsureInternalIteratorInitialized(nullptr).ok()) {
return;
}
db_iter_->Prev();
MaybeAutoRefresh(false /* is_seek */, DBIter::kReverse);
}
@@ -126,12 +96,11 @@ class ArenaWrappedDBIter : public Iterator {
Status Refresh() override;
Status Refresh(const Snapshot*) override;
bool PrepareValue() override {
return EnsureInternalIteratorInitialized(nullptr).ok() &&
db_iter_->PrepareValue();
}
bool PrepareValue() override { return db_iter_->PrepareValue(); }
void Prepare(const MultiScanArgs& scan_opts) override;
void Prepare(const MultiScanArgs& scan_opts) override {
db_iter_->Prepare(scan_opts);
}
// FIXME: we could just pass SV in for mutable cf option, version and version
// number, but this is used by SstFileReader which does not have a SV.
@@ -141,53 +110,27 @@ class ArenaWrappedDBIter : public Iterator {
const SequenceNumber& sequence, uint64_t version_number,
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, bool allow_refresh,
ReadOnlyMemTable* active_mem, DBImpl* db_impl = nullptr,
ColumnFamilyData* cfd = nullptr);
ReadOnlyMemTable* active_mem);
// Store parameters used only by explicit/auto-refresh.
void StoreRefreshInfo(ReadCallback* read_callback, bool expose_blob_index) {
// Store some parameters so we can refresh the iterator at a later point
// with these same params
void StoreRefreshInfo(ColumnFamilyHandleImpl* cfh,
ReadCallback* read_callback, bool expose_blob_index) {
cfh_ = cfh;
read_callback_ = read_callback;
expose_blob_index_ = expose_blob_index;
}
void StoreDeferredInitInfo(DBImpl* db_impl, ColumnFamilyData* cfd,
SuperVersion* sv, const SequenceNumber& sequence,
bool allow_mark_memtable_for_flush) {
assert(cfd != nullptr);
db_impl_ = db_impl;
cfd->Ref();
cfd_ref_ = ColumnFamilyDataRef(cfd, ColumnFamilyDataUnrefDeleter{db_impl});
deferred_cfd_ = cfd;
deferred_sv_ = sv;
sequence_ = sequence;
allow_mark_memtable_for_flush_ = allow_mark_memtable_for_flush;
}
private:
Status EnsureInternalIteratorInitialized(const MultiScanArgs* scan_opts);
void SetIterUnderDBIterImpl(InternalIterator* iter) {
db_iter_->SetIter(iter);
internal_iter_initialized_ = true;
}
void CleanupDeferredSuperVersion();
void DestroyDBIter();
void DestroyDBIterAndArena();
void DoRefresh(const Snapshot* snapshot, uint64_t sv_number);
void MaybeAutoRefresh(bool is_seek, DBIter::Direction direction);
DBIter* db_iter_ = nullptr;
Arena arena_;
uint64_t sv_number_ = 0;
DBImpl* db_impl_ = nullptr;
ColumnFamilyDataRef cfd_ref_{nullptr, ColumnFamilyDataUnrefDeleter{}};
ColumnFamilyData* deferred_cfd_ = nullptr;
SuperVersion* deferred_sv_ = nullptr;
SequenceNumber sequence_ = kMaxSequenceNumber;
bool internal_iter_initialized_ = false;
bool prepare_called_ = false;
uint64_t sv_number_;
ColumnFamilyHandleImpl* cfh_ = nullptr;
ReadOptions read_options_;
ReadOptions child_read_options_;
ReadCallback* read_callback_ = nullptr;
ReadCallback* read_callback_;
bool expose_blob_index_ = false;
bool allow_refresh_ = true;
bool allow_mark_memtable_for_flush_ = true;
-3
View File
@@ -22,7 +22,6 @@
#include "logging/logging.h"
#include "options/cf_options.h"
#include "options/options_helper.h"
#include "rocksdb/file_system.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "test_util/sync_point.h"
@@ -201,8 +200,6 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
{
assert(file_options_);
fo_copy = *file_options_;
fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
FileOpenContract::kNoReadersWhileOpenForWrite;
fo_copy.write_hint = write_hint_;
Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy);
-1
View File
@@ -123,7 +123,6 @@ Status BlobFileCache::OpenBlobFileReaderUncached(
Statistics* const statistics = immutable_options_->stats;
RecordTick(statistics, NO_FILE_OPENS);
assert(file_options_);
Status s = BlobFileReader::Create(
*immutable_options_, read_options, *file_options_, column_family_id_,
blob_file_read_hist_, blob_file_number, io_tracer_,
+2 -5
View File
@@ -24,7 +24,6 @@
#include "file/read_write_util.h"
#include "file/writable_file_writer.h"
#include "logging/logging.h"
#include "rocksdb/file_system.h"
#include "util/aligned_buffer.h"
#include "util/compression.h"
#include "util/mutexlock.h"
@@ -200,9 +199,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
}
std::unique_ptr<FSWritableFile> file;
FileOptions writer_file_options = file_options_;
writer_file_options.open_contract = FileOpenContract::kNoReopenForWrite;
Status s = NewWritableFile(fs_, blob_file_path, &file, writer_file_options);
Status s = NewWritableFile(fs_, blob_file_path, &file, file_options_);
if (!s.ok()) {
RemoveFilePartitionMapping(blob_file_number);
return s;
@@ -211,7 +208,7 @@ Status BlobFilePartitionManager::OpenNewBlobFile(Partition* partition,
const bool perform_data_verification =
checksum_handoff_file_types_.Contains(FileType::kBlobFile);
auto file_writer = std::make_unique<WritableFileWriter>(
std::move(file), blob_file_path, writer_file_options, clock_, io_tracer_,
std::move(file), blob_file_path, file_options_, clock_, io_tracer_,
statistics_, Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS, listeners_,
file_checksum_gen_factory_, perform_data_verification);
+18 -19
View File
@@ -12,7 +12,6 @@
#include "db/blob/blob_log_format.h"
#include "file/file_prefetch_buffer.h"
#include "file/filename.h"
#include "file/read_write_util.h"
#include "monitoring/statistics_impl.h"
#include "options/cf_options.h"
#include "rocksdb/file_system.h"
@@ -107,6 +106,24 @@ Status BlobFileReader::OpenFile(
constexpr IODebugContext* dbg = nullptr;
{
TEST_SYNC_POINT("BlobFileReader::OpenFile:GetFileSize");
const Status s =
fs->GetFileSize(blob_file_path, IOOptions(), file_size, dbg);
if (!s.ok()) {
return s;
}
}
if (!skip_footer_size_check &&
*file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
return Status::Corruption("Malformed blob file");
}
if (skip_footer_size_check && *file_size < BlobLogHeader::kSize) {
return Status::Corruption("Malformed blob file");
}
std::unique_ptr<FSRandomAccessFile> file;
FileOptions reader_file_opts = file_opts;
@@ -126,24 +143,6 @@ Status BlobFileReader::OpenFile(
assert(file);
{
Status s = GetFileSizeFromOpenFileOrPath(
file.get(), fs, blob_file_path, file_size, dbg,
FileSizeFallback::kNotSupportedOnly,
[]() { TEST_SYNC_POINT("BlobFileReader::OpenFile:GetFileSize"); });
if (!s.ok()) {
return s;
}
}
if (!skip_footer_size_check &&
*file_size < BlobLogHeader::kSize + BlobLogFooter::kSize) {
return Status::Corruption("Malformed blob file");
}
if (skip_footer_size_check && *file_size < BlobLogHeader::kSize) {
return Status::Corruption("Malformed blob file");
}
if (immutable_options.advise_random_on_open) {
file->Hint(FSRandomAccessFile::kRandom);
}
+1 -368
View File
@@ -7,12 +7,10 @@
#include <cassert>
#include <string>
#include <utility>
#include "db/blob/blob_contents.h"
#include "db/blob/blob_log_format.h"
#include "db/blob/blob_log_writer.h"
#include "env/composite_env_wrapper.h"
#include "env/mock_env.h"
#include "file/filename.h"
#include "file/read_write_util.h"
@@ -138,167 +136,6 @@ class BlobFileReaderTest : public testing::Test {
std::unique_ptr<Env> mock_env_;
};
namespace {
// Returns a stale path-level size for one target blob file while leaving the
// opened file handle untouched. This lets the test verify
// BlobFileReader::Create prefers the opened handle's size when the path stat
// lags behind.
class StalePathSizeFileSystem : public FileSystemWrapper {
public:
static const char* kClassName() { return "StalePathSizeFileSystem"; }
StalePathSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string stale_path)
: FileSystemWrapper(target), stale_path_(std::move(stale_path)) {}
const char* Name() const override { return kClassName(); }
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (fname == stale_path_) {
*file_size = 0;
return IOStatus::OK();
}
return FileSystemWrapper::GetFileSize(fname, options, file_size, dbg);
}
private:
const std::string stale_path_;
};
// Makes opened blob file handles fail GetFileSize() with a hard error so the
// reader path can verify that such errors are propagated directly.
class FailingOpenFileSizeRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
explicit FailingOpenFileSizeRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target)
: FSRandomAccessFileOwnerWrapper(std::move(target)) {}
IOStatus GetFileSize(uint64_t* /*result*/) override {
return IOStatus::IOError("open file size failed");
}
};
// Makes opened blob file handles report GetFileSize() as unsupported so the
// reader path exercises its path-level fallback logic.
class NotSupportedOpenFileSizeRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
explicit NotSupportedOpenFileSizeRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target)
: FSRandomAccessFileOwnerWrapper(std::move(target)) {}
IOStatus GetFileSize(uint64_t* /*result*/) override {
return IOStatus::NotSupported("open file size not supported");
}
};
class OpenFileSizeFileSystem : public FileSystemWrapper {
public:
OpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: FileSystemWrapper(target), target_path_(std::move(target_path)) {}
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& options,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSRandomAccessFile> file;
IOStatus s =
FileSystemWrapper::NewRandomAccessFile(fname, options, &file, dbg);
if (!s.ok()) {
return IOStatus(std::move(s));
}
if (fname == target_path_) {
*result = WrapTargetFile(std::move(file));
} else {
*result = std::move(file);
}
return IOStatus::OK();
}
protected:
virtual std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) = 0;
private:
const std::string target_path_;
};
// Wraps the target blob file in FailingOpenFileSizeRandomAccessFile while
// leaving all other files alone.
class FailingOpenFileSizeFileSystem : public OpenFileSizeFileSystem {
public:
static const char* kClassName() { return "FailingOpenFileSizeFileSystem"; }
FailingOpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: OpenFileSizeFileSystem(target, std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
private:
std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) override {
return std::unique_ptr<FSRandomAccessFile>(
new FailingOpenFileSizeRandomAccessFile(std::move(file)));
}
};
// Wraps the target blob file in NotSupportedOpenFileSizeRandomAccessFile while
// leaving all other files alone.
class NotSupportedOpenFileSizeFileSystem : public OpenFileSizeFileSystem {
public:
static const char* kClassName() {
return "NotSupportedOpenFileSizeFileSystem";
}
NotSupportedOpenFileSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: OpenFileSizeFileSystem(target, std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
private:
std::unique_ptr<FSRandomAccessFile> WrapTargetFile(
std::unique_ptr<FSRandomAccessFile>&& file) override {
return std::unique_ptr<FSRandomAccessFile>(
new NotSupportedOpenFileSizeRandomAccessFile(std::move(file)));
}
};
// Forces the target blob file onto the NotSupported open-handle-size branch and
// then fails the fallback path-level GetFileSize() call.
class FailingFallbackPathSizeFileSystem
: public NotSupportedOpenFileSizeFileSystem {
public:
static const char* kClassName() {
return "FailingFallbackPathSizeFileSystem";
}
FailingFallbackPathSizeFileSystem(const std::shared_ptr<FileSystem>& target,
std::string target_path)
: NotSupportedOpenFileSizeFileSystem(target, target_path),
target_path_(std::move(target_path)) {}
const char* Name() const override { return kClassName(); }
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (fname == target_path_) {
return IOStatus::IOError("fallback path size failed");
}
return FileSystemWrapper::GetFileSize(fname, options, file_size, dbg);
}
private:
const std::string target_path_;
};
} // namespace
TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
Options options;
options.env = mock_env_.get();
@@ -591,211 +428,6 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
}
}
// Verifies Create() prefers the file size reported by the opened handle over a
// stale path-level size query by returning a readable reader even when the path
// stat intentionally reports 0 for the blob file.
TEST_F(BlobFileReaderTest, CreateReaderUsesOpenedFileSizeWhenPathSizeIsStale) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_CreateReaderUsesOpenedFileSizeWhenPathSizeIsStale");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto stale_size_fs = std::make_shared<StalePathSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper stale_size_env(mock_env_.get(), stale_size_fs);
Options options;
options.env = &stale_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
read_options.verify_checksums = false;
ASSERT_OK(BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader));
ASSERT_NE(reader, nullptr);
std::unique_ptr<BlobContents> value;
uint64_t bytes_read = 0;
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
kNoCompression,
/*prefetch_buffer=*/nullptr,
/*allocator=*/nullptr, &value, &bytes_read));
ASSERT_NE(value, nullptr);
ASSERT_EQ(value->data(), Slice(blob));
ASSERT_EQ(bytes_read, blob_size);
}
// Verifies Create() falls back to the path-level GetFileSize() call when the
// opened file handle reports that size queries are unsupported.
TEST_F(BlobFileReaderTest,
CreateReaderFallsBackToPathSizeWhenOpenedFileSizeNotSupported) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_"
"CreateReaderFallsBackToPathSizeWhenOpenedFileSizeNotSupported");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto not_supported_open_file_size_fs =
std::make_shared<NotSupportedOpenFileSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper not_supported_open_file_size_env(
mock_env_.get(), not_supported_open_file_size_fs);
Options options;
options.env = &not_supported_open_file_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
read_options.verify_checksums = false;
ASSERT_OK(BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader));
ASSERT_NE(reader, nullptr);
std::unique_ptr<BlobContents> value;
uint64_t bytes_read = 0;
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
kNoCompression,
/*prefetch_buffer=*/nullptr,
/*allocator=*/nullptr, &value, &bytes_read));
ASSERT_NE(value, nullptr);
ASSERT_EQ(value->data(), Slice(blob));
ASSERT_EQ(bytes_read, blob_size);
}
// Verifies Create() propagates a real error from the opened file handle's
// GetFileSize() rather than masking it with any path-level fallback.
TEST_F(BlobFileReaderTest, CreateReaderPropagatesOpenedFileSizeError) {
const std::string db_path = test::PerThreadDBPath(
mock_env_.get(),
"BlobFileReaderTest_CreateReaderPropagatesOpenedFileSizeError");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto failing_open_file_size_fs =
std::make_shared<FailingOpenFileSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper failing_open_file_size_env(mock_env_.get(),
failing_open_file_size_fs);
Options options;
options.env = &failing_open_file_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
Status s = BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader);
ASSERT_TRUE(s.IsIOError());
ASSERT_EQ(reader, nullptr);
}
// Verifies Create() also propagates errors from the path-level fallback size
// query when the opened file handle only reports NotSupported for GetFileSize.
TEST_F(
BlobFileReaderTest,
CreateReaderPropagatesFallbackPathSizeErrorWhenOpenedFileSizeNotSupported) {
const std::string db_path =
test::PerThreadDBPath(mock_env_.get(),
"BlobFileReaderTest_"
"CreateReaderPropagatesFallbackPathSizeErrorWhenOpe"
"nedFileSizeNotSupported");
constexpr uint64_t blob_file_number = 1;
const std::string blob_file_path = BlobFileName(db_path, blob_file_number);
auto failing_fallback_path_size_fs =
std::make_shared<FailingFallbackPathSizeFileSystem>(
mock_env_->GetFileSystem(), blob_file_path);
CompositeEnvWrapper failing_fallback_path_size_env(
mock_env_.get(), failing_fallback_path_size_fs);
Options options;
options.env = &failing_fallback_path_size_env;
options.cf_paths.emplace_back(db_path, 0);
options.enable_blob_files = true;
ImmutableOptions immutable_options(options);
constexpr uint32_t column_family_id = 1;
constexpr bool has_ttl = false;
constexpr ExpirationRange expiration_range;
constexpr char key[] = "key";
constexpr char blob[] = "blob";
uint64_t blob_offset = 0;
uint64_t blob_size = 0;
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
expiration_range, blob_file_number, key, blob, kNoCompression,
&blob_offset, &blob_size);
std::unique_ptr<BlobFileReader> reader;
ReadOptions read_options;
Status s = BlobFileReader::Create(
immutable_options, read_options, FileOptions(), column_family_id,
/*blob_file_read_hist=*/nullptr, blob_file_number, nullptr /*IOTracer*/,
&reader);
ASSERT_TRUE(s.IsIOError());
ASSERT_EQ(reader, nullptr);
}
TEST_F(BlobFileReaderTest, Malformed) {
// Write a blob file consisting of nothing but a header, and make sure we
// detect the error when we open it for reading
@@ -1217,6 +849,7 @@ class BlobFileReaderIOErrorTest
INSTANTIATE_TEST_CASE_P(BlobFileReaderTest, BlobFileReaderIOErrorTest,
::testing::ValuesIn(std::vector<std::string>{
"BlobFileReader::OpenFile:GetFileSize",
"BlobFileReader::OpenFile:NewRandomAccessFile",
"BlobFileReader::ReadHeader:ReadFromFile",
"BlobFileReader::ReadFooter:ReadFromFile",
-229
View File
@@ -10,14 +10,11 @@
#include <algorithm>
#include <array>
#include <functional>
#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "cache/compressed_secondary_cache.h"
@@ -29,12 +26,10 @@
#include "db/db_test_util.h"
#include "db/db_with_timestamp_test_util.h"
#include "db/wide/wide_column_test_util.h"
#include "env/composite_env_wrapper.h"
#include "file/filename.h"
#include "file/random_access_file_reader.h"
#include "port/stack_trace.h"
#include "rocksdb/convenience.h"
#include "rocksdb/file_system.h"
#include "rocksdb/trace_reader_writer.h"
#include "rocksdb/trace_record.h"
#include "rocksdb/utilities/replayer.h"
@@ -106,206 +101,6 @@ class RecordingBlobDirectWritePartitionStrategy
std::vector<Call> calls_;
};
namespace {
// Matches BlobDB file names so the test filesystem can special-case only active
// blob files without changing unrelated file opens.
bool IsBlobFilePath(const std::string& fname) {
const size_t basename_pos = fname.find_last_of("/\\");
const std::string basename = basename_pos == std::string::npos
? fname
: fname.substr(basename_pos + 1);
uint64_t file_number = 0;
FileType file_type = kWalFile;
return ParseFileName(basename, &file_number, &file_type) &&
file_type == kBlobFile;
}
// Keeps track of when an active blob file stops being writer-visible so the
// test filesystem can model current-size visibility only while the file remains
// active.
class ActiveBlobVisibilityWritableFile : public FSWritableFileOwnerWrapper {
public:
ActiveBlobVisibilityWritableFile(std::unique_ptr<FSWritableFile>&& target,
std::function<void()> on_close)
: FSWritableFileOwnerWrapper(std::move(target)),
on_close_(std::move(on_close)) {}
~ActiveBlobVisibilityWritableFile() override { DeactivateOnce(); }
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
IOStatus s = target()->Close(options, dbg);
DeactivateOnce();
return s;
}
private:
void DeactivateOnce() {
if (on_close_) {
on_close_();
on_close_ = nullptr;
}
}
std::function<void()> on_close_;
};
// Simulates a remote readable file whose GetFileSize() either exposes the live
// on-disk size for default-contract active blobs or hides it for
// append-only-no-readers files.
class ActiveBlobVisibilityRandomAccessFile
: public FSRandomAccessFileOwnerWrapper {
public:
ActiveBlobVisibilityRandomAccessFile(
std::unique_ptr<FSRandomAccessFile>&& target, bool expose_current_size,
std::shared_ptr<FileSystem> underlying_fs, std::string fname)
: FSRandomAccessFileOwnerWrapper(std::move(target)),
expose_current_size_(expose_current_size),
underlying_fs_(std::move(underlying_fs)),
fname_(std::move(fname)) {}
IOStatus GetFileSize(uint64_t* result) override {
if (!expose_current_size_) {
*result = 0;
return IOStatus::OK();
}
// Delegate to the underlying FS path-level GetFileSize (bypassing the
// wrapper that reports 0 for active blobs) to simulate a remote FS that
// exposes the current file size through the open handle.
return underlying_fs_->GetFileSize(fname_, IOOptions(), result,
/*dbg=*/nullptr);
}
private:
const bool expose_current_size_;
std::shared_ptr<FileSystem> underlying_fs_;
std::string fname_;
};
// Models a remote filesystem where active direct-write blobs are only
// reader-visible when the writer does not promise no concurrent readers.
class RemoteBlobVisibilityFileSystem : public FileSystemWrapper {
public:
explicit RemoteBlobVisibilityFileSystem(const std::shared_ptr<FileSystem>& fs)
: FileSystemWrapper(fs) {}
static const char* kClassName() { return "RemoteBlobVisibilityFileSystem"; }
const char* Name() const override { return kClassName(); }
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSWritableFile> file;
IOStatus s = target()->NewWritableFile(fname, opts, &file, dbg);
if (!s.ok() || !IsBlobFilePath(fname)) {
*result = std::move(file);
return IOStatus(std::move(s));
}
const bool has_no_reopen_for_write_contract = HasFileOpenContract(
opts.open_contract, FileOpenContract::kNoReopenForWrite);
const bool has_no_readers_while_open_for_write_contract =
HasFileOpenContract(opts.open_contract,
FileOpenContract::kNoReadersWhileOpenForWrite);
{
std::lock_guard<std::mutex> lock(mu_);
saw_no_reopen_for_write_writer_contract_ |=
has_no_reopen_for_write_contract;
saw_no_readers_while_open_for_write_writer_contract_ |=
has_no_readers_while_open_for_write_contract;
if (!has_no_readers_while_open_for_write_contract) {
active_blob_paths_.insert(fname);
}
}
if (has_no_readers_while_open_for_write_contract) {
*result = std::move(file);
return IOStatus::OK();
}
result->reset(new ActiveBlobVisibilityWritableFile(
std::move(file), [this, tracked_path = std::string(fname)]() {
DeactivateBlobPath(tracked_path);
}));
return IOStatus::OK();
}
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
std::unique_ptr<FSRandomAccessFile> file;
IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg);
if (!s.ok() || !IsBlobFilePath(fname)) {
*result = std::move(file);
return IOStatus(std::move(s));
}
const bool has_no_readers_while_open_for_write_contract =
HasFileOpenContract(opts.open_contract,
FileOpenContract::kNoReadersWhileOpenForWrite);
const bool active_blob = IsActiveBlobPath(fname);
{
std::lock_guard<std::mutex> lock(mu_);
saw_no_readers_while_open_for_write_reader_contract_ |=
has_no_readers_while_open_for_write_contract;
}
if (!active_blob) {
*result = std::move(file);
return IOStatus::OK();
}
result->reset(new ActiveBlobVisibilityRandomAccessFile(
std::move(file), !has_no_readers_while_open_for_write_contract, target_,
fname));
return IOStatus::OK();
}
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
uint64_t* file_size, IODebugContext* dbg) override {
if (IsActiveBlobPath(fname)) {
*file_size = 0;
return IOStatus::OK();
}
return target()->GetFileSize(fname, options, file_size, dbg);
}
bool SawNoReopenForWriteWriterContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_reopen_for_write_writer_contract_;
}
bool SawNoReadersWhileOpenForWriteWriterContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_readers_while_open_for_write_writer_contract_;
}
bool SawNoReadersWhileOpenForWriteReaderContract() const {
std::lock_guard<std::mutex> lock(mu_);
return saw_no_readers_while_open_for_write_reader_contract_;
}
private:
void DeactivateBlobPath(const std::string& fname) {
std::lock_guard<std::mutex> lock(mu_);
active_blob_paths_.erase(fname);
}
bool IsActiveBlobPath(const std::string& fname) const {
std::lock_guard<std::mutex> lock(mu_);
return active_blob_paths_.find(fname) != active_blob_paths_.end();
}
mutable std::mutex mu_;
bool saw_no_reopen_for_write_writer_contract_ = false;
bool saw_no_readers_while_open_for_write_writer_contract_ = false;
bool saw_no_readers_while_open_for_write_reader_contract_ = false;
std::unordered_set<std::string> active_blob_paths_;
};
} // namespace
class DBBlobDirectWriteTest : public DBTestBase {
protected:
DBBlobDirectWriteTest()
@@ -1021,30 +816,6 @@ TEST_F(DBBlobDirectWriteTest, DirectWriteImmutableMemtableRead) {
verify_reads();
}
// Verifies active blob direct-write files promise no write reopen without also
// promising no concurrent readers.
TEST_F(DBBlobDirectWriteTest, DirectWriteUsesReadableContractForRemoteFile) {
auto remote_fs =
std::make_shared<RemoteBlobVisibilityFileSystem>(env_->GetFileSystem());
std::unique_ptr<Env> remote_env(new CompositeEnvWrapper(env_, remote_fs));
Options options = GetDirectWriteOptions();
options.env = remote_env.get();
Reopen(options);
const std::string key = "remote_blob_key";
const std::string value(128, 'w');
ASSERT_OK(Put(key, value));
ASSERT_TRUE(remote_fs->SawNoReopenForWriteWriterContract());
ASSERT_FALSE(remote_fs->SawNoReadersWhileOpenForWriteWriterContract());
ASSERT_EQ(Get(key), value);
ASSERT_FALSE(remote_fs->SawNoReadersWhileOpenForWriteReaderContract());
Close();
}
TEST_F(DBBlobDirectWriteTest, DirectWriteRefreshesReaderAfterFlush) {
Options options = GetDirectWriteOptions();
options.blob_direct_write_partitions = 1;
-2
View File
@@ -179,8 +179,6 @@ Status BuildTable(
TEST_SYNC_POINT_CALLBACK("BuildTable:create_file", &use_direct_writes);
#endif // !NDEBUG
FileOptions fo_copy = file_options;
fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
FileOpenContract::kNoReadersWhileOpenForWrite;
fo_copy.write_hint = write_hint;
IOStatus io_s = NewWritableFile(fs, fname, &file, fo_copy);
assert(s.ok());
-43
View File
@@ -148,7 +148,6 @@ using ROCKSDB_NAMESPACE::TransactionDB;
using ROCKSDB_NAMESPACE::TransactionDBOptions;
using ROCKSDB_NAMESPACE::TransactionLogIterator;
using ROCKSDB_NAMESPACE::TransactionOptions;
using ROCKSDB_NAMESPACE::TxnDBWritePolicy;
using ROCKSDB_NAMESPACE::WaitForCompactOptions;
using ROCKSDB_NAMESPACE::WALRecoveryMode;
using ROCKSDB_NAMESPACE::WritableFile;
@@ -1385,10 +1384,6 @@ void rocksdb_backup_engine_info_destroy(
delete info;
}
void rocksdb_backup_engine_stop_backup(rocksdb_backup_engine_t* be) {
be->rep->StopBackup();
}
void rocksdb_backup_engine_close(rocksdb_backup_engine_t* be) {
delete be->rep;
delete be;
@@ -1470,20 +1465,6 @@ uint64_t rocksdb_backup_engine_options_get_restore_rate_limit(
return options->rep.restore_rate_limit;
}
void rocksdb_backup_engine_options_set_backup_rate_limiter(
rocksdb_backup_engine_options_t* options, rocksdb_ratelimiter_t* limiter) {
if (limiter) {
options->rep.backup_rate_limiter = limiter->rep;
}
}
void rocksdb_backup_engine_options_set_restore_rate_limiter(
rocksdb_backup_engine_options_t* options, rocksdb_ratelimiter_t* limiter) {
if (limiter) {
options->rep.restore_rate_limiter = limiter->rep;
}
}
void rocksdb_backup_engine_options_set_max_background_operations(
rocksdb_backup_engine_options_t* options, int val) {
options->rep.max_background_operations = val;
@@ -4266,15 +4247,6 @@ void rocksdb_set_options(rocksdb_t* db, int count, const char* const keys[],
SaveError(errptr, db->rep->SetOptions(options_map));
}
void rocksdb_set_db_options(rocksdb_t* db, int count, const char* const keys[],
const char* const values[], char** errptr) {
std::unordered_map<std::string, std::string> options_map;
for (int i = 0; i < count; i++) {
options_map[keys[i]] = values[i];
}
SaveError(errptr, db->rep->SetDBOptions(options_map));
}
void rocksdb_set_options_cf(rocksdb_t* db,
rocksdb_column_family_handle_t* handle, int count,
const char* const keys[],
@@ -5071,16 +5043,6 @@ unsigned char rocksdb_options_get_use_direct_io_for_flush_and_compaction(
return opt->rep.use_direct_io_for_flush_and_compaction;
}
void rocksdb_options_set_use_direct_io_for_compaction_reads(
rocksdb_options_t* opt, unsigned char v) {
opt->rep.use_direct_io_for_compaction_reads = v;
}
unsigned char rocksdb_options_get_use_direct_io_for_compaction_reads(
rocksdb_options_t* opt) {
return opt->rep.use_direct_io_for_compaction_reads;
}
void rocksdb_options_set_allow_mmap_reads(rocksdb_options_t* opt,
unsigned char v) {
opt->rep.allow_mmap_reads = v;
@@ -7565,11 +7527,6 @@ void rocksdb_transactiondb_options_set_default_lock_timeout(
opt->rep.default_lock_timeout = default_lock_timeout;
}
void rocksdb_transactiondb_options_set_write_policy(
rocksdb_transactiondb_options_t* opt, int write_policy) {
opt->rep.write_policy = static_cast<TxnDBWritePolicy>(write_policy);
}
void rocksdb_transactiondb_options_set_use_per_key_point_lock_mgr(
rocksdb_transactiondb_options_t* opt, int use_per_key_point_lock_mgr) {
opt->rep.use_per_key_point_lock_mgr = use_per_key_point_lock_mgr;
+16 -148
View File
@@ -6,7 +6,6 @@
#include "rocksdb/c.h"
#include <assert.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@@ -15,6 +14,7 @@
#ifndef OS_WIN
#include <unistd.h>
#endif
#include <inttypes.h>
// Can not use port/port.h macros as this is a c file
#ifdef OS_WIN
@@ -32,14 +32,6 @@ int geteuid() {
#endif
static uint64_t GetTestId(void) {
#ifdef OS_WIN
return (uint64_t)geteuid();
#else
return ((uint64_t)geteuid() << 32) ^ (uint64_t)getpid();
#endif
}
const char* phase = "";
static char dbname[200];
static char sstfilename[200];
@@ -856,21 +848,20 @@ int main(int argc, char** argv) {
char* err = NULL;
int run = -1;
snprintf(dbname, sizeof(dbname), "%s/rocksdb_c_test-%" PRIu64, GetTempDir(),
GetTestId());
snprintf(dbname, sizeof(dbname), "%s/rocksdb_c_test-%d", GetTempDir(),
((int)geteuid()));
snprintf(dbbackupname, sizeof(dbbackupname),
"%s/rocksdb_c_test-%" PRIu64 "-backup", GetTempDir(), GetTestId());
snprintf(dbbackupname, sizeof(dbbackupname), "%s/rocksdb_c_test-%d-backup",
GetTempDir(), ((int)geteuid()));
snprintf(dbcheckpointname, sizeof(dbcheckpointname),
"%s/rocksdb_c_test-%" PRIu64 "-checkpoint", GetTempDir(),
GetTestId());
"%s/rocksdb_c_test-%d-checkpoint", GetTempDir(), ((int)geteuid()));
snprintf(sstfilename, sizeof(sstfilename),
"%s/rocksdb_c_test-%" PRIu64 "-sst", GetTempDir(), GetTestId());
snprintf(sstfilename, sizeof(sstfilename), "%s/rocksdb_c_test-%d-sst",
GetTempDir(), ((int)geteuid()));
snprintf(dbpathname, sizeof(dbpathname),
"%s/rocksdb_c_test-%" PRIu64 "-dbpath", GetTempDir(), GetTestId());
snprintf(dbpathname, sizeof(dbpathname), "%s/rocksdb_c_test-%d-dbpath",
GetTempDir(), ((int)geteuid()));
StartPhase("create_objects");
cmp = rocksdb_comparator_create(NULL, CmpDestroy, CmpCompare, CmpName);
@@ -960,19 +951,6 @@ int main(int argc, char** argv) {
CheckNoError(err);
CheckGet(db, roptions, "foo", "hello");
StartPhase("set_db_options");
{
const char* keys[] = {"stats_dump_period_sec"};
const char* values[] = {"10"};
rocksdb_set_db_options(db, 1, keys, values, &err);
CheckNoError(err);
keys[0] = "not_a_db_option";
rocksdb_set_db_options(db, 1, keys, values, &err);
CheckCondition(err != NULL);
Free(&err);
}
StartPhase("backup_and_restore");
{
rocksdb_destroy_db(options, dbbackupname, &err);
@@ -1040,12 +1018,6 @@ int main(int argc, char** argv) {
Free(&before_db_id);
Free(&after_db_id);
rocksdb_backup_engine_stop_backup(be);
rocksdb_backup_engine_create_new_backup(be, db, &err);
CheckCondition(err != NULL);
free(err);
err = NULL;
rocksdb_backup_engine_close(be);
}
@@ -1175,11 +1147,9 @@ int main(int argc, char** argv) {
static char cf_export_path[200];
static char db_import_path[200];
snprintf(cf_export_path, sizeof(cf_export_path),
"%s/rocksdb_c_test-%" PRIu64 "-cf_export", GetTempDir(),
GetTestId());
"%s/rocksdb_c_test-%d-cf_export", GetTempDir(), ((int)geteuid()));
snprintf(db_import_path, sizeof(db_import_path),
"%s/rocksdb_c_test-%" PRIu64 "-db_import", GetTempDir(),
GetTestId());
"%s/rocksdb_c_test-%d-db_import", GetTempDir(), ((int)geteuid()));
rocksdb_options_t* db_options = rocksdb_options_create();
rocksdb_column_family_handle_t* cf_export =
@@ -2088,9 +2058,8 @@ int main(int argc, char** argv) {
// use dbpathname2 as the cf_path for "cf1"
rocksdb_dbpath_t* dbpath2;
char dbpathname2[200];
snprintf(dbpathname2, sizeof(dbpathname2),
"%s/rocksdb_c_test-%" PRIu64 "-dbpath2", GetTempDir(),
GetTestId());
snprintf(dbpathname2, sizeof(dbpathname2), "%s/rocksdb_c_test-%d-dbpath2",
GetTempDir(), ((int)geteuid()));
dbpath2 = rocksdb_dbpath_create(dbpathname2, 1024 * 1024);
const rocksdb_dbpath_t* cf_paths[1] = {dbpath2};
rocksdb_options_set_cf_paths(cf_options_2, cf_paths, 1);
@@ -2791,10 +2760,6 @@ int main(int argc, char** argv) {
CheckCondition(
1 == rocksdb_options_get_use_direct_io_for_flush_and_compaction(o));
rocksdb_options_set_use_direct_io_for_compaction_reads(o, 1);
CheckCondition(1 ==
rocksdb_options_get_use_direct_io_for_compaction_reads(o));
rocksdb_options_set_is_fd_close_on_exec(o, 1);
CheckCondition(1 == rocksdb_options_get_is_fd_close_on_exec(o));
@@ -3022,8 +2987,6 @@ int main(int argc, char** argv) {
CheckCondition(1 == rocksdb_options_get_use_direct_reads(copy));
CheckCondition(
1 == rocksdb_options_get_use_direct_io_for_flush_and_compaction(copy));
CheckCondition(
1 == rocksdb_options_get_use_direct_io_for_compaction_reads(copy));
CheckCondition(1 == rocksdb_options_get_is_fd_close_on_exec(copy));
CheckCondition(18 == rocksdb_options_get_stats_dump_period_sec(copy));
CheckCondition(5 == rocksdb_options_get_stats_persist_period_sec(copy));
@@ -3302,12 +3265,6 @@ int main(int argc, char** argv) {
CheckCondition(
1 == rocksdb_options_get_use_direct_io_for_flush_and_compaction(o));
rocksdb_options_set_use_direct_io_for_compaction_reads(copy, 0);
CheckCondition(
0 == rocksdb_options_get_use_direct_io_for_compaction_reads(copy));
CheckCondition(1 ==
rocksdb_options_get_use_direct_io_for_compaction_reads(o));
rocksdb_options_set_is_fd_close_on_exec(copy, 0);
CheckCondition(0 == rocksdb_options_get_is_fd_close_on_exec(copy));
CheckCondition(1 == rocksdb_options_get_is_fd_close_on_exec(o));
@@ -3740,52 +3697,6 @@ int main(int argc, char** argv) {
CheckCondition(37 ==
rocksdb_backup_engine_options_get_restore_rate_limit(bdo));
{
rocksdb_ratelimiter_t* limiter = rocksdb_ratelimiter_create_with_mode(
1024 * 1024, 100 * 1000, 10, 2, 0);
rocksdb_backup_engine_options_set_backup_rate_limiter(bdo, limiter);
rocksdb_backup_engine_options_set_restore_rate_limiter(bdo, limiter);
rocksdb_ratelimiter_destroy(limiter);
rocksdb_backup_engine_options_t* rate_beo =
rocksdb_backup_engine_options_create(dbbackupname);
rocksdb_ratelimiter_t* backup_limiter =
rocksdb_ratelimiter_create_with_mode(1024 * 1024, 100 * 1000, 10, 2,
0);
rocksdb_ratelimiter_t* restore_limiter =
rocksdb_ratelimiter_create_with_mode(1024 * 1024, 100 * 1000, 10, 2,
0);
rocksdb_backup_engine_options_set_backup_rate_limiter(rate_beo,
backup_limiter);
rocksdb_backup_engine_options_set_restore_rate_limiter(rate_beo,
restore_limiter);
rocksdb_ratelimiter_destroy(backup_limiter);
rocksdb_ratelimiter_destroy(restore_limiter);
rocksdb_env_t* rate_benv = rocksdb_create_default_env();
rocksdb_backup_engine_t* rate_be =
rocksdb_backup_engine_open_opts(rate_beo, rate_benv, &err);
rocksdb_backup_engine_options_destroy(rate_beo);
rocksdb_env_destroy(rate_benv);
CheckNoError(err);
rocksdb_backup_engine_create_new_backup(rate_be, db, &err);
CheckNoError(err);
{
char rate_restore_path[220];
snprintf(rate_restore_path, sizeof(rate_restore_path),
"%s.rate_restore", dbname);
rocksdb_restore_options_t* rate_restore_opts =
rocksdb_restore_options_create();
rocksdb_backup_engine_restore_db_from_latest_backup(
rate_be, rate_restore_path, rate_restore_path, rate_restore_opts,
&err);
CheckNoError(err);
rocksdb_restore_options_destroy(rate_restore_opts);
rocksdb_destroy_db(options, rate_restore_path, &err);
err = NULL;
}
rocksdb_backup_engine_close(rate_be);
}
rocksdb_backup_engine_options_set_max_background_operations(bdo, 20);
CheckCondition(
20 == rocksdb_backup_engine_options_get_max_background_operations(bdo));
@@ -3897,43 +3808,6 @@ int main(int argc, char** argv) {
}
}
StartPhase("transactiondb_set_write_policy_prepared");
{
char wp_dbname[200];
rocksdb_transactiondb_t* wp_txn_db;
rocksdb_transactiondb_options_t* wp_txn_opts;
snprintf(wp_dbname, sizeof(wp_dbname), "%s/rocksdb_c_test-txnwp-%" PRIu64,
GetTempDir(), GetTestId());
rocksdb_destroy_db(options, wp_dbname, &err);
Free(&err);
wp_txn_opts = rocksdb_transactiondb_options_create();
rocksdb_transactiondb_options_set_write_policy(
wp_txn_opts, rocksdb_txndb_write_policy_write_prepared);
rocksdb_options_set_create_if_missing(options, 1);
// create new TransactionDB with write policy WRITE_PREPARED
wp_txn_db =
rocksdb_transactiondb_open(options, wp_txn_opts, wp_dbname, &err);
CheckNoError(err);
{
rocksdb_transaction_options_t* wp_txn_options =
rocksdb_transaction_options_create();
rocksdb_transaction_t* wp_txn =
rocksdb_transaction_begin(wp_txn_db, woptions, wp_txn_options, NULL);
// write one record within a transaction
rocksdb_transaction_put(wp_txn, "k", 1, "v", 1, &err);
CheckNoError(err);
rocksdb_transaction_commit(wp_txn, &err);
CheckNoError(err);
rocksdb_transaction_destroy(wp_txn);
rocksdb_transaction_options_destroy(wp_txn_options);
}
CheckTxnDBGet(wp_txn_db, roptions, "k", "v");
rocksdb_transactiondb_close(wp_txn_db);
rocksdb_transactiondb_options_destroy(wp_txn_opts);
rocksdb_destroy_db(options, wp_dbname, &err);
CheckNoError(err);
}
StartPhase("transactions");
{
rocksdb_close(db);
@@ -4571,7 +4445,7 @@ int main(int argc, char** argv) {
rocksdb_options_set_max_open_files(opts, -1);
rocksdb_options_set_create_if_missing(opts, 1);
snprintf(secondary_path, sizeof(secondary_path),
"%s/rocksdb_c_test_secondary-%" PRIu64, GetTempDir(), GetTestId());
"%s/rocksdb_c_test_secondary-%d", GetTempDir(), ((int)geteuid()));
db1 = rocksdb_open_as_secondary(opts, dbname, secondary_path, &err);
CheckNoError(err);
@@ -5030,13 +4904,7 @@ int main(int argc, char** argv) {
rocksdb_options_set_create_if_missing(null_opts, 1);
rocksdb_options_set_compaction_service(null_opts, null_service);
char null_db[200];
int null_db_len = snprintf(null_db, sizeof(null_db),
"%s/rocksdb_c_test_null_service-%" PRIu64,
GetTempDir(), GetTestId());
if (null_db_len <= 0 || (size_t)null_db_len >= sizeof(null_db)) {
abort();
}
const char* null_db = "rocksdb_c_test_null_service";
rocksdb_t* null_db_handle = rocksdb_open(null_opts, null_db, &err);
CheckNoError(err);
+1 -19
View File
@@ -173,7 +173,6 @@ CompactionJob::CompactionJob(
fs_(db_options.fs, io_tracer),
file_options_for_read_(
fs_->OptimizeForCompactionTableRead(file_options, db_options_)),
file_options_for_compaction_input_read_(file_options_for_read_),
versions_(versions),
shutting_down_(shutting_down),
manual_compaction_canceled_(manual_compaction_canceled),
@@ -204,11 +203,6 @@ CompactionJob::CompactionJob(
assert(job_context);
assert(job_context->snapshot_context_initialized);
if (db_options_.use_direct_io_for_compaction_reads &&
!db_options_.use_direct_reads) {
file_options_for_compaction_input_read_.use_direct_reads = true;
}
const auto* cfd = compact_->compaction->column_family_data();
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
ThreadStatusUtil::SetColumnFamily(cfd);
@@ -1542,20 +1536,10 @@ InternalIterator* CompactionJob::CreateInputIterator(
// Although the v2 aggregator is what the level iterator(s) know about,
// the AddTombstones calls will be propagated down to the v1 aggregator.
const bool open_ephemeral_table_reader =
db_options_.use_direct_io_for_compaction_reads &&
!db_options_.use_direct_reads;
FileOptions& input_file_options =
open_ephemeral_table_reader ? file_options_for_compaction_input_read_
: file_options_for_read_;
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::CreateInputIterator:InputFileOptions",
&input_file_options);
iterators.raw_input =
std::unique_ptr<InternalIterator>(versions_->MakeInputIterator(
read_options, sub_compact->compaction, sub_compact->RangeDelAgg(),
input_file_options, boundaries.start, boundaries.end,
open_ephemeral_table_reader));
file_options_for_read_, boundaries.start, boundaries.end));
InternalIterator* input = iterators.raw_input.get();
if (boundaries.start.has_value() || boundaries.end.has_value()) {
@@ -2486,8 +2470,6 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
auto temperature =
sub_compact->compaction->GetOutputTemperature(outputs.IsProximalLevel());
fo_copy.temperature = temperature;
fo_copy.open_contract = FileOpenContract::kNoReopenForWrite |
FileOpenContract::kNoReadersWhileOpenForWrite;
fo_copy.write_hint = write_hint_;
Status s;
-2
View File
@@ -462,8 +462,6 @@ class CompactionJob {
FileSystemPtr fs_;
// env_option optimized for compaction table reads
FileOptions file_options_for_read_;
// file_options_for_read_ plus compaction-input-only overrides.
FileOptions file_options_for_compaction_input_read_;
VersionSet* versions_;
const std::atomic<bool>* shutting_down_;
const std::atomic<bool>& manual_compaction_canceled_;
-8
View File
@@ -431,14 +431,6 @@ Status CompactionServiceCompactionJob::Run() {
// Please note that input stats will be updated by primary host when all
// subcompactions are finished
UpdateCompactionJobOutputStatsFromInternalStats(status, internal_stats_);
// Whole-file filtering changes which keys are fed to the iterator, so the
// iterator-based input record count should not be treated as exact.
for (size_t level = 1; level < c->num_input_levels(); ++level) {
if (!c->filtered_input_levels(level).empty()) {
compaction_result_->stats.has_accurate_num_input_records = false;
break;
}
}
// and set fields that are not propagated as part of the update
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
-99
View File
@@ -611,101 +611,6 @@ TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(CompactionServiceTest,
StandaloneDeleteRangeTombstoneMakesInputCountInaccurate) {
Options options = CurrentOptions();
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
options.compaction_verify_record_count = true;
ReopenWithCompactionService(&options);
std::vector<std::string> files;
{
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file1 = dbname_ + "file1.sst";
ASSERT_OK(sst_file_writer.Open(file1));
ASSERT_OK(sst_file_writer.Put("a", "a1"));
ASSERT_OK(sst_file_writer.Put("b", "b1"));
ExternalSstFileInfo file1_info;
ASSERT_OK(sst_file_writer.Finish(&file1_info));
files.push_back(std::move(file1));
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.Put("x", "x1"));
ASSERT_OK(sst_file_writer.Put("y", "y1"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
}
IngestExternalFileOptions ifo;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("b"), "b1");
ASSERT_EQ(Get("x"), "x1");
ASSERT_EQ(Get("y"), "y1");
ASSERT_EQ(2, NumTableFilesAtLevel(6));
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
size_t num_files_after_filtered = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
[&](void* arg) {
num_files_after_filtered = *static_cast<size_t*>(arg);
});
SyncPoint::GetInstance()->EnableProcessing();
{
// The standalone range tombstone can fully cover the old versioned files,
// so universal compaction may filter those files before iteration.
files.clear();
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
std::string file3 = dbname_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("a", "a2"));
ASSERT_OK(sst_file_writer.Put("b", "b2"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
std::string file4 = dbname_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("x", "x2"));
ASSERT_OK(sst_file_writer.Put("y", "y2"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_FALSE(result.stats.has_accurate_num_input_records);
ASSERT_EQ(1, num_files_after_filtered);
ASSERT_EQ(Get("a"), "a2");
ASSERT_EQ(Get("b"), "b2");
ASSERT_EQ(Get("x"), "x2");
ASSERT_EQ(Get("y"), "y2");
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -1411,10 +1316,6 @@ TEST_F(CompactionServiceTest, TruncatedOutput) {
TEST_F(CompactionServiceTest, CustomFileChecksum) {
Options options = CurrentOptions();
// Pin compression so the auto-compacted LSM shape (and thus whether the
// manual CompactRange below schedules a remote compaction) doesn't depend on
// the default compression type. kNoCompression is always available.
options.compression = kNoCompression;
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
ReopenWithCompactionService(&options);
GenerateTestData();
+1 -1
View File
@@ -6331,7 +6331,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
Options options_disallow = options_allow;
options_disallow.disallow_memtable_writes = true;
options_disallow.paranoid_memory_checks = true;
options_disallow.memtable_verify_per_key_checksum_on_seek = true;
options_disallow.memtable_veirfy_per_key_checksum_on_seek = true;
DestroyAndReopen(options_allow);
// CFs allowing and disallowing memtable write
-3
View File
@@ -1482,13 +1482,10 @@ TEST_P(DBBlockCacheTypeTest, AddRedundantStats) {
// Access just data, forcing redundant load+insert
ReadOptions read_options;
std::unique_ptr<Iterator> iter{db_->NewIterator(read_options)};
ASSERT_OK(iter->Refresh());
cache->SetNthLookupNotFound(1);
iter->SeekToFirst();
ASSERT_TRUE(iter->Valid());
ASSERT_EQ(iter->key(), "bar");
ASSERT_EQ(iter->value(), "value");
EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_ADD));
EXPECT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_ADD));
-568
View File
@@ -8,13 +8,11 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <tuple>
#include <utility>
#include "compaction/compaction_picker_universal.h"
#include "db/blob/blob_index.h"
#include "db/db_test_util.h"
#include "db/dbformat.h"
#include "db/table_cache.h"
#include "env/mock_env.h"
#include "file/filename.h"
#include "port/port.h"
@@ -6653,572 +6651,6 @@ TEST_P(DBCompactionDirectIOTest, DirectIO) {
INSTANTIATE_TEST_CASE_P(DBCompactionDirectIOTest, DBCompactionDirectIOTest,
testing::Bool());
// With use_direct_io_for_compaction_reads OFF, compaction reads must stay
// buffered: neither the compaction-input FileOptions nor a kernel O_DIRECT
// open should fire. Runs on every platform (the sync points just don't fire
// where O_DIRECT isn't reachable). Pairs with
// UseDirectIoForCompactionReadsEndToEnd for the on case.
TEST_F(DBCompactionTest, UseDirectIoForCompactionReadsOffStaysBuffered) {
Options options = CurrentOptions();
Destroy(options);
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.use_direct_reads = false;
options.use_direct_io_for_compaction_reads = false;
options.use_direct_io_for_flush_and_compaction = false;
std::atomic<bool> observed_direct_compaction_read{false};
std::atomic<int> observed_callbacks{0};
std::atomic<int> observed_odirect_opens{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::CreateInputIterator:InputFileOptions", [&](void* arg) {
const auto* fo = static_cast<const FileOptions*>(arg);
observed_callbacks.fetch_add(1, std::memory_order_relaxed);
if (fo->use_direct_reads) {
observed_direct_compaction_read.store(true,
std::memory_order_relaxed);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewRandomAccessFile:O_DIRECT", [&](void* /*arg*/) {
observed_odirect_opens.fetch_add(1, std::memory_order_relaxed);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(TryReopen(options));
const std::string value(4096, 'v');
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_GT(observed_callbacks.load(), 0);
ASSERT_FALSE(observed_direct_compaction_read.load());
ASSERT_EQ(0, observed_odirect_opens.load());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
TEST_F(DBCompactionTest,
UseDirectIoForCompactionReadsUsesBoundedEphemeralReaders) {
auto fs = std::make_shared<MockFileSystem>(Env::Default()->GetSystemClock(),
/*supports_direct_io=*/true);
std::unique_ptr<Env> mock_env = NewCompositeEnv(fs);
Options options = CurrentOptions();
options.env = mock_env.get();
Destroy(options);
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.use_direct_reads = false;
options.use_direct_io_for_compaction_reads = true;
options.use_direct_io_for_flush_and_compaction = false;
options.max_subcompactions = 3;
options.statistics = CreateDBStatistics();
BlockBasedTableOptions table_options;
table_options.block_cache = NewLRUCache(64 << 20);
table_options.cache_index_and_filter_blocks = true;
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
std::atomic<int> fresh_reader_opens{0};
std::atomic<int> malformed_fresh_reader_options{0};
std::atomic<int> avoid_shared_metadata_cache_opens{0};
std::atomic<int> shared_metadata_cache_uses{0};
std::atomic<size_t> input_iterator_file_bound{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"TableCache::FindTable:FreshTableReader", [&](void* arg) {
const auto* open_options = static_cast<TableCacheOpenOptions*>(arg);
fresh_reader_opens.fetch_add(1, std::memory_order_relaxed);
if (!open_options->open_ephemeral_table_reader ||
!open_options->avoid_shared_metadata_cache ||
!open_options->skip_filters) {
malformed_fresh_reader_options.fetch_add(1,
std::memory_order_relaxed);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"BlockBasedTable::PrefetchIndexAndFilterBlocks:SharedMetadataCache",
[&](void* arg) {
const auto* context = static_cast<std::pair<bool, bool>*>(arg);
const bool avoid_shared_metadata_cache = context->first;
const bool use_cache = context->second;
if (avoid_shared_metadata_cache) {
avoid_shared_metadata_cache_opens.fetch_add(
1, std::memory_order_relaxed);
if (use_cache) {
shared_metadata_cache_uses.fetch_add(1, std::memory_order_relaxed);
}
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
[&](void* arg) {
input_iterator_file_bound.fetch_add(*static_cast<size_t*>(arg),
std::memory_order_relaxed);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(TryReopen(options));
const std::string value(4096, 'v');
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_GT(fresh_reader_opens.load(), 0);
EXPECT_EQ(0, malformed_fresh_reader_options.load());
ASSERT_GT(avoid_shared_metadata_cache_opens.load(), 0);
EXPECT_EQ(0, shared_metadata_cache_uses.load());
ASSERT_GT(input_iterator_file_bound.load(), 0);
EXPECT_LE(static_cast<size_t>(fresh_reader_opens.load()),
input_iterator_file_bound.load());
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
// End-to-end check that use_direct_io_for_compaction_reads opens compaction
// inputs with O_DIRECT while use_direct_reads stays off (user reads buffered).
// The NewRandomAccessFile:O_DIRECT sync point in env/fs_posix.cc fires once
// per fresh open with the O_DIRECT flag, so this proves the kernel path, not
// just the FileOptions. Only runs on platforms that take the O_DIRECT path.
#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) && !defined(OS_SOLARIS) && \
!defined(OS_WIN)
TEST_F(DBCompactionTest, UseDirectIoForCompactionReadsEndToEnd) {
if (!IsDirectIOSupported()) {
ROCKSDB_GTEST_BYPASS("Direct IO not supported");
return;
}
Options options = CurrentOptions();
Destroy(options);
options.create_if_missing = true;
options.disable_auto_compactions = true;
// User reads stay buffered, compaction reads should switch to O_DIRECT.
options.use_direct_reads = false;
options.use_direct_io_for_compaction_reads = true;
// Isolate the read-side change; leave the compaction write path buffered.
options.use_direct_io_for_flush_and_compaction = false;
// Sync-point callbacks fire on compaction threads while the test thread
// reads these counters, so use atomics to avoid a data race.
std::atomic<int> observed_run_starts{0};
std::atomic<int> observed_odirect_opens{0};
std::atomic<bool> observed_direct_compaction_read{false};
std::atomic<int> observed_callbacks{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({});
// Plumbing-level probe: the compaction-input FileOptions should carry
// use_direct_reads = true when the new flag is enabled.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::CreateInputIterator:InputFileOptions", [&](void* arg) {
const auto* fo = static_cast<const FileOptions*>(arg);
observed_callbacks.fetch_add(1, std::memory_order_relaxed);
if (fo != nullptr && fo->use_direct_reads) {
observed_direct_compaction_read.store(true,
std::memory_order_relaxed);
}
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Run():Start", [&](void* /*arg*/) {
observed_run_starts.fetch_add(1, std::memory_order_relaxed);
});
// Kernel-level probe: this fires only when open() is issued with O_DIRECT,
// proving we change the actual cache mode, not just the FileOptions struct.
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewRandomAccessFile:O_DIRECT", [&](void* /*arg*/) {
observed_odirect_opens.fetch_add(1, std::memory_order_relaxed);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Status s = TryReopen(options);
if (s.IsNotSupported() || s.IsInvalidArgument()) {
ROCKSDB_GTEST_BYPASS(
"Direct IO reads not supported in this test environment");
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
return;
}
ASSERT_OK(s);
// Produce two L0 files with OVERLAPPING key ranges so that CompactRange has
// actual merge work to do (otherwise RocksDB performs a trivial file move
// and never constructs a CompactionJob).
const std::string value(4096, 'v');
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
for (int i = 0; i < 64; ++i) {
ASSERT_OK(Put(Key(i), value));
}
ASSERT_OK(Flush());
// User reads should still go through the buffered path. Confirm that the
// option does not silently flip use_direct_reads for user reads.
for (int i = 0; i < 8; ++i) {
std::string actual;
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &actual));
ASSERT_EQ(value, actual);
}
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
// Wait for compaction to complete and CompactionJob to be constructed.
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Confirm the compaction actually ran; otherwise the missing sync-point hits
// would be a test-setup problem, not an option regression.
ASSERT_GT(observed_run_starts.load(), 0)
<< "CompactionJob::Run():Start never fired; CompactRange did not "
"schedule a compaction.";
ASSERT_GT(observed_callbacks.load(), 0);
ASSERT_TRUE(observed_direct_compaction_read.load());
// At least one compaction-input open went through O_DIRECT. Without the
// TableCache bypass this would be zero, since compaction would reuse the
// buffered handles cached for user reads.
EXPECT_GT(observed_odirect_opens.load(), 0)
<< "no compaction-input opens went through O_DIRECT; "
"observed_odirect_opens="
<< observed_odirect_opens.load();
// Quick sanity sweep after compaction to confirm data is intact.
for (int i = 0; i < 64; ++i) {
std::string actual;
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &actual));
ASSERT_EQ(value, actual);
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
// Exercise the LevelIterator bypass path (L1+ compactions) with range
// tombstones, where the ephemeral TableReader's lifetime is coupled to the
// range_tombstone_iter the file iterator returns. The end-to-end test above
// only makes two L0 files, which take the direct NewIterator path and never
// hit LevelIterator. Here we build L1/L2 with tombstones and compact L1->L2 so
// LevelIterator::NewFileIterator drives the bypass; a wrong reader/iterator
// lifetime would crash or trip sanitizers as LevelIterator switches files.
// Correctness is checked by computing the expected state of every key and
// asserting Get() matches: wave-2 puts beat wave-1, and each key's most-recent
// covering tombstone shadows older puts.
TEST_F(DBCompactionTest,
UseDirectIoForCompactionReadsLevelIteratorWithTombstones) {
if (!IsDirectIOSupported()) {
ROCKSDB_GTEST_BYPASS("Direct IO not supported");
return;
}
Options options = CurrentOptions();
Destroy(options);
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.use_direct_reads = false;
options.use_direct_io_for_compaction_reads = true;
options.use_direct_io_for_flush_and_compaction = false;
// Small files / small level base so we can pack data into L1 and L2 with
// a few flushes and CompactRange calls instead of needing millions of keys.
options.write_buffer_size = 64 * 1024;
options.target_file_size_base = 64 * 1024;
options.max_bytes_for_level_base = 256 * 1024;
options.level0_file_num_compaction_trigger = 100; // never auto-trigger
std::atomic<int> observed_odirect_opens{0};
std::atomic<int> observed_run_starts{0};
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewRandomAccessFile:O_DIRECT", [&](void* /*arg*/) {
observed_odirect_opens.fetch_add(1, std::memory_order_relaxed);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::Run():Start", [&](void* /*arg*/) {
observed_run_starts.fetch_add(1, std::memory_order_relaxed);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
Status s = TryReopen(options);
if (s.IsNotSupported() || s.IsInvalidArgument()) {
ROCKSDB_GTEST_BYPASS(
"Direct IO reads not supported in this test environment");
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
return;
}
ASSERT_OK(s);
// Use distinguishable values per wave so we can verify which wave's put
// won the merge for each key, not just "some put won".
const std::string wave1_value(1024, '1');
const std::string wave2_value(1024, '2');
auto write_batch = [&](int begin, int end, const std::string& value,
bool with_range_tombstone) {
for (int i = begin; i < end; ++i) {
ASSERT_OK(Put(Key(i), value));
}
if (with_range_tombstone) {
// Drop a slice in the middle of the just-written range. The
// DeleteRange follows the puts in this batch, so its sequence number
// is higher and it shadows the puts on [del_lo, del_hi) within this
// SST.
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
Key(begin + (end - begin) / 4),
Key(begin + 3 * (end - begin) / 4)));
}
ASSERT_OK(Flush());
};
// Wave 1: four flushed SSTs covering [0, 800), each with a tombstone
// covering the middle half of its own range. Then compact down so the
// next phase exercises LevelIterator over L1+ files.
constexpr int kWave1Begin = 0;
constexpr int kWave1End = 800;
constexpr int kWave1BatchSize = 200;
for (int batch_begin = kWave1Begin; batch_begin < kWave1End;
batch_begin += kWave1BatchSize) {
write_batch(batch_begin, batch_begin + kWave1BatchSize, wave1_value,
/*with_range_tombstone=*/true);
}
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Wave 2: two more flushed SSTs at L0 that overlap wave 1, each with their
// own range tombstone. The puts in wave 2 have higher seqno than wave 1,
// so they win when not tombstoned.
struct Wave2Range {
int begin;
int end;
};
const Wave2Range wave2_ranges[] = {{50, 250}, {350, 550}};
for (const auto& r : wave2_ranges) {
write_batch(r.begin, r.end, wave2_value, /*with_range_tombstone=*/true);
}
const int run_starts_before = observed_run_starts.load();
const int odirect_before = observed_odirect_opens.load();
// Compact everything together, forcing a LevelIterator over the lower-level
// files on the bypass path. Wrong ephemeral reader / tombstone-iter
// lifetimes should trip sanitizers here.
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
ASSERT_GT(observed_run_starts.load(), run_starts_before)
<< "expected at least one compaction to run during the L1+ phase";
EXPECT_GT(observed_odirect_opens.load(), odirect_before)
<< "no compaction-input opens went through O_DIRECT during L1+ "
"compaction; LevelIterator bypass path may be broken";
// Compute the precise expected state per key from the test parameters.
// For each k in the wave-1 range:
// - If k is inside a wave-2 batch: wave-2 wins. NotFound iff k is in
// that batch's tombstone range; otherwise present with wave2_value.
// - Else: wave 1 wins. NotFound iff k is in its batch's tombstone
// range; otherwise present with wave1_value.
enum class Expectation { kAbsent, kWave1Value, kWave2Value };
auto classify = [&](int k) -> Expectation {
// Wave 2 first (it shadows wave 1 wherever it covers).
for (const auto& r : wave2_ranges) {
if (k >= r.begin && k < r.end) {
const int width = r.end - r.begin;
const int del_lo = r.begin + width / 4;
const int del_hi = r.begin + 3 * width / 4;
if (k >= del_lo && k < del_hi) {
return Expectation::kAbsent;
}
return Expectation::kWave2Value;
}
}
// Fall back to wave 1.
const int batch = (k - kWave1Begin) / kWave1BatchSize;
const int batch_begin = kWave1Begin + batch * kWave1BatchSize;
const int del_lo = batch_begin + kWave1BatchSize / 4;
const int del_hi = batch_begin + 3 * kWave1BatchSize / 4;
if (k >= del_lo && k < del_hi) {
return Expectation::kAbsent;
}
return Expectation::kWave1Value;
};
int present_w1 = 0;
int present_w2 = 0;
int absent = 0;
std::string actual;
for (int k = kWave1Begin; k < kWave1End; ++k) {
const Status get_s = db_->Get(ReadOptions(), Key(k), &actual);
const Expectation exp = classify(k);
switch (exp) {
case Expectation::kAbsent:
EXPECT_TRUE(get_s.IsNotFound())
<< "key " << k << " expected NotFound (covered by tombstone); "
<< "got status=" << get_s.ToString()
<< " value_len=" << (get_s.ok() ? actual.size() : 0);
++absent;
break;
case Expectation::kWave1Value:
ASSERT_OK(get_s) << "key " << k << " expected wave-1 value";
EXPECT_EQ(wave1_value, actual)
<< "key " << k << " expected wave-1 value but got a different one";
++present_w1;
break;
case Expectation::kWave2Value:
ASSERT_OK(get_s) << "key " << k << " expected wave-2 value";
EXPECT_EQ(wave2_value, actual)
<< "key " << k << " expected wave-2 value but got a different one";
++present_w2;
break;
}
}
// All three buckets should be non-empty, so the test really exercises both
// tombstone paths and the wave-2-wins path. A zero here means the setup
// drifted and the setup (not these checks) should be fixed.
EXPECT_GT(present_w1, 0);
EXPECT_GT(present_w2, 0);
EXPECT_GT(absent, 0);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Destroy(options);
}
// With the option enabled, run reader threads against the live DB while manual
// compactions run in the background. Each compaction-input file is open through
// an ephemeral O_DIRECT handle while the shared TableCache still serves user
// reads through a buffered handle, so the same SST is open in two cache modes
// at once. This stresses that coexistence under TSAN/ASAN/UBSAN; it asserts
// reads stay consistent and final values match the last writes, not anything
// about timing.
TEST_F(DBCompactionTest, UseDirectIoForCompactionReadsConcurrentReadStress) {
if (!IsDirectIOSupported()) {
ROCKSDB_GTEST_BYPASS("Direct IO not supported");
return;
}
Options options = CurrentOptions();
Destroy(options);
options.create_if_missing = true;
options.disable_auto_compactions = true;
options.use_direct_reads = false;
options.use_direct_io_for_compaction_reads = true;
options.use_direct_io_for_flush_and_compaction = false;
options.write_buffer_size = 64 * 1024;
options.target_file_size_base = 64 * 1024;
options.max_bytes_for_level_base = 256 * 1024;
options.level0_file_num_compaction_trigger = 100; // never auto-trigger
Status s = TryReopen(options);
if (s.IsNotSupported() || s.IsInvalidArgument()) {
ROCKSDB_GTEST_BYPASS(
"Direct IO reads not supported in this test environment");
return;
}
ASSERT_OK(s);
constexpr int kNumKeys = 512;
constexpr int kValueSize = 256;
const std::string base_value(kValueSize, 'b');
// Initial population, flushed into a few L0 SSTs.
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), base_value));
if (i > 0 && i % 128 == 0) {
ASSERT_OK(Flush());
}
}
ASSERT_OK(Flush());
std::atomic<bool> stop{false};
std::atomic<int64_t> reads_observed{0};
std::atomic<int64_t> read_errors{0};
// Reader threads loop until stop is set, recording any status that isn't OK
// or NotFound. The point is to surface Corruption/IOError/use-after-free, so
// the main thread can fail the test.
constexpr int kNumReaders = 4;
std::vector<port::Thread> readers;
readers.reserve(kNumReaders);
for (int t = 0; t < kNumReaders; ++t) {
readers.emplace_back([&, t]() {
std::string value;
while (!stop.load(std::memory_order_acquire)) {
const int k =
(t * 7919 +
static_cast<int>(reads_observed.load(std::memory_order_relaxed))) %
kNumKeys;
Status read_s = db_->Get(ReadOptions(), Key(k), &value);
if (!read_s.ok() && !read_s.IsNotFound()) {
read_errors.fetch_add(1, std::memory_order_relaxed);
}
reads_observed.fetch_add(1, std::memory_order_relaxed);
}
});
}
// Drive a few rounds of writes and compactions on the main thread while
// the readers hammer Get(). Each round overwrites every key with a
// round-tagged value and then compacts.
constexpr int kRounds = 4;
std::string last_value = base_value;
for (int round = 0; round < kRounds; ++round) {
const std::string round_value(kValueSize,
static_cast<char>('A' + (round % 26)));
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(Put(Key(i), round_value));
if (i > 0 && i % 64 == 0) {
ASSERT_OK(Flush());
}
}
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_OK(dbfull()->TEST_WaitForCompact());
last_value = round_value;
}
stop.store(true, std::memory_order_release);
for (auto& reader : readers) {
reader.join();
}
// Every key must now hold the final round's value. Catches wholesale
// corruption the sampling readers might miss, and confirms compaction
// finished under the bypass path.
std::string value;
for (int i = 0; i < kNumKeys; ++i) {
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &value));
EXPECT_EQ(last_value, value);
}
EXPECT_GT(reads_observed.load(), 0)
<< "reader threads never observed a single read; test was a no-op";
EXPECT_EQ(0, read_errors.load())
<< "concurrent reads against compaction with bypass-path saw "
<< read_errors.load() << " non-OK/non-NotFound status returns";
Destroy(options);
}
#endif // !defined(OS_MACOSX) && !defined(OS_OPENBSD) && ...
class CompactionPriTest : public DBTestBase,
public testing::WithParamInterface<uint32_t> {
public:
+130 -434
View File
@@ -102,7 +102,6 @@
#include "table/get_context.h"
#include "table/merging_iterator.h"
#include "table/multiget_context.h"
#include "table/prepared_file_info.h"
#include "table/sst_file_dumper.h"
#include "table/table_builder.h"
#include "table/two_level_iterator.h"
@@ -835,10 +834,6 @@ Status DBImpl::CloseHelper() {
assert(!immutable_db_options_.open_files_async || !opened_successfully_ ||
bg_async_file_open_state_ != AsyncFileOpenState::kNotScheduled);
// No FileIngestionHandle from PrepareFileIngestion() may still be
// outstanding
assert(num_outstanding_prepared_ingestions_.load() == 0);
TEST_SYNC_POINT_CALLBACK("DBImpl::CloseHelper:PendingPurgeFinished",
&files_grabbed_for_purge_);
EraseThreadStatusDbInfo();
@@ -860,8 +855,6 @@ Status DBImpl::CloseHelper() {
if (default_cf_handle_ != nullptr || persist_stats_cf_handle_ != nullptr) {
// we need to delete handle outside of lock because it does its own locking
mutex_.Unlock();
TEST_SYNC_POINT("DBImpl::CloseHelper:CFHandleCleanupUnlocked");
TEST_SYNC_POINT("DBImpl::CloseHelper:CFHandleCleanupAllowed");
if (default_cf_handle_) {
delete default_cf_handle_;
default_cf_handle_ = nullptr;
@@ -929,6 +922,25 @@ Status DBImpl::CloseHelper() {
logs_.clear();
}
// Table cache may have table handles holding blocks from the block cache.
// We need to release them before the block cache is destroyed. The block
// cache may be destroyed inside versions_.reset(), when column family data
// list is destroyed, so leaving handles in table cache after
// versions_.reset() may cause issues. Here we clean all unreferenced handles
// in table cache, and (for certain builds/conditions) assert that no obsolete
// files are hanging around unreferenced (leak) in the table/blob file cache.
// Now we assume all user queries have finished, so only version set itself
// can possibly hold the blocks from block cache. After releasing unreferenced
// handles here, only handles held by version set left and inside
// versions_.reset(), we will release them. There, we need to make sure every
// time a handle is released, we erase it from the cache too. By doing that,
// we can guarantee that after versions_.reset(), table cache is empty
// so the cache can be safely destroyed.
#ifndef NDEBUG
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
#endif // !NDEBUG
table_cache_->EraseUnRefEntries();
for (auto& txn_entry : recovered_transactions_) {
delete txn_entry.second;
}
@@ -954,42 +966,6 @@ Status DBImpl::CloseHelper() {
}
}
// Drain obsolete-file purge work started AFTER the early CloseHelper wait
// near the top of this method. A late SuperVersion cleanup -- a dropped
// column family handle, or an in-flight iterator/Get whose ReadOptions or
// immutable_db_options_.avoid_unnecessary_blocking_io selected background
// purge -- can run in one of the mutex-unlocked windows above and enter the
// FindObsoleteFiles() -> PurgeObsoleteFiles(..., true) handoff. During that
// handoff pending_purge_obsolete_files_ is already nonzero, but
// bg_purge_scheduled_ may still be zero until SchedulePurge() runs at the end
// of PurgeObsoleteFiles(). Wait for both states while mutex_/this are still
// alive; bg_cv_.Wait() releases mutex_ so pending and scheduled purges can
// finish and signal.
TEST_SYNC_POINT("DBImpl::CloseHelper:BeforeFinalPurgeDrain");
while (pending_purge_obsolete_files_ || bg_purge_scheduled_) {
TEST_SYNC_POINT("DBImpl::CloseHelper:FinalPurgeDrainWait");
bg_cv_.Wait();
}
// Table cache may have table handles holding blocks from the block cache.
// We need to release them before the block cache is destroyed. The block
// cache may be destroyed inside versions_.reset(), when column family data
// list is destroyed, so leaving handles in table cache after
// versions_.reset() may cause issues. Here we clean all unreferenced handles
// in table cache, and (for certain builds/conditions) assert that no obsolete
// files are hanging around unreferenced (leak) in the table/blob file cache.
// Now we assume all user queries have finished, and close-time purge work has
// settled, so only version set itself can possibly hold the blocks from block
// cache. After releasing unreferenced handles here, only handles held by
// version set left and inside versions_.reset(), we will release them. There,
// we need to make sure every time a handle is released, we erase it from the
// cache too. By doing that, we can guarantee that after versions_.reset(),
// table cache is empty so the cache can be safely destroyed.
#ifndef NDEBUG
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
#endif // !NDEBUG
table_cache_->EraseUnRefEntries();
versions_.reset();
mutex_.Unlock();
if (db_lock_ != nullptr) {
@@ -1868,8 +1844,6 @@ Status DBImpl::SetDBOptions(
// TODO(xiez): clarify why apply optimize for read to write options
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
file_options_for_compaction_, immutable_db_options_);
TEST_SYNC_POINT_CALLBACK("DBImpl::SetDBOptions:FileOptionsForCompaction",
&file_options_for_compaction_);
if (wal_other_option_changed || wal_size_option_changed) {
WriteThread::Writer w;
write_thread_.EnterUnbatched(&w, &mutex_);
@@ -2501,8 +2475,30 @@ struct SuperVersionHandle {
static void CleanupSuperVersionHandle(void* arg1, void* /*arg2*/) {
SuperVersionHandle* sv_handle = static_cast<SuperVersionHandle*>(arg1);
sv_handle->db->CleanupIteratorSuperVersion(sv_handle->super_version,
sv_handle->background_purge);
if (sv_handle->super_version->Unref()) {
// Job id == 0 means that this is not our background process, but rather
// user thread
JobContext job_context(0);
sv_handle->mu->Lock();
sv_handle->super_version->Cleanup();
sv_handle->db->FindObsoleteFiles(&job_context, false, true);
if (sv_handle->background_purge) {
sv_handle->db->ScheduleBgLogWriterClose(&job_context);
sv_handle->db->AddSuperVersionsToFreeQueue(sv_handle->super_version);
sv_handle->db->SchedulePurge();
}
sv_handle->mu->Unlock();
if (!sv_handle->background_purge) {
delete sv_handle->super_version;
}
if (job_context.HaveSomethingToDelete()) {
sv_handle->db->PurgeObsoleteFiles(job_context,
sv_handle->background_purge);
}
job_context.Clean();
}
delete sv_handle;
}
@@ -2524,8 +2520,7 @@ static void CleanupGetMergeOperandsState(void* arg1, void* /*arg2*/) {
InternalIterator* DBImpl::NewInternalIterator(
const ReadOptions& read_options, ColumnFamilyData* cfd,
SuperVersion* super_version, Arena* arena, SequenceNumber sequence,
bool allow_unprepared_value, ArenaWrappedDBIter* db_iter,
const MultiScanArgs* scan_opts) {
bool allow_unprepared_value, ArenaWrappedDBIter* db_iter) {
InternalIterator* internal_iter;
assert(arena != nullptr);
auto prefix_extractor =
@@ -2537,62 +2532,47 @@ InternalIterator* DBImpl::NewInternalIterator(
// here, and no unit test cares about the value provided here.
!read_options.total_order_seek && prefix_extractor != nullptr,
read_options.iterate_upper_bound);
// Collect iterator for mutable memtable
auto mem_iter = super_version->mem->NewIterator(
read_options, super_version->GetSeqnoToTimeMapping(), arena,
super_version->mutable_cf_options.prefix_extractor.get(),
/*for_flush=*/false);
Status s;
const Comparator* user_comparator = cfd->user_comparator();
const bool mem_intersects =
!super_version->mem->IsEmpty() &&
MultiScanIntersectsMemTable(super_version->mem, read_options,
super_version->GetSeqnoToTimeMapping(),
prefix_extractor, scan_opts, user_comparator);
if (scan_opts == nullptr || mem_intersects) {
// Collect iterator for mutable memtable
auto mem_iter = super_version->mem->NewIterator(
read_options, super_version->GetSeqnoToTimeMapping(), arena,
super_version->mutable_cf_options.prefix_extractor.get(),
/*for_flush=*/false);
if (!read_options.ignore_range_deletions) {
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
super_version->mem->NewRangeTombstoneIterator(
read_options, sequence, false /* immutable_memtable */));
if (range_del_iter != nullptr && !range_del_iter->empty()) {
mem_tombstone_iter = std::make_unique<TruncatedRangeDelIterator>(
std::move(range_del_iter), &cfd->ioptions().internal_comparator,
nullptr /* smallest */, nullptr /* largest */);
}
merge_iter_builder.AddPointAndTombstoneIterator(
mem_iter, std::move(mem_tombstone_iter));
if (!read_options.ignore_range_deletions) {
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
auto range_del_iter = super_version->mem->NewRangeTombstoneIterator(
read_options, sequence, false /* immutable_memtable */);
if (range_del_iter == nullptr || range_del_iter->empty()) {
delete range_del_iter;
} else {
merge_iter_builder.AddIterator(mem_iter);
mem_tombstone_iter = std::make_unique<TruncatedRangeDelIterator>(
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
&cfd->ioptions().internal_comparator, nullptr /* smallest */,
nullptr /* largest */);
}
} else if (scan_opts != nullptr) {
merge_iter_builder.SetMemtablePruned(true);
merge_iter_builder.AddPointAndTombstoneIterator(
mem_iter, std::move(mem_tombstone_iter));
} else {
merge_iter_builder.AddIterator(mem_iter);
}
if (s.ok() &&
(scan_opts == nullptr || super_version->imm->GetTotalNumEntries() > 0)) {
// Collect all needed child iterators for immutable memtables. When scan
// ranges are provided, AddIterators prunes each immutable memtable
// individually.
// Collect all needed child iterators for immutable memtables
if (s.ok()) {
super_version->imm->AddIterators(
read_options, super_version->GetSeqnoToTimeMapping(),
super_version->mutable_cf_options.prefix_extractor.get(),
&merge_iter_builder, !read_options.ignore_range_deletions, sequence,
scan_opts, user_comparator);
&merge_iter_builder, !read_options.ignore_range_deletions);
}
TEST_SYNC_POINT_CALLBACK("DBImpl::NewInternalIterator:StatusCallback", &s);
if (s.ok()) {
// Collect iterators for files in L0 - Ln
if (read_options.read_tier != kMemtableTier) {
super_version->current->AddIterators(
read_options, file_options_, &merge_iter_builder,
allow_unprepared_value, sequence, scan_opts);
super_version->current->AddIterators(read_options, file_options_,
&merge_iter_builder,
allow_unprepared_value);
}
internal_iter = merge_iter_builder.Finish(
read_options.ignore_range_deletions ? nullptr : db_iter);
if (internal_iter == nullptr) {
internal_iter = NewEmptyInternalIterator<Slice>(arena);
}
SuperVersionHandle* cleanup = new SuperVersionHandle(
this, &mutex_, super_version,
read_options.background_purge_on_iterator_cleanup ||
@@ -2606,35 +2586,6 @@ InternalIterator* DBImpl::NewInternalIterator(
return NewErrorInternalIterator<Slice>(s, arena);
}
void DBImpl::CleanupIteratorSuperVersion(SuperVersion* super_version,
bool background_purge) {
background_purge =
background_purge || immutable_db_options_.avoid_unnecessary_blocking_io;
if (super_version->Unref()) {
// Job id == 0 means that this is not our background process, but rather
// user thread
JobContext job_context(0);
mutex_.Lock();
super_version->Cleanup();
FindObsoleteFiles(&job_context, false, true);
if (background_purge) {
ScheduleBgLogWriterClose(&job_context);
AddSuperVersionsToFreeQueue(super_version);
SchedulePurge();
}
mutex_.Unlock();
if (!background_purge) {
delete super_version;
}
if (job_context.HaveSomethingToDelete()) {
PurgeObsoleteFiles(job_context, background_purge);
}
job_context.Clean();
}
}
ColumnFamilyHandle* DBImpl::DefaultColumnFamily() const {
return default_cf_handle_;
}
@@ -6002,79 +5953,6 @@ Status DBImpl::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
return versions_->GetLiveFilesChecksumInfo(checksum_list);
}
Status DBImpl::GetPreparedFileInfoForExternalSstIngestion(
const std::string& file_path,
std::shared_ptr<const PreparedFileInfo>* file_info) {
if (file_info == nullptr) {
return Status::InvalidArgument("file_info must not be null");
}
file_info->reset();
const size_t file_name_pos = file_path.find_last_of("/\\");
const std::string file_name = file_name_pos == std::string::npos
? file_path
: file_path.substr(file_name_pos + 1);
uint64_t file_number = 0;
FileType file_type;
if (!ParseFileName(file_name, &file_number, &file_type) ||
file_type != kTableFile || file_number == 0) {
return Status::InvalidArgument("Invalid table file name: " + file_path);
}
int file_level = -1;
FileMetaData* file_meta = nullptr;
ColumnFamilyData* file_cfd = nullptr;
Version* file_version = nullptr;
std::shared_ptr<const TableProperties> table_properties;
ReadOptions read_options;
{
InstrumentedMutexLock l(&mutex_);
Status s = versions_->GetMetadataForFile(file_number, &file_level,
&file_meta, &file_cfd);
if (!s.ok()) {
return s;
}
const std::string expected_file_path = TableFileName(
file_cfd->ioptions().cf_paths, file_number, file_meta->fd.GetPathId());
if (file_path != expected_file_path) {
return Status::InvalidArgument("Path does not match live table file: " +
file_path);
}
file_cfd->Ref();
file_version = file_cfd->current();
file_version->Ref();
}
const Defer cleanup_refs([&]() {
InstrumentedMutexLock l(&mutex_);
file_version->Unref();
file_cfd->UnrefAndTryDelete();
});
Status s = file_cfd->table_cache()->GetTableProperties(
file_options_, read_options, file_cfd->internal_comparator(), *file_meta,
&table_properties, file_version->GetMutableCFOptions(),
false /* no_io */);
if (!s.ok()) {
return s;
}
assert(table_properties != nullptr);
auto prepared_file_info = std::make_shared<PreparedFileInfo>();
prepared_file_info->file_size = file_meta->fd.GetFileSize();
prepared_file_info->smallest = file_meta->smallest;
prepared_file_info->largest = file_meta->largest;
prepared_file_info->table_properties = *table_properties;
prepared_file_info->table_properties.key_largest_seqno =
file_meta->fd.largest_seqno;
prepared_file_info->table_properties.key_smallest_seqno =
file_meta->fd.smallest_seqno;
*file_info = std::move(prepared_file_info);
return s;
}
void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
ColumnFamilyMetaData* cf_meta) {
assert(column_family);
@@ -6797,76 +6675,11 @@ Status DBImpl::IngestExternalFile(
return IngestExternalFiles({arg});
}
class FileIngestionHandleImpl : public FileIngestionHandle {
public:
explicit FileIngestionHandleImpl(DBImpl* db) : db_(db) {}
~FileIngestionHandleImpl() override;
FileIngestionHandleImpl(const FileIngestionHandleImpl&) = delete;
FileIngestionHandleImpl& operator=(const FileIngestionHandleImpl&) = delete;
FileIngestionHandleImpl(FileIngestionHandleImpl&&) = delete;
FileIngestionHandleImpl& operator=(FileIngestionHandleImpl&&) = delete;
Status Abort() override;
DBImpl* const db_;
std::vector<ExternalSstFileIngestionJob> jobs_;
std::unique_ptr<std::list<uint64_t>::iterator> pending_output_elem_;
bool fill_cache_ = true;
// Set true once committed or aborted, so the destructor does not roll back.
bool consumed_ = false;
};
FileIngestionHandleImpl::~FileIngestionHandleImpl() {
if (consumed_ || db_ == nullptr) {
return;
}
// Dropped without commit or abort: roll back as a safety net.
db_->RollbackPreparedFileIngestion(this);
ROCKS_LOG_WARN(
db_->immutable_db_options_.info_log,
"[%zu CF(s)] File ingestion handle destroyed without commit or "
"abort; prepared files were rolled back.",
jobs_.size());
}
void DBImpl::RollbackPreparedFileIngestion(FileIngestionHandleImpl* const h) {
// Delete the staged internal files and release the reserved file numbers /
// pending-output protection, leaving the DB unchanged.
const Status rollback_status = Status::Incomplete("file ingestion aborted");
for (auto& job : h->jobs_) {
job.Cleanup(rollback_status);
}
{
InstrumentedMutexLock l(&mutex_);
ReleaseFileNumberFromPendingOutputs(h->pending_output_elem_);
}
h->consumed_ = true;
num_outstanding_prepared_ingestions_.fetch_sub(1);
}
Status FileIngestionHandleImpl::Abort() {
if (consumed_) {
return Status::InvalidArgument(
"file ingestion handle has already been committed or aborted");
}
db_->RollbackPreparedFileIngestion(this);
return Status::OK();
}
Status DBImpl::PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& args,
std::unique_ptr<FileIngestionHandle>* handle) {
if (handle == nullptr) {
return Status::InvalidArgument("file ingestion handle output is null");
}
handle->reset();
// Recorded as INGEST_EXTERNAL_FILE_PREPARE_TIME on success below.
const bool record_ingest_micros =
stats_ != nullptr &&
stats_->get_stats_level() > StatsLevel::kExceptTimers;
const uint64_t prepare_start_micros =
record_ingest_micros ? immutable_db_options_.clock->NowMicros() : 0;
Status DBImpl::IngestExternalFiles(
const std::vector<IngestExternalFileArg>& args) {
PERF_TIMER_GUARD(file_ingestion_nanos);
// TODO: plumb Env::IOActivity, Env::IOPriority
const WriteOptions write_options;
if (args.empty()) {
return Status::InvalidArgument("ingestion arg list is empty");
@@ -6891,17 +6704,6 @@ Status DBImpl::PrepareFileIngestion(
"external_files[" + std::to_string(i) + "] is empty";
return Status::InvalidArgument(err_msg);
}
if (!args[i].file_infos.empty()) {
if (args[i].file_infos.size() != args[i].external_files.size()) {
return Status::InvalidArgument("file_infos[" + std::to_string(i) +
"] size must match external_files[" +
std::to_string(i) + "] size");
}
if (args[i].options.write_global_seqno) {
return Status::InvalidArgument(
"write_global_seqno is not supported when file_infos is set");
}
}
if (i && args[i].options.fill_cache != args[i - 1].options.fill_cache) {
return Status::InvalidArgument(
"fill_cache should be the same across ingestion options.");
@@ -6978,7 +6780,6 @@ Status DBImpl::PrepareFileIngestion(
}
std::vector<ExternalSstFileIngestionJob> ingestion_jobs;
ingestion_jobs.reserve(num_cfs);
for (const auto& arg : args) {
auto* cfd = static_cast<ColumnFamilyHandleImpl*>(arg.column_family)->cfd();
ingestion_jobs.emplace_back(versions_.get(), cfd, immutable_db_options_,
@@ -6996,9 +6797,8 @@ Status DBImpl::PrepareFileIngestion(
this);
Status es = ingestion_jobs[i].Prepare(
args[i].external_files, args[i].files_checksums,
args[i].files_checksum_func_names, args[i].file_infos,
args[i].atomic_replace_range, args[i].file_temperature,
start_file_number, super_version);
args[i].files_checksum_func_names, args[i].atomic_replace_range,
args[i].file_temperature, start_file_number, super_version);
// capture first error only
if (!es.ok() && status.ok()) {
status = es;
@@ -7013,9 +6813,8 @@ Status DBImpl::PrepareFileIngestion(
this);
Status es = ingestion_jobs[0].Prepare(
args[0].external_files, args[0].files_checksums,
args[0].files_checksum_func_names, args[0].file_infos,
args[0].atomic_replace_range, args[0].file_temperature,
next_file_number, super_version);
args[0].files_checksum_func_names, args[0].atomic_replace_range,
args[0].file_temperature, next_file_number, super_version);
if (!es.ok()) {
status = es;
}
@@ -7030,86 +6829,8 @@ Status DBImpl::PrepareFileIngestion(
return status;
}
auto handle_impl = std::make_unique<FileIngestionHandleImpl>(this);
handle_impl->jobs_ = std::move(ingestion_jobs);
handle_impl->pending_output_elem_ = std::move(pending_output_elem);
handle_impl->fill_cache_ = args[0].options.fill_cache;
if (record_ingest_micros) {
RecordTimeToHistogram(
stats_, INGEST_EXTERNAL_FILE_PREPARE_TIME,
immutable_db_options_.clock->NowMicros() - prepare_start_micros);
}
num_outstanding_prepared_ingestions_.fetch_add(1);
*handle = std::move(handle_impl);
return Status::OK();
}
Status DBImpl::CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> handles) {
if (handles.empty()) {
return Status::InvalidArgument("no file ingestion handles to commit");
}
// Validate the handles and group their jobs by column family, merging same-CF
// jobs into one so each column family commits via a single atomic version
// edit and one SuperVersion install.
Status status;
std::vector<FileIngestionHandleImpl*> hs;
hs.reserve(handles.size());
std::vector<ExternalSstFileIngestionJob*> ingestion_jobs;
const bool fill_cache =
static_cast<FileIngestionHandleImpl*>(handles[0].get())->fill_cache_;
int max_file_opening_threads = 1;
{
UnorderedMap<ColumnFamilyData*, ExternalSstFileIngestionJob*>
primary_job_for_cfd;
for (const auto& handle : handles) {
assert(handle);
auto* h = static_cast<FileIngestionHandleImpl*>(handle.get());
assert(h->db_ == this);
if (h->consumed_) {
status = Status::InvalidArgument(
"file ingestion handle has already been committed or aborted");
}
if (h->fill_cache_ != fill_cache) {
status = Status::InvalidArgument(
"fill cache arg must be consistent across all handles");
}
if (!status.ok()) {
return status;
}
hs.push_back(h);
for (auto& job : h->jobs_) {
max_file_opening_threads =
std::max(max_file_opening_threads, job.file_opening_threads());
auto [it, inserted] =
primary_job_for_cfd.try_emplace(job.GetColumnFamilyData(), &job);
if (inserted) {
ingestion_jobs.push_back(&job);
} else {
status = it->second->MergeForSameColumnFamily(&job);
if (!status.ok()) {
return status;
}
}
}
}
}
const size_t num_jobs = ingestion_jobs.size();
// TODO: plumb Env::IOActivity, Env::IOPriority
const WriteOptions write_options;
// Decided here (not in Prepare) so the histograms reflect the stats level at
// commit time; recorded only on success below.
const bool record_ingest_micros =
stats_ != nullptr &&
stats_->get_stats_level() > StatsLevel::kExceptTimers;
const uint64_t run_start_micros =
record_ingest_micros ? immutable_db_options_.clock->NowMicros() : 0;
std::vector<SuperVersionContext> sv_ctxs;
for (size_t i = 0; i != num_jobs; ++i) {
for (size_t i = 0; i != num_cfs; ++i) {
sv_ctxs.emplace_back(true /* create_superversion */);
}
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeJobsRun:0");
@@ -7120,9 +6841,9 @@ Status DBImpl::CommitFileIngestionHandles(
// mutex so the lock-acquisition order is ingest_sst_lock -> DB mutex
// throughout. Use readlock so we still allow concurrent ingestions.
std::vector<std::unique_ptr<ReadLock>> ingest_read_locks;
ingest_read_locks.reserve(num_jobs);
for (auto* job : ingestion_jobs) {
auto* cfd = job->GetColumnFamilyData();
ingest_read_locks.reserve(num_cfs);
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
if (!cfd->IsDropped()) {
ingest_read_locks.emplace_back(
std::make_unique<ReadLock>(&cfd->GetIngestSstLock()));
@@ -7148,14 +6869,14 @@ Status DBImpl::CommitFileIngestionHandles(
// So wait here to ensure there is no pending write to memtable.
WaitForPendingWrites();
num_running_ingest_file_ += static_cast<int>(num_jobs);
num_running_ingest_file_ += static_cast<int>(num_cfs);
TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter");
TEST_SYNC_POINT("DBImpl::IngestExternalFile:AfterIncIngestFileCounter:2");
bool at_least_one_cf_need_flush = false;
std::vector<bool> need_flush(num_jobs, false);
for (size_t i = 0; i != num_jobs; ++i) {
auto* cfd = ingestion_jobs[i]->GetColumnFamilyData();
std::vector<bool> need_flush(num_cfs, false);
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
if (cfd->IsDropped()) {
// TODO (yanqin) investigate whether we should abort ingestion or
// proceed with other non-dropped column families.
@@ -7164,7 +6885,7 @@ Status DBImpl::CommitFileIngestionHandles(
break;
}
bool tmp = false;
status = ingestion_jobs[i]->NeedsFlush(&tmp, cfd->GetSuperVersion());
status = ingestion_jobs[i].NeedsFlush(&tmp, cfd->GetSuperVersion());
need_flush[i] = tmp;
at_least_one_cf_need_flush = (at_least_one_cf_need_flush || tmp);
if (!status.ok()) {
@@ -7184,11 +6905,11 @@ Status DBImpl::CommitFileIngestionHandles(
{} /* provided_candidate_cfds */, true /* entered_write_thread */);
mutex_.Lock();
} else {
for (size_t i = 0; i != num_jobs; ++i) {
for (size_t i = 0; i != num_cfs; ++i) {
if (need_flush[i]) {
mutex_.Unlock();
status =
FlushMemTable(ingestion_jobs[i]->GetColumnFamilyData(),
FlushMemTable(ingestion_jobs[i].GetColumnFamilyData(),
flush_opts, FlushReason::kExternalFileIngestion,
true /* entered_write_thread */);
mutex_.Lock();
@@ -7199,22 +6920,22 @@ Status DBImpl::CommitFileIngestionHandles(
}
}
if (status.ok()) {
for (size_t i = 0; i != num_jobs; ++i) {
for (size_t i = 0; i != num_cfs; ++i) {
if (immutable_db_options_.atomic_flush || need_flush[i]) {
ingestion_jobs[i]->SetFlushedBeforeRun();
ingestion_jobs[i].SetFlushedBeforeRun();
}
}
}
}
// Run ingestion jobs.
if (status.ok()) {
for (size_t i = 0; i != num_jobs; ++i) {
for (size_t i = 0; i != num_cfs; ++i) {
mutex_.AssertHeld();
status = ingestion_jobs[i]->Run();
status = ingestion_jobs[i].Run();
if (!status.ok()) {
break;
}
ingestion_jobs[i]->RegisterRange();
ingestion_jobs[i].RegisterRange();
}
}
// Now that Run() has assigned the actual seqno for each ingested file,
@@ -7226,9 +6947,9 @@ Status DBImpl::CommitFileIngestionHandles(
// next conversion observes the new barrier and refuses any insert
// with insert_seq < assigned.
if (status.ok()) {
for (auto* job : ingestion_jobs) {
auto* cfd = job->GetColumnFamilyData();
SequenceNumber assigned = job->MaxAssignedSequenceNumber();
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
SequenceNumber assigned = ingestion_jobs[i].MaxAssignedSequenceNumber();
if (assigned > 0) {
cfd->mem()->BumpIngestSeqnoBarrier(assigned);
}
@@ -7236,16 +6957,16 @@ Status DBImpl::CommitFileIngestionHandles(
}
if (status.ok()) {
ReadOptions read_options;
read_options.fill_cache = fill_cache;
read_options.fill_cache = args[0].options.fill_cache;
autovector<ColumnFamilyData*> cfds_to_commit;
autovector<autovector<VersionEdit*>> edit_lists;
uint32_t num_entries = 0;
for (auto* job : ingestion_jobs) {
auto* cfd = job->GetColumnFamilyData();
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
assert(!cfd->IsDropped());
cfds_to_commit.push_back(cfd);
autovector<VersionEdit*> edit_list;
auto* edit = job->edit();
auto* edit = ingestion_jobs[i].edit();
edit->MarkForegroundOperation();
edit_list.push_back(edit);
edit_lists.push_back(edit_list);
@@ -7260,11 +6981,9 @@ Status DBImpl::CommitFileIngestionHandles(
}
assert(0 == num_entries);
}
status = versions_->LogAndApply(
cfds_to_commit, read_options, write_options, edit_lists, &mutex_,
directories_.GetDbDir(), false /* new_descriptor_log */,
nullptr /* new_cf_options */, {} /* manifest_wcbs */, {} /* pre_cb */,
max_file_opening_threads);
status =
versions_->LogAndApply(cfds_to_commit, read_options, write_options,
edit_lists, &mutex_, directories_.GetDbDir());
// It is safe to update VersionSet last seqno here after LogAndApply since
// LogAndApply persists last sequence number from VersionEdits,
// which are from file's largest seqno and not from VersionSet.
@@ -7273,10 +6992,11 @@ Status DBImpl::CommitFileIngestionHandles(
// mutex when persisting MANIFEST file, and the snapshots taken during
// that period will not be stable if VersionSet last seqno is updated
// before LogAndApply.
SequenceNumber max_assigned_seqno = 0;
for (auto* job : ingestion_jobs) {
max_assigned_seqno =
std::max(max_assigned_seqno, job->MaxAssignedSequenceNumber());
SequenceNumber max_assigned_seqno =
ingestion_jobs[0].MaxAssignedSequenceNumber();
for (size_t i = 1; i != num_cfs; ++i) {
max_assigned_seqno = std::max(
max_assigned_seqno, ingestion_jobs[i].MaxAssignedSequenceNumber());
}
if (max_assigned_seqno > 0) {
const SequenceNumber last_seqno = versions_->LastSequence();
@@ -7288,17 +7008,17 @@ Status DBImpl::CommitFileIngestionHandles(
}
}
for (auto* job : ingestion_jobs) {
job->UnregisterRange();
for (auto& job : ingestion_jobs) {
job.UnregisterRange();
}
if (status.ok()) {
for (size_t i = 0; i != num_jobs; ++i) {
auto* cfd = ingestion_jobs[i]->GetColumnFamilyData();
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
assert(!cfd->IsDropped());
InstallSuperVersionAndScheduleWork(cfd, &sv_ctxs[i]);
#ifndef NDEBUG
if (0 == i && num_jobs > 1) {
if (0 == i && num_cfs > 1) {
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:InstallSVForFirstCF:0");
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:InstallSVForFirstCF:1");
}
@@ -7323,14 +7043,12 @@ Status DBImpl::CommitFileIngestionHandles(
PERF_TIMER_STOP(file_ingestion_blocking_live_writes_nanos);
if (status.ok()) {
for (auto* job : ingestion_jobs) {
job->UpdateStats();
}
for (auto* h : hs) {
ReleaseFileNumberFromPendingOutputs(h->pending_output_elem_);
for (auto& job : ingestion_jobs) {
job.UpdateStats();
}
}
num_running_ingest_file_ -= static_cast<int>(num_jobs);
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
num_running_ingest_file_ -= static_cast<int>(num_cfs);
if (0 == num_running_ingest_file_) {
bg_cv_.SignalAll();
}
@@ -7338,46 +7056,24 @@ Status DBImpl::CommitFileIngestionHandles(
}
// mutex_ is unlocked here
for (auto& sv_ctx : sv_ctxs) {
sv_ctx.Clean();
// Cleanup
for (size_t i = 0; i != num_cfs; ++i) {
sv_ctxs[i].Clean();
// This may rollback jobs that have completed successfully. This is
// intended for atomicity.
ingestion_jobs[i].Cleanup(status);
}
if (!status.ok()) {
// The atomic commit failed; nothing was made visible. The handles are NOT
// consumed, so each one's destructor rolls it back
return status;
}
for (auto* job : ingestion_jobs) {
job->Cleanup(status);
auto* cfd = job->GetColumnFamilyData();
if (!cfd->IsDropped()) {
NotifyOnExternalFileIngested(cfd, *job);
if (status.ok()) {
for (size_t i = 0; i != num_cfs; ++i) {
auto* cfd = ingestion_jobs[i].GetColumnFamilyData();
if (!cfd->IsDropped()) {
NotifyOnExternalFileIngested(cfd, ingestion_jobs[i]);
}
}
}
for (auto* h : hs) {
h->consumed_ = true;
num_outstanding_prepared_ingestions_.fetch_sub(1);
}
// Record commit latency.
if (record_ingest_micros) {
RecordTimeToHistogram(
stats_, INGEST_EXTERNAL_FILE_RUN_TIME,
immutable_db_options_.clock->NowMicros() - run_start_micros);
}
return status;
}
Status DBImpl::IngestExternalFiles(
const std::vector<IngestExternalFileArg>& args) {
PERF_TIMER_GUARD(file_ingestion_nanos);
std::unique_ptr<FileIngestionHandle> handle;
Status status = PrepareFileIngestion(args, &handle);
if (!status.ok()) {
return status;
}
return CommitFileIngestionHandle(std::move(handle));
}
Status DBImpl::CreateColumnFamilyWithImport(
const ColumnFamilyOptions& options, const std::string& column_family_name,
const ImportColumnFamilyOptions& import_options,
+6 -38
View File
@@ -76,7 +76,6 @@ namespace ROCKSDB_NAMESPACE {
class Arena;
class ArenaWrappedDBIter;
class FileIngestionHandleImpl;
class InMemoryStatsHistoryIterator;
class MemTable;
class PersistentStatsHistoryIterator;
@@ -576,10 +575,6 @@ class DBImpl : public DB {
const LiveFilesStorageInfoOptions& opts,
std::vector<LiveFileStorageInfo>* files) override;
Status GetPreparedFileInfoForExternalSstIngestion(
const std::string& file_path,
std::shared_ptr<const PreparedFileInfo>* file_info) override;
// Obtains the meta data of the specified column family of the DB.
// TODO(yhchiang): output parameter is placed in the end in this codebase.
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
@@ -609,14 +604,6 @@ class DBImpl : public DB {
Status IngestExternalFiles(
const std::vector<IngestExternalFileArg>& args) override;
using DB::PrepareFileIngestion;
Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& args,
std::unique_ptr<FileIngestionHandle>* handle) override;
Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> handles) override;
using DB::CreateColumnFamilyWithImport;
Status CreateColumnFamilyWithImport(
const ColumnFamilyOptions& options, const std::string& column_family_name,
@@ -870,22 +857,12 @@ class DBImpl : public DB {
// memtable range tombstone iterator used by the underlying merging iterator.
// This range tombstone iterator can be refreshed later by db_iter.
// @param read_options Must outlive the returned iterator.
// @param sequence The snapshot sequence captured when the DB iterator was
// created. Child iterators must use this instead of dereferencing
// read_options.snapshot, which may be released before lazy initialization.
// @param scan_opts Optional bounded scan ranges used only to prune the
// iterator tree during lazy Prepare() initialization.
InternalIterator* NewInternalIterator(
const ReadOptions& read_options, ColumnFamilyData* cfd,
SuperVersion* super_version, Arena* arena, SequenceNumber sequence,
bool allow_unprepared_value, ArenaWrappedDBIter* db_iter = nullptr,
const MultiScanArgs* scan_opts = nullptr);
// Release a SuperVersion held by an iterator. This preserves the cleanup
// behavior used by materialized internal iterators even when the DB iterator
// never needed to lazily build its child iterator tree.
void CleanupIteratorSuperVersion(SuperVersion* super_version,
bool background_purge);
InternalIterator* NewInternalIterator(const ReadOptions& read_options,
ColumnFamilyData* cfd,
SuperVersion* super_version,
Arena* arena, SequenceNumber sequence,
bool allow_unprepared_value,
ArenaWrappedDBIter* db_iter = nullptr);
LogsWithPrepTracker* logs_with_prep_tracker() {
return &logs_with_prep_tracker_;
@@ -1930,7 +1907,6 @@ class DBImpl : public DB {
friend class WriteBatchWithIndex;
friend class WriteUnpreparedTxnDB;
friend class WriteUnpreparedTxn;
friend class FileIngestionHandleImpl;
friend class ForwardIterator;
friend struct SuperVersion;
@@ -2268,10 +2244,6 @@ class DBImpl : public DB {
void ReleaseFileNumberFromPendingOutputs(
std::unique_ptr<std::list<uint64_t>::iterator>& v);
// Rolls back one prepared file ingestion (delete its staged files, release
// the reserved file numbers)
void RollbackPreparedFileIngestion(FileIngestionHandleImpl* const h);
// Similar to pending_outputs, preserve OPTIONS file. Used for remote
// compaction.
std::list<uint64_t>::iterator CaptureOptionsFileNumber();
@@ -3491,10 +3463,6 @@ class DBImpl : public DB {
// REQUIRES: mutex held
int num_running_ingest_file_ = 0;
// Number of FileIngestionHandle objects produced by PrepareFileIngestion()
// that have not been committed or destroyed yet.
std::atomic<uint32_t> num_outstanding_prepared_ingestions_{0};
WalManager wal_manager_;
// A value of > 0 temporarily disables scheduling of background work
-1
View File
@@ -833,7 +833,6 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
}
wal_manager_.PurgeObsoleteWALFiles();
LogFlush(immutable_db_options_.info_log);
TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:BeforePendingPurgeFinished");
InstrumentedMutexLock l(&mutex_);
--pending_purge_obsolete_files_;
assert(pending_purge_obsolete_files_ >= 0);
+1 -13
View File
@@ -255,17 +255,6 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
"then direct I/O reads (use_direct_reads) must be disabled. ");
}
if (db_options.allow_mmap_reads &&
db_options.use_direct_io_for_compaction_reads) {
// mmap reads and direct I/O share the same EnvOptions field, so enabling
// both would try to mmap and O_DIRECT the same reads. Reject it here rather
// than tripping a lower-level assert.
return Status::NotSupported(
"If memory mapped reads (allow_mmap_reads) are enabled "
"then compaction-only direct I/O reads "
"(use_direct_io_for_compaction_reads) must be disabled. ");
}
if (db_options.allow_mmap_writes &&
db_options.use_direct_io_for_flush_and_compaction) {
return Status::NotSupported(
@@ -517,8 +506,7 @@ Status DBImpl::Recover(
std::unique_ptr<FSRandomAccessFile> idfile;
FileOptions customized_fs(file_options_);
customized_fs.use_direct_reads |=
immutable_db_options_.use_direct_io_for_flush_and_compaction ||
immutable_db_options_.use_direct_io_for_compaction_reads;
immutable_db_options_.use_direct_io_for_flush_and_compaction;
const std::string& fname =
manifest_path.empty() ? current_fname : manifest_path;
s = fs_->NewRandomAccessFile(fname, customized_fs, &idfile, nullptr);
-13
View File
@@ -134,19 +134,6 @@ class DBImplReadOnly : public DBImpl {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::PrepareFileIngestion;
Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& /*args*/,
std::unique_ptr<FileIngestionHandle>* /*handle*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CommitFileIngestionHandles;
Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> /*handles*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::CreateColumnFamilyWithImport;
Status CreateColumnFamilyWithImport(
const ColumnFamilyOptions& /*options*/,
-13
View File
@@ -239,19 +239,6 @@ class DBImplSecondary : public DBImpl {
return Status::NotSupported("Not supported operation in secondary mode.");
}
using DB::PrepareFileIngestion;
Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& /*args*/,
std::unique_ptr<FileIngestionHandle>* /*handle*/) override {
return Status::NotSupported("Not supported operation in secondary mode.");
}
using DB::CommitFileIngestionHandles;
Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> /*handles*/) override {
return Status::NotSupported("Not supported operation in secondary mode.");
}
// Try to catch up with the primary by reading as much as possible from the
// log files until there is nothing more to read or encounters an error. If
// the amount of information in the log files to process is huge, this
+27 -40
View File
@@ -67,9 +67,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
const MutableCFOptions& mutable_cf_options,
const Comparator* cmp, InternalIterator* iter,
const Version* version, SequenceNumber s, bool arena_mode,
ReadCallback* read_callback, DBImpl* db_impl,
ColumnFamilyData* cfd, bool expose_blob_index,
ReadOnlyMemTable* active_mem)
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
bool expose_blob_index, ReadOnlyMemTable* active_mem)
: prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
env_(_env),
clock_(ioptions.clock),
@@ -77,26 +76,21 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
user_comparator_(cmp),
merge_operator_(ioptions.merge_operator.get()),
iter_(iter),
blob_state_(version, read_options.read_tier,
read_options.verify_checksums, read_options.fill_cache,
read_options.io_activity,
cfd ? cfd->blob_file_cache() : nullptr,
cfd != nullptr && cfd->blob_partition_manager() != nullptr),
blob_state_(
version, read_options.read_tier, read_options.verify_checksums,
read_options.fill_cache, read_options.io_activity,
cfh ? cfh->cfd()->blob_file_cache() : nullptr,
cfh != nullptr && cfh->cfd()->blob_partition_manager() != nullptr),
read_callback_(read_callback),
sequence_(s),
value_columns_state_(version, read_options, cfd),
value_columns_state_(version, read_options, cfh),
statistics_(ioptions.stats),
max_skip_(mutable_cf_options.max_sequential_skip_in_iterations),
max_skippable_internal_keys_(read_options.max_skippable_internal_keys),
num_internal_keys_skipped_(0),
iterate_lower_bound_(read_options.iterate_lower_bound),
iterate_upper_bound_(read_options.iterate_upper_bound),
trace_db_(db_impl),
trace_cf_id_(cfd != nullptr ? cfd->GetID() : 0),
has_trace_state_(db_impl != nullptr && cfd != nullptr),
allow_blob_write_path_fallback_(cfd != nullptr &&
cfd->blob_partition_manager() != nullptr),
ingest_sst_lock_(cfd != nullptr ? &cfd->GetIngestSstLock() : nullptr),
cfh_(cfh),
timestamp_ub_(read_options.timestamp),
timestamp_lb_(read_options.iter_start_ts),
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
@@ -311,8 +305,10 @@ bool DBIter::SetValueAndColumnsFromBlobImpl(const Slice& user_key,
// Keep the non-BDW iterator path on the pre-existing Version::GetBlob()
// fast path. Only enable the direct-write fallback when this CF actually
// has a write-path partition manager.
const bool allow_write_path_fallback =
cfh_ != nullptr && cfh_->cfd()->blob_partition_manager() != nullptr;
const Status s = blob_state_.mut()->reader.RetrieveAndSetBlobValue(
user_key, blob_index, allow_blob_write_path_fallback_);
user_key, blob_index, allow_write_path_fallback);
if (!s.ok()) {
status_ = s;
valid_ = false;
@@ -1621,8 +1617,10 @@ bool DBIter::MergeWithBlobBaseValue(const Slice& blob_index,
return false;
}
const bool allow_write_path_fallback =
cfh_ != nullptr && cfh_->cfd()->blob_partition_manager() != nullptr;
const Status s = blob_state_.mut()->reader.RetrieveAndSetBlobValue(
user_key, blob_index, allow_blob_write_path_fallback_);
user_key, blob_index, allow_write_path_fallback);
if (!s.ok()) {
status_ = s;
valid_ = false;
@@ -1818,10 +1816,10 @@ void DBIter::MaybeInsertRangeTombstone(const Slice& end_key) {
}
}
assert(ingest_sst_lock_ != nullptr);
assert(cfh_ != nullptr);
if (active_mem_->AddLogicallyRedundantRangeTombstone(
insert_seq, range_tomb_first_key_.GetUserKey(), end_key,
*ingest_sst_lock_)) {
cfh_->cfd()->GetIngestSstLock())) {
RecordTick(statistics_, READ_PATH_RANGE_TOMBSTONES_INSERTED);
ROCKS_LOG_DEBUG(logger_,
"Inserted range tombstone [%s, %s) @ seq %" PRIu64
@@ -1961,10 +1959,10 @@ Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
return Status::OK();
}
Status DBIter::SetScanOptionsForPrepare(const MultiScanArgs& scan_opts) {
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
status_ = ValidateScanOptions(scan_opts);
if (!status_.ok()) {
return status_;
return;
}
std::optional<MultiScanArgs> new_scan_opts;
new_scan_opts.emplace(scan_opts);
@@ -1977,27 +1975,14 @@ Status DBIter::SetScanOptionsForPrepare(const MultiScanArgs& scan_opts) {
if (!scan_opts_.value().io_dispatcher) {
scan_opts_->io_dispatcher.reset(NewIODispatcher());
}
return Status::OK();
}
void DBIter::PrepareInternalChildren() {
if (!scan_opts_.has_value() || !status_.ok()) {
return;
}
if (scan_opts_.value().HasBoundedScanRanges()) {
if (!scan_opts.empty()) {
iter_.Prepare(&scan_opts_.value());
} else {
iter_.Prepare(nullptr);
}
}
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
if (SetScanOptionsForPrepare(scan_opts).ok()) {
PrepareInternalChildren();
}
}
void DBIter::Seek(const Slice& target) {
PERF_COUNTER_ADD(iter_seek_count, 1);
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
@@ -2044,7 +2029,7 @@ void DBIter::Seek(const Slice& target) {
scan_index_++;
}
if (has_trace_state_) {
if (cfh_ != nullptr) {
// TODO: What do we do if this returns an error?
Slice lower_bound, upper_bound;
if (iterate_lower_bound_ != nullptr) {
@@ -2057,7 +2042,9 @@ void DBIter::Seek(const Slice& target) {
} else {
upper_bound = Slice("");
}
trace_db_->TraceIteratorSeek(trace_cf_id_, target, lower_bound, upper_bound)
cfh_->db()
->TraceIteratorSeek(cfh_->cfd()->GetID(), target, lower_bound,
upper_bound)
.PermitUncheckedError();
}
@@ -2110,7 +2097,7 @@ void DBIter::SeekForPrev(const Slice& target) {
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
StopWatch sw(clock_, statistics_, DB_SEEK);
if (has_trace_state_) {
if (cfh_ != nullptr) {
// TODO: What do we do if this returns an error?
Slice lower_bound, upper_bound;
if (iterate_lower_bound_ != nullptr) {
@@ -2123,8 +2110,8 @@ void DBIter::SeekForPrev(const Slice& target) {
} else {
upper_bound = Slice("");
}
trace_db_
->TraceIteratorSeekForPrev(trace_cf_id_, target, lower_bound,
cfh_->db()
->TraceIteratorSeekForPrev(cfh_->cfd()->GetID(), target, lower_bound,
upper_bound)
.PermitUncheckedError();
}
+11 -25
View File
@@ -32,9 +32,6 @@
namespace ROCKSDB_NAMESPACE {
class BlobFileCache;
class Version;
namespace port {
class RWMutex;
}
// This file declares the factory functions of DBIter, in its original form
// or a wrapped form with class ArenaWrappedDBIter, which is defined here.
@@ -86,19 +83,14 @@ class DBIter final : public Iterator {
ReadCallback* read_callback,
ReadOnlyMemTable* active_mem,
ColumnFamilyHandleImpl* cfh = nullptr,
bool expose_blob_index = false, Arena* arena = nullptr,
DBImpl* db_impl = nullptr,
ColumnFamilyData* cfd = nullptr) {
if (cfh != nullptr) {
db_impl = cfh->db();
cfd = cfh->cfd();
}
bool expose_blob_index = false,
Arena* arena = nullptr) {
void* mem = arena ? arena->AllocateAligned(sizeof(DBIter))
: operator new(sizeof(DBIter));
DBIter* db_iter = new (mem)
DBIter(env, read_options, ioptions, mutable_cf_options,
user_key_comparator, internal_iter, version, sequence, arena,
read_callback, db_impl, cfd, expose_blob_index, active_mem);
read_callback, cfh, expose_blob_index, active_mem);
return db_iter;
}
@@ -215,7 +207,7 @@ class DBIter final : public Iterator {
}
Status status() const override {
if (status_.ok() && iter_.iter() != nullptr) {
if (status_.ok()) {
return iter_.status();
} else {
assert(!valid_);
@@ -256,22 +248,19 @@ class DBIter final : public Iterator {
iter_.SetRangeDelReadSeqno(s);
}
void set_valid(bool v) { valid_ = v; }
void set_status(Status s) { status_ = std::move(s); }
bool PrepareValue() override;
void Prepare(const MultiScanArgs& scan_opts) override;
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
Status SetScanOptionsForPrepare(const MultiScanArgs& scan_opts);
void PrepareInternalChildren();
private:
DBIter(Env* _env, const ReadOptions& read_options,
const ImmutableOptions& ioptions,
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
InternalIterator* iter, const Version* version, SequenceNumber s,
bool arena_mode, ReadCallback* read_callback, DBImpl* db_impl,
ColumnFamilyData* cfd, bool expose_blob_index,
bool arena_mode, ReadCallback* read_callback,
ColumnFamilyHandleImpl* cfh, bool expose_blob_index,
ReadOnlyMemTable* active_mem);
class BlobReader {
@@ -329,12 +318,13 @@ class DBIter final : public Iterator {
class ValueColumnsState {
public:
ValueColumnsState(const Version* version, const ReadOptions& read_options,
ColumnFamilyData* cfd)
ColumnFamilyHandleImpl* cfh)
: entity_blob_resolver_(
version, read_options.read_tier, read_options.verify_checksums,
read_options.fill_cache, read_options.io_activity,
cfd ? cfd->blob_file_cache() : nullptr,
cfd != nullptr && cfd->blob_partition_manager() != nullptr) {}
cfh ? cfh->cfd()->blob_file_cache() : nullptr,
cfh != nullptr &&
cfh->cfd()->blob_partition_manager() != nullptr) {}
Slice& value() { return value_; }
const Slice& value() const { return value_; }
@@ -716,11 +706,7 @@ class DBIter final : public Iterator {
MergeContext merge_context_;
LocalStatistics local_stats_;
PinnedIteratorsManager pinned_iters_mgr_;
DBImpl* trace_db_;
uint32_t trace_cf_id_;
bool has_trace_state_;
bool allow_blob_write_path_fallback_;
port::RWMutex* ingest_sst_lock_;
ColumnFamilyHandleImpl* cfh_;
const Slice* const timestamp_ub_;
const Slice* const timestamp_lb_;
const size_t timestamp_size_;
-481
View File
@@ -10,9 +10,6 @@
#include <functional>
#include <iomanip>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
#include "db/arena_wrapped_db_iter.h"
#include "db/db_iter.h"
@@ -90,392 +87,6 @@ TEST_F(DBIteratorBaseTest, APICallsWithPerfContext) {
delete iter;
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanPrunesNonIntersectingFiles) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Flush());
ASSERT_OK(Put("z", "vz"));
ASSERT_OK(Flush());
ASSERT_EQ(2, NumTableFilesAtLevel(0));
int table_iterators_created = 0;
int files_added = 0;
int block_based_iterators = 0;
int level_iterators = 0;
SyncPoint::GetInstance()->SetCallBack(
"TableCache::NewIterator::BeforeFindTable",
[&](void* /*arg*/) { ++table_iterators_created; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:AddedFile",
[&](void* /*arg*/) { ++files_added; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:IteratorType", [&](void* arg) {
auto* iterator_type = static_cast<std::pair<bool, bool>*>(arg);
if (iterator_type->first) {
++block_based_iterators;
}
if (iterator_type->second) {
++level_iterators;
}
});
SyncPoint::GetInstance()->EnableProcessing();
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("b"));
Slice upper_bound("b");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(0, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(0, level_iterators);
iter->Prepare(scan_opts);
ASSERT_EQ(1, table_iterators_created);
ASSERT_EQ(1, files_added);
ASSERT_EQ(1, block_based_iterators);
ASSERT_EQ(0, level_iterators);
std::vector<std::string> keys;
for (iter->Seek("a"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"a"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanPrunesNonIntersectingLevels) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("z", "vz"));
ASSERT_OK(Flush());
MoveFilesToLevel(2);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
ASSERT_OK(Put("m", "vm"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
int table_iterators_created = 0;
int files_added = 0;
int block_based_iterators = 0;
int level_iterators = 0;
SyncPoint::GetInstance()->SetCallBack(
"TableCache::NewIterator::BeforeFindTable",
[&](void* /*arg*/) { ++table_iterators_created; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:AddedFile",
[&](void* /*arg*/) { ++files_added; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:IteratorType", [&](void* arg) {
auto* iterator_type = static_cast<std::pair<bool, bool>*>(arg);
if (iterator_type->first) {
++block_based_iterators;
}
if (iterator_type->second) {
++level_iterators;
}
});
SyncPoint::GetInstance()->EnableProcessing();
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("n"));
Slice upper_bound("n");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(0, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(0, level_iterators);
iter->Prepare(scan_opts);
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(2, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(1, level_iterators);
std::vector<std::string> keys;
for (iter->Seek("a"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"a", "m"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanAllowsSingleUnboundedRange) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Put("b", "vb"));
ASSERT_OK(Put("c", "vc"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("b"));
ReadOptions read_options;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
iter->Prepare(scan_opts);
std::vector<std::string> keys;
for (iter->Seek("b"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"b", "c"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanRejectsRepeatedPrepare) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("b"));
Slice upper_bound("b");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
iter->Prepare(scan_opts);
ASSERT_OK(iter->status());
iter->Prepare(scan_opts);
ASSERT_NOK(iter->status());
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanDedupsMultipleRangesInSameFile) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Put("m", "vm"));
ASSERT_OK(Put("z", "vz"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
int table_iterators_created = 0;
int files_added = 0;
int block_based_iterators = 0;
int level_iterators = 0;
SyncPoint::GetInstance()->SetCallBack(
"TableCache::NewIterator::BeforeFindTable",
[&](void* /*arg*/) { ++table_iterators_created; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:AddedFile",
[&](void* /*arg*/) { ++files_added; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:IteratorType", [&](void* arg) {
auto* iterator_type = static_cast<std::pair<bool, bool>*>(arg);
if (iterator_type->first) {
++block_based_iterators;
}
if (iterator_type->second) {
++level_iterators;
}
});
SyncPoint::GetInstance()->EnableProcessing();
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("b"));
scan_opts.insert(Slice("m"), Slice("n"));
Slice upper_bound("b");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(0, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(0, level_iterators);
iter->Prepare(scan_opts);
ASSERT_EQ(1, table_iterators_created);
ASSERT_EQ(1, files_added);
ASSERT_EQ(1, block_based_iterators);
ASSERT_EQ(0, level_iterators);
std::vector<std::string> keys;
for (iter->Seek("a"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_OK(iter->status());
ASSERT_EQ(keys, std::vector<std::string>({"a"}));
upper_bound = "n";
for (iter->Seek("m"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"a", "m"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanPrunesOverlappingL0Files) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Flush());
ASSERT_OK(Put("m", "vm"));
ASSERT_OK(Flush());
ASSERT_EQ(2, NumTableFilesAtLevel(0));
int table_iterators_created = 0;
int files_added = 0;
int block_based_iterators = 0;
int level_iterators = 0;
SyncPoint::GetInstance()->SetCallBack(
"TableCache::NewIterator::BeforeFindTable",
[&](void* /*arg*/) { ++table_iterators_created; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:AddedFile",
[&](void* /*arg*/) { ++files_added; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:IteratorType", [&](void* arg) {
auto* iterator_type = static_cast<std::pair<bool, bool>*>(arg);
if (iterator_type->first) {
++block_based_iterators;
}
if (iterator_type->second) {
++level_iterators;
}
});
SyncPoint::GetInstance()->EnableProcessing();
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("n"));
Slice upper_bound("n");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(0, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(0, level_iterators);
iter->Prepare(scan_opts);
ASSERT_EQ(2, table_iterators_created);
ASSERT_EQ(2, files_added);
ASSERT_EQ(2, block_based_iterators);
ASSERT_EQ(0, level_iterators);
std::vector<std::string> keys;
for (iter->Seek("a"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"a", "m"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DBIteratorBaseTest, PrepareWithMultiScanPrunesNonIntersectingMemTables) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
DestroyAndReopen(options);
ASSERT_OK(Put("a", "va"));
ASSERT_OK(Flush());
MoveFilesToLevel(1);
ASSERT_OK(db_->PauseBackgroundWork());
ASSERT_OK(Put("z_imm", "vz"));
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
ASSERT_OK(Put("z_mem", "vz"));
int table_iterators_created = 0;
int files_added = 0;
int block_based_iterators = 0;
int level_iterators = 0;
int merging_iterators = 0;
SyncPoint::GetInstance()->SetCallBack(
"TableCache::NewIterator::BeforeFindTable",
[&](void* /*arg*/) { ++table_iterators_created; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:AddedFile",
[&](void* /*arg*/) { ++files_added; });
SyncPoint::GetInstance()->SetCallBack(
"Version::AddIteratorsForLevel:IteratorType", [&](void* arg) {
auto* iterator_type = static_cast<std::pair<bool, bool>*>(arg);
if (iterator_type->first) {
++block_based_iterators;
}
if (iterator_type->second) {
++level_iterators;
}
});
SyncPoint::GetInstance()->SetCallBack(
"MergeIteratorBuilder::Finish:UseMergingIterator", [&](void* arg) {
if (*static_cast<bool*>(arg)) {
++merging_iterators;
}
});
SyncPoint::GetInstance()->EnableProcessing();
MultiScanArgs scan_opts(BytewiseComparator());
scan_opts.insert(Slice("a"), Slice("b"));
Slice upper_bound("b");
ReadOptions read_options;
read_options.iterate_upper_bound = &upper_bound;
std::unique_ptr<Iterator> iter(
db_->NewIterator(read_options, db_->DefaultColumnFamily()));
ASSERT_EQ(0, table_iterators_created);
ASSERT_EQ(0, files_added);
ASSERT_EQ(0, block_based_iterators);
ASSERT_EQ(0, level_iterators);
ASSERT_EQ(0, merging_iterators);
iter->Prepare(scan_opts);
ASSERT_EQ(1, table_iterators_created);
ASSERT_EQ(1, files_added);
ASSERT_EQ(1, block_based_iterators);
ASSERT_EQ(0, level_iterators);
ASSERT_EQ(0, merging_iterators);
std::vector<std::string> keys;
for (iter->Seek("a"); iter->Valid(); iter->Next()) {
keys.push_back(iter->key().ToString());
}
ASSERT_EQ(keys, std::vector<std::string>({"a"}));
ASSERT_FALSE(iter->Valid());
ASSERT_OK(iter->status());
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
ASSERT_OK(db_->ContinueBackgroundWork());
}
// Test param:
// bool: whether to pass read_callback to NewIterator().
class DBIteratorTest : public DBIteratorBaseTest,
@@ -1334,68 +945,6 @@ TEST_P(DBIteratorTest, IteratorDeleteAfterCfDelete) {
delete iter;
}
TEST_P(DBIteratorTest, IteratorSeekAfterCfDelete) {
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
ASSERT_OK(Put(1, "foo", "delete-cf-then-seek-iter"));
ASSERT_OK(Put(1, "hello", "value2"));
ColumnFamilyHandle* cf = handles_[1];
ReadOptions ro;
auto* iter = db_->NewIterator(ro, cf);
// Delete the CF handle before the lazy iterator tree is materialized.
EXPECT_OK(db_->DestroyColumnFamilyHandle(cf));
handles_.erase(std::begin(handles_) + 1);
iter->Seek("foo");
ASSERT_EQ(IterStatus(iter), "foo->delete-cf-then-seek-iter");
iter->SeekForPrev("hello");
ASSERT_EQ(IterStatus(iter), "hello->value2");
iter->SeekToFirst();
ASSERT_EQ(IterStatus(iter), "foo->delete-cf-then-seek-iter");
iter->Next();
ASSERT_EQ(IterStatus(iter), "hello->value2");
delete iter;
}
TEST_P(DBIteratorTest, IteratorAutoRefreshAfterCfDeleteBeforeLazyInit) {
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
ASSERT_OK(Put(1, "foo", "delete-cf-then-auto-refresh"));
ASSERT_OK(Flush(1));
ColumnFamilyHandle* cf = handles_[1];
const Snapshot* snapshot = db_->GetSnapshot();
ReadOptions ro;
ro.snapshot = snapshot;
ro.auto_refresh_iterator_with_snapshot = true;
auto* iter = db_->NewIterator(ro, cf);
ASSERT_OK(Put(1, "zzz", "after-snapshot"));
ASSERT_OK(Flush(1));
// Delete the CF handle before the lazy iterator tree is materialized. The
// following operations force auto-refresh to acquire a newer SuperVersion.
EXPECT_OK(db_->DestroyColumnFamilyHandle(cf));
handles_.erase(std::begin(handles_) + 1);
iter->Seek("foo");
ASSERT_EQ(IterStatus(iter), "foo->delete-cf-then-auto-refresh");
ASSERT_OK(iter->status());
iter->SeekForPrev("foo");
ASSERT_EQ(IterStatus(iter), "foo->delete-cf-then-auto-refresh");
ASSERT_OK(iter->status());
iter->Next();
ASSERT_OK(iter->status());
delete iter;
db_->ReleaseSnapshot(snapshot);
}
TEST_P(DBIteratorTest, IteratorDeleteAfterCfDrop) {
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
@@ -1417,30 +966,6 @@ TEST_P(DBIteratorTest, IteratorDeleteAfterCfDrop) {
delete iter;
}
TEST_P(DBIteratorTest, IteratorSeekAfterCfDrop) {
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
ASSERT_OK(Put(1, "foo", "drop-cf-then-seek-iter"));
ReadOptions ro;
ColumnFamilyHandle* cf = handles_[1];
auto* iter = db_->NewIterator(ro, cf);
// Drop and delete the CF before the lazy iterator tree is materialized.
EXPECT_OK(db_->DropColumnFamily(cf));
EXPECT_OK(db_->DestroyColumnFamilyHandle(cf));
handles_.erase(std::begin(handles_) + 1);
iter->Seek("foo");
ASSERT_EQ(IterStatus(iter), "foo->drop-cf-then-seek-iter");
iter->SeekForPrev("foo");
ASSERT_EQ(IterStatus(iter), "foo->drop-cf-then-seek-iter");
iter->SeekToFirst();
ASSERT_EQ(IterStatus(iter), "foo->drop-cf-then-seek-iter");
delete iter;
}
// SetOptions not defined in ROCKSDB LITE
TEST_P(DBIteratorTest, DBIteratorBoundTest) {
Options options = CurrentOptions();
@@ -2752,10 +2277,6 @@ TEST_P(DBIteratorTest, ReadAhead) {
options.env = env_;
options.disable_auto_compactions = true;
options.write_buffer_size = 4 << 20;
// Pin compression so the readahead byte thresholds below don't depend on the
// default compression type. kNoCompression keeps the SST files large enough
// to exercise readahead and is always available.
options.compression = kNoCompression;
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
BlockBasedTableOptions table_options;
table_options.block_size = 1024;
@@ -3172,8 +2693,6 @@ TEST_P(DBIteratorTest, CreationFailure) {
Iterator* iter = NewIterator(ReadOptions());
ASSERT_FALSE(iter->Valid());
iter->SeekToFirst();
ASSERT_FALSE(iter->Valid());
ASSERT_TRUE(iter->status().IsCorruption());
delete iter;
}
+3 -3
View File
@@ -359,7 +359,7 @@ class DBMemTableTestForSeek : public DBMemTableTest,
TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
// Validate key corruption could be detected during seek.
// We insert many keys into skiplist. Then we corrupt the each key one at a
// time. With memtable_verify_per_key_checksum_on_seek enabled, when the
// time. With memtable_veirfy_per_key_checksum_on_seek enabled, when the
// corrupted key is searched, the checksum of every key visited during the
// seek is validated. It will report data corruption. Otherwise seek returns
// not found.
@@ -367,7 +367,7 @@ TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
Options options = CurrentOptions();
options.allow_data_in_errors = allow_data_in_error;
options.paranoid_memory_checks = std::get<1>(GetParam());
options.memtable_verify_per_key_checksum_on_seek = std::get<2>(GetParam());
options.memtable_veirfy_per_key_checksum_on_seek = std::get<2>(GetParam());
options.memtable_protection_bytes_per_key = 8;
DestroyAndReopen(options);
@@ -410,7 +410,7 @@ TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
ASSERT_EQ(raw_data_pointer.size(), key_count);
bool enable_key_validation_on_seek =
options.memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek;
// For each key, corrupt it, validate corruption is detected correctly, then
// revert it.
+2 -2
View File
@@ -151,8 +151,8 @@ TEST_F(DBOpenWithConfigTest, OpenWithComprehensiveConfig) {
// memtable_op_scan_flush_trigger=0
options.memtable_op_scan_flush_trigger = 0;
// memtable_verify_per_key_checksum_on_seek=false
options.memtable_verify_per_key_checksum_on_seek = false;
// memtable_veirfy_per_key_checksum_on_seek=false
options.memtable_veirfy_per_key_checksum_on_seek = false;
// Level and compaction settings
// num_levels=50
-166
View File
@@ -13,7 +13,6 @@
#include "db/column_family.h"
#include "db/db_impl/db_impl.h"
#include "db/db_test_util.h"
#include "env/mock_env.h"
#include "options/options_helper.h"
#include "port/stack_trace.h"
#include "rocksdb/cache.h"
@@ -1764,171 +1763,6 @@ TEST_F(DBOptionsTest, SetOptionsMultipleColumnFamilies) {
ASSERT_TRUE(dbfull()->GetOptions(handles_[2]).disable_auto_compactions);
}
// Confirms the default value and serialization/parse round-trip of the new
// option. No DB open required; exercises only the options-metadata layer.
TEST_F(DBOptionsTest, UseDirectIoForCompactionReadsRoundTrip) {
// Default value must remain false to preserve existing semantics.
ASSERT_FALSE(DBOptions().use_direct_io_for_compaction_reads);
DBOptions parsed;
ConfigOptions config_options;
ASSERT_OK(GetDBOptionsFromString(config_options, DBOptions(),
"use_direct_io_for_compaction_reads=true",
&parsed));
ASSERT_TRUE(parsed.use_direct_io_for_compaction_reads);
ASSERT_OK(GetDBOptionsFromString(config_options, DBOptions(),
"use_direct_io_for_compaction_reads=false",
&parsed));
ASSERT_FALSE(parsed.use_direct_io_for_compaction_reads);
}
// Validates that Open rejects the documented incompatible combination.
TEST_F(DBOptionsTest, UseDirectIoForCompactionReadsValidation) {
// mmap_reads + use_direct_io_for_compaction_reads is rejected at Open
// time, the same way mmap_reads + use_direct_reads has always been
// rejected.
Options bad_options = CurrentOptions();
bad_options.create_if_missing = true;
bad_options.allow_mmap_reads = true;
bad_options.use_direct_io_for_compaction_reads = true;
Status bad_status = TryReopen(bad_options);
ASSERT_TRUE(bad_status.IsNotSupported()) << bad_status.ToString();
}
// Confirms the option is plumbed all the way to the live DB's options API:
// after opening with the flag set, GetDBOptions() reports it back. Only runs
// when the environment supports direct I/O.
TEST_F(DBOptionsTest, UseDirectIoForCompactionReadsLiveReopen) {
Options options = CurrentOptions();
options.create_if_missing = true;
options.use_direct_io_for_compaction_reads = true;
// Use a buffered user-read setup so the new flag is the one doing the work.
options.use_direct_reads = false;
options.use_direct_io_for_flush_and_compaction = false;
Status s = TryReopen(options);
if (s.IsNotSupported() || s.IsInvalidArgument()) {
ROCKSDB_GTEST_BYPASS(
"Direct I/O not supported in this test environment; live reopen "
"cannot be exercised.");
return;
}
ASSERT_OK(s);
ASSERT_TRUE(dbfull()->GetDBOptions().use_direct_io_for_compaction_reads);
Close();
}
TEST_F(DBOptionsTest, UseDirectIoForCompactionReadsUnsupportedFileSystem) {
auto fs = std::make_shared<MockFileSystem>(Env::Default()->GetSystemClock(),
/*supports_direct_io=*/false);
std::unique_ptr<Env> mock_env = NewCompositeEnv(fs);
Options options = CurrentOptions();
options.env = mock_env.get();
options.create_if_missing = true;
options.use_direct_reads = false;
options.use_direct_io_for_flush_and_compaction = false;
options.use_direct_io_for_compaction_reads = true;
Status s = TryReopen(options);
ASSERT_TRUE(s.IsInvalidArgument()) << s.ToString();
ASSERT_NE(s.ToString().find("Direct I/O is not supported"), std::string::npos)
<< s.ToString();
}
TEST_F(
DBOptionsTest,
UseDirectIoForCompactionReadsSetDBOptionsKeepsVerificationReadsBuffered) {
auto fs = std::make_shared<MockFileSystem>(Env::Default()->GetSystemClock(),
/*supports_direct_io=*/true);
std::unique_ptr<Env> mock_env = NewCompositeEnv(fs);
Options options = CurrentOptions();
options.env = mock_env.get();
options.create_if_missing = true;
options.use_direct_reads = false;
options.use_direct_io_for_flush_and_compaction = false;
options.use_direct_io_for_compaction_reads = true;
ASSERT_OK(TryReopen(options));
bool observed = false;
FileOptions observed_file_options;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"DBImpl::SetDBOptions:FileOptionsForCompaction", [&](void* arg) {
observed = true;
observed_file_options = *static_cast<FileOptions*>(arg);
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "1024"}}));
ASSERT_TRUE(observed);
ASSERT_FALSE(observed_file_options.use_direct_reads);
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
Close();
}
// Exercises the FileSystem::OptimizeForCompactionTableRead and
// OptimizeForBlobFileRead helpers directly, asserting that the compaction-only
// flag does not affect shared FileSystem hooks.
TEST_F(DBOptionsTest, OptimizeForCompactionTableReadUsesGlobalDirectReadsOnly) {
FileOptions in_opts;
in_opts.use_direct_reads = false;
{
Options check_options;
check_options.use_direct_reads = false;
check_options.use_direct_io_for_compaction_reads = true;
check_options.use_direct_io_for_flush_and_compaction = false;
ImmutableDBOptions immutable(check_options);
FileOptions sst_read =
env_->GetFileSystem()->OptimizeForCompactionTableRead(in_opts,
immutable);
FileOptions blob_read =
env_->GetFileSystem()->OptimizeForBlobFileRead(in_opts, immutable);
EXPECT_FALSE(sst_read.use_direct_reads);
EXPECT_FALSE(blob_read.use_direct_reads);
EXPECT_FALSE(sst_read.use_direct_writes);
}
{
Options off_options;
off_options.use_direct_reads = false;
off_options.use_direct_io_for_compaction_reads = false;
off_options.use_direct_io_for_flush_and_compaction = false;
ImmutableDBOptions immutable_off(off_options);
FileOptions sst_read_off =
env_->GetFileSystem()->OptimizeForCompactionTableRead(in_opts,
immutable_off);
EXPECT_FALSE(sst_read_off.use_direct_reads);
}
{
Options global_on_options;
global_on_options.use_direct_reads = true;
global_on_options.use_direct_io_for_compaction_reads = false;
global_on_options.use_direct_io_for_flush_and_compaction = false;
ImmutableDBOptions immutable_global(global_on_options);
FileOptions sst_read_global =
env_->GetFileSystem()->OptimizeForCompactionTableRead(in_opts,
immutable_global);
EXPECT_TRUE(sst_read_global.use_direct_reads);
}
{
Options both_on;
both_on.use_direct_reads = true;
both_on.use_direct_io_for_compaction_reads = true;
both_on.use_direct_io_for_flush_and_compaction = false;
ImmutableDBOptions immutable_both(both_on);
FileOptions sst_read_both =
env_->GetFileSystem()->OptimizeForCompactionTableRead(in_opts,
immutable_both);
EXPECT_TRUE(sst_read_both.use_direct_reads);
}
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
-18
View File
@@ -3516,18 +3516,6 @@ class ModelDB : public DB {
return Status::NotSupported("Not implemented");
}
using DB::PrepareFileIngestion;
Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& /*args*/,
std::unique_ptr<FileIngestionHandle>* /*handle*/) override {
return Status::NotSupported("Not implemented.");
}
Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> /*handles*/) override {
return Status::NotSupported("Not implemented.");
}
using DB::CreateColumnFamilyWithImport;
Status CreateColumnFamilyWithImport(
const ColumnFamilyOptions& /*options*/,
@@ -3785,12 +3773,6 @@ class ModelDB : public DB {
return Status::OK();
}
Status GetPreparedFileInfoForExternalSstIngestion(
const std::string& /*file_path*/,
std::shared_ptr<const PreparedFileInfo>* /*file_info*/) override {
return Status::NotSupported();
}
Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
return Status::OK();
}
-2
View File
@@ -860,8 +860,6 @@ TEST_F(DBWALTest, WALWithChecksumHandoff) {
std::shared_ptr<FaultInjectionTestFS> fault_fs(
new FaultInjectionTestFS(FileSystem::Default()));
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
// Close the DB before the local Env is destroyed if an ASSERT exits early.
Defer close_db_on_exit([this]() { Close(); });
do {
Options options = CurrentOptions();
-103
View File
@@ -7,7 +7,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.
#include <atomic>
#include <cstdlib>
#include <map>
#include <string>
@@ -259,108 +258,6 @@ TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) {
CheckFileTypeCounts(dbname_, 0, 1, 1);
}
// Regression test for a use-after-free where an obsolete-file purge started
// DURING DB close -- after CloseHelper's early purge drain -- continued using
// DBImpl after ~DBImpl destroyed mutex_. Seen as a vhost-user-blk SIGABRT when
// many RocksDB instances closed concurrently under a Warm Storage fault, with
// avoid_unnecessary_blocking_io=true on a shared, process-wide Env.
//
// Here we make the straggler deterministic: keep a dropped column family handle
// alive, delete it only after CloseHelper has passed its early purge drain,
// then park that cleanup after FindObsoleteFiles() has incremented
// pending_purge_obsolete_files_ but before PurgeObsoleteFiles(..., true)
// schedules BGWorkPurge. Without the fix, the final drain sees
// bg_purge_scheduled_ == 0 and can destroy DBImpl while the pending cleanup is
// still about to lock/use it. With the fix, CloseHelper waits through the
// pending handoff and then through the scheduled purge.
TEST_F(DeleteFileTest, CloseWaitsForPurgeScheduledDuringClose) {
Options options = CurrentOptions();
SetOptions(&options);
Destroy(options);
options.create_if_missing = true;
// Route obsolete-file deletion through the background purge queue, matching
// the production config that hit the bug.
options.avoid_unnecessary_blocking_io = true;
Reopen(options);
// A dropped CF whose handle we keep alive: deleting the handle schedules a
// *background* purge (the dropped-CF teardown path).
ColumnFamilyHandle* cfh = nullptr;
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "dropme", &cfh));
ASSERT_OK(db_->Put(WriteOptions(), cfh, "key", "value"));
ASSERT_OK(db_->Flush(FlushOptions(), cfh));
ASSERT_OK(db_->DropColumnFamily(cfh));
ASSERT_OK(dbfull()->TEST_WaitForPurge()); // settle setup purges
std::atomic<bool> block_pending_handoff{false};
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
SyncPoint::GetInstance()->SetCallBack(
"DBImpl::PurgeObsoleteFiles:BeforePendingPurgeFinished", [&](void*) {
if (!block_pending_handoff.exchange(false)) {
return;
}
TEST_SYNC_POINT(
"DeleteFileTest::CloseWaitsForPurge:PendingHandoffReady");
TEST_SYNC_POINT(
"DeleteFileTest::CloseWaitsForPurge:PendingHandoffBlocked");
});
SyncPoint::GetInstance()->LoadDependency({
// Delete the dropped CF handle only after CloseHelper has passed its
// early purge drain and entered a mutex-unlocked close window.
{"DBImpl::CloseHelper:CFHandleCleanupUnlocked",
"DeleteFileTest::CloseWaitsForPurge:DropDuringCloseUnlock"},
// Let the test thread observe that the deleter is parked in the pending
// handoff window.
{"DeleteFileTest::CloseWaitsForPurge:PendingHandoffReady",
"DeleteFileTest::CloseWaitsForPurge:WaitForPendingHandoff"},
// Keep CloseHelper in the mutex-unlocked close window until the
// dropped-CF cleanup reaches the pending-to-scheduled handoff.
{"DeleteFileTest::CloseWaitsForPurge:PendingHandoffReady",
"DBImpl::CloseHelper:CFHandleCleanupAllowed"},
// Observe that CloseHelper is actually waiting in the final purge drain
// while the dropped-CF cleanup is still parked in the pending handoff.
{"DBImpl::CloseHelper:FinalPurgeDrainWait",
"DeleteFileTest::CloseWaitsForPurge:WaitForFinalPurgeDrain"},
// Keep the dropped-CF cleanup parked in the pending handoff window until
// the test releases it.
{"DeleteFileTest::CloseWaitsForPurge:ReleasePendingHandoff",
"DeleteFileTest::CloseWaitsForPurge:PendingHandoffBlocked"},
// Park the scheduled BGWorkPurge before it locks mutex_ until we release
// it, so CloseHelper also has to wait for bg_purge_scheduled_.
{"DeleteFileTest::CloseWaitsForPurge:ReleaseBackgroundPurge",
"DBImpl::BackgroundCallPurge:beforeMutexLock"},
});
SyncPoint::GetInstance()->EnableProcessing();
port::Thread closer([&]() { db_.reset(); });
// Unblocks once CloseHelper is in a mutex-unlocked close window; then
// deleting the dropped CF handle starts purge work the early drain missed.
TEST_SYNC_POINT("DeleteFileTest::CloseWaitsForPurge:DropDuringCloseUnlock");
block_pending_handoff.store(true);
ColumnFamilyHandle* dropped_cfh = cfh;
cfh = nullptr;
port::Thread dropped_cf_deleter([dropped_cfh]() { delete dropped_cfh; });
TEST_SYNC_POINT("DeleteFileTest::CloseWaitsForPurge:WaitForPendingHandoff");
// Wait until CloseHelper is blocked in the final drain with no scheduled
// purge yet. Without the fix, it would proceed while the dropped-CF cleanup
// is still pending. With the fix, it waits for pending_purge_obsolete_files_.
TEST_SYNC_POINT("DeleteFileTest::CloseWaitsForPurge:WaitForFinalPurgeDrain");
// Release the pending cleanup, then the BGWorkPurge it schedules. With the
// fix, the closer waits for both transitions and completes cleanly.
TEST_SYNC_POINT("DeleteFileTest::CloseWaitsForPurge:ReleasePendingHandoff");
dropped_cf_deleter.join();
TEST_SYNC_POINT("DeleteFileTest::CloseWaitsForPurge:ReleaseBackgroundPurge");
closer.join();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(DeleteFileTest, PurgeDuringOpen) {
Options options = CurrentOptions();
CheckFileTypeCounts(dbname_, -1, 0, -1);
+4 -4
View File
@@ -1311,7 +1311,7 @@ TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
});
if (i == 0) {
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::CheckSyncReturnCode", [&](void* s) {
"ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* s) {
Status* status = static_cast<Status*>(s);
if (status->IsNotSupported()) {
no_sync = true;
@@ -1367,13 +1367,13 @@ TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
}
}
TEST_F(ExternalSSTFileBasicTest, SyncFileNotSupported) {
TEST_F(ExternalSSTFileBasicTest, ReopenNotSupported) {
Options options;
options.create_if_missing = true;
options.env = env_;
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::CheckSyncReturnCode", [&](void* arg) {
"ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* arg) {
Status* s = static_cast<Status*>(arg);
*s = Status::NotSupported();
});
@@ -1386,7 +1386,7 @@ TEST_F(ExternalSSTFileBasicTest, SyncFileNotSupported) {
std::unique_ptr<SstFileWriter> sst_file_writer(
new SstFileWriter(EnvOptions(), sst_file_writer_options));
std::string file_name =
sst_files_dir_ + "sync_file_not_supported_test_" + ".sst";
sst_files_dir_ + "reopen_not_supported_test_" + ".sst";
ASSERT_OK(sst_file_writer->Open(file_name));
ASSERT_OK(sst_file_writer->Put("bar", "v2"));
ASSERT_OK(sst_file_writer->Finish());
+110 -289
View File
@@ -19,7 +19,6 @@
#include "logging/logging.h"
#include "monitoring/statistics_impl.h"
#include "table/merging_iterator.h"
#include "table/prepared_file_info.h"
#include "table/sst_file_writer_collectors.h"
#include "table/table_builder.h"
#include "table/unique_id_impl.h"
@@ -32,24 +31,18 @@ Status ExternalSstFileIngestionJob::Prepare(
const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::vector<const PreparedFileInfo*>& file_infos,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv) {
Status status;
// Read the information of files we are ingesting.
for (size_t i = 0; i < external_files_paths.size(); i++) {
const std::string& file_path = external_files_paths[i];
// Read the information of files we are ingesting
for (const std::string& file_path : external_files_paths) {
IngestedFileInfo file_to_ingest;
// For temperature, first assume it matches provided hint
file_to_ingest.file_temperature = file_temperature;
file_to_ingest.prefetch_lmax_index_and_filter_blocks =
ingestion_options_.prefetch_lmax_index_and_filter_blocks;
const PreparedFileInfo* prepared_file_info =
file_infos.empty() ? nullptr : file_infos[i];
status = GetIngestedFileInfo(file_path, next_file_number++,
prepared_file_info, &file_to_ingest, sv);
status =
GetIngestedFileInfo(file_path, next_file_number++, &file_to_ingest, sv);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to get ingested file info: %s: %s",
@@ -83,10 +76,23 @@ Status ExternalSstFileIngestionJob::Prepare(
auto num_files = files_to_ingest_.size();
if (num_files == 0) {
return Status::InvalidArgument("The list of files is empty");
} else if (num_files > 1) {
// Verify that passed files don't have overlapping ranges
autovector<const IngestedFileInfo*> sorted_files;
for (size_t i = 0; i < num_files; i++) {
sorted_files.push_back(&files_to_ingest_[i]);
}
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
for (size_t i = 0; i + 1 < num_files; i++) {
if (file_range_checker_.Overlaps(*sorted_files[i], *sorted_files[i + 1],
/* known_sorted= */ true)) {
files_overlap_ = true;
break;
}
}
}
// Detect whether the input files overlap one another; this drives how they
// are divided into batches below.
files_overlap_ = ComputeFilesOverlap(files_to_ingest_);
if (atomic_replace_range.has_value()) {
atomic_replace_range_.emplace();
@@ -159,21 +165,35 @@ Status ExternalSstFileIngestionJob::Prepare(
// It is unsafe to assume application had sync the file and file
// directory before ingest the file. For integrity of RocksDB we need
// to sync the file.
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
Status s = fs_->SyncFile(path_inside_db, env_options_, IOOptions(),
db_options_.use_fsync, nullptr);
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncIngestedFile");
TEST_SYNC_POINT_CALLBACK(
"ExternalSstFileIngestionJob::CheckSyncReturnCode", &s);
// TODO(xingbo), We should in general be moving away from production
// uses of ReuseWritableFile (except explicitly for WAL recycling),
// ReopenWritableFile, and NewRandomRWFile. We should create a
// FileSystem::SyncFile/FsyncFile API that by default does the
// re-open+sync+close combo but can (a) be reused easily, and (b) be
// overridden to do that more cleanly, e.g. in EncryptedEnv.
// https://github.com/facebook/rocksdb/issues/13741
std::unique_ptr<FSWritableFile> file_to_sync;
Status s = fs_->ReopenWritableFile(path_inside_db, env_options_,
&file_to_sync, nullptr);
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Prepare:Reopen",
&s);
// Some file systems (especially remote/distributed) don't support
// explicitly syncing the file and don't require it. Ignore the
// NotSupported error in that case.
// reopening a file for writing and don't require reopening and
// syncing the file. Ignore the NotSupported error in that case.
if (!s.IsNotSupported()) {
status = s;
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to sync ingested file %s: %s",
path_inside_db.c_str(), status.ToString().c_str());
if (status.ok()) {
TEST_SYNC_POINT(
"ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
status = SyncIngestedFile(file_to_sync.get());
TEST_SYNC_POINT(
"ExternalSstFileIngestionJob::AfterSyncIngestedFile");
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to sync ingested file %s: %s",
path_inside_db.c_str(), status.ToString().c_str());
}
}
}
} else if (status.IsNotSupported() &&
@@ -427,60 +447,6 @@ void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
}
}
bool ExternalSstFileIngestionJob::ComputeFilesOverlap(
const autovector<IngestedFileInfo>& files) const {
const size_t num_files = files.size();
if (num_files <= 1) {
return false;
}
// Verify whether the files have overlapping ranges by sorting copies of the
// file ranges and checking adjacent pairs.
autovector<const IngestedFileInfo*> sorted_files;
for (size_t i = 0; i < num_files; i++) {
sorted_files.push_back(&files[i]);
}
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
for (size_t i = 0; i + 1 < num_files; i++) {
if (file_range_checker_.Overlaps(*sorted_files[i], *sorted_files[i + 1],
/* known_sorted= */ true)) {
return true;
}
}
return false;
}
Status ExternalSstFileIngestionJob::MergeForSameColumnFamily(
ExternalSstFileIngestionJob* other) {
assert(other != nullptr);
assert(other != this);
assert(cfd_ == other->cfd_);
if (atomic_replace_range_.has_value() ||
other->atomic_replace_range_.has_value()) {
return Status::NotSupported(
"cannot merge file ingestion handles for the same column family when "
"atomic_replace_range is used");
}
if (!(ingestion_options_ == other->ingestion_options_)) {
return Status::InvalidArgument(
"file ingestion handles for the same column family must be prepared "
"with the same IngestExternalFileOptions");
}
// Append the other job's prepared files after this job's so that, for any
// overlapping keys, the other job's data wins via a higher assigned sequence
// number -- the same semantics as passing all the files to a single ingestion
// call in this order. Recompute overlap and rebuild the batches over the
// union.
for (IngestedFileInfo& file : other->files_to_ingest_) {
files_to_ingest_.push_back(std::move(file));
}
other->files_to_ingest_.clear();
other->file_batches_to_ingest_.clear();
files_overlap_ = ComputeFilesOverlap(files_to_ingest_);
file_batches_to_ingest_.clear();
DivideInputFilesIntoBatches();
return Status::OK();
}
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
SuperVersion* super_version) {
Status status;
@@ -750,9 +716,6 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
tail_size, file->user_defined_timestamps_persisted, "", "");
f_metadata.temperature = file->file_temperature;
f_metadata.marked_for_compaction = marked_for_compaction;
f_metadata.skip_index_and_filter_blocks_prefetch =
!file->prefetch_lmax_index_and_filter_blocks &&
file->picked_level == cfd_->NumberLevels() - 1;
// 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.
@@ -1007,14 +970,18 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
}
Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
const std::string& external_file, const TableProperties& props,
IngestedFileInfo* file_to_ingest) {
const auto& uprops = props.user_collected_properties;
const std::string& external_file, uint64_t new_file_number,
SuperVersion* sv, IngestedFileInfo* file_to_ingest,
std::unique_ptr<TableReader>* table_reader) {
// Get the external file properties
auto props = table_reader->get()->GetTableProperties();
assert(props.get());
const auto& uprops = props->user_collected_properties;
// Get table version
auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
if (version_iter == uprops.end()) {
assert(!SstFileWriter::CreatedBySstFileWriter(props));
assert(!SstFileWriter::CreatedBySstFileWriter(*props));
if (!ingestion_options_.allow_db_generated_files) {
return Status::Corruption("External file version not found");
} else {
@@ -1023,7 +990,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
file_to_ingest->version = 0;
}
} else {
assert(SstFileWriter::CreatedBySstFileWriter(props));
assert(SstFileWriter::CreatedBySstFileWriter(*props));
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
}
@@ -1037,17 +1004,12 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
// Set the global sequence number
file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
file_to_ingest->global_seqno_offset =
static_cast<size_t>(props.external_sst_file_global_seqno_offset);
// The on-disk offset is only needed if we will write the global seqno back
// into the file (write_global_seqno). The metadata fast-path does not open
// the file, and its in-memory table properties do not carry the offset
// (it is only computed while reading the file back); that is fine as long
// as write_global_seqno is not requested.
if (ingestion_options_.write_global_seqno &&
file_to_ingest->global_seqno_offset == 0) {
if (props->external_sst_file_global_seqno_offset == 0) {
file_to_ingest->global_seqno_offset = 0;
return Status::Corruption("Was not able to find file global seqno field");
}
file_to_ingest->global_seqno_offset =
static_cast<size_t>(props->external_sst_file_global_seqno_offset);
} else if (file_to_ingest->version == 1) {
// SST file V1 should not have global seqno field
assert(seqno_iter == uprops.end());
@@ -1068,20 +1030,23 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
" is not supported");
}
file_to_ingest->cf_id = static_cast<uint32_t>(props.column_family_id);
file_to_ingest->table_properties = props;
file_to_ingest->cf_id = static_cast<uint32_t>(props->column_family_id);
// This assignment works fine even though `table_reader` may later be reset,
// since that will not affect how table properties are parsed, and this
// assignment is making a copy.
file_to_ingest->table_properties = *props;
// Get number of entries in table
file_to_ingest->num_entries = props.num_entries;
file_to_ingest->num_range_deletions = props.num_range_deletions;
file_to_ingest->num_entries = props->num_entries;
file_to_ingest->num_range_deletions = props->num_range_deletions;
// Validate table properties related to comparator name and user defined
// timestamps persisted flag.
file_to_ingest->user_defined_timestamps_persisted =
static_cast<bool>(props.user_defined_timestamps_persisted);
static_cast<bool>(props->user_defined_timestamps_persisted);
bool mark_sst_file_has_no_udt = false;
Status s = ValidateUserDefinedTimestampsOptions(
cfd_->user_comparator(), props.comparator_name,
cfd_->user_comparator(), props->comparator_name,
cfd_->ioptions().persist_user_defined_timestamps,
file_to_ingest->user_defined_timestamps_persisted,
&mark_sst_file_has_no_udt);
@@ -1089,9 +1054,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
// A column family that enables user-defined timestamps in Memtable only
// feature can also ingest external files created by a setting that disables
// user-defined timestamps. In that case, we need to re-mark the
// user_defined_timestamps_persisted flag for the file. The open-and-scan
// caller is then responsible for reopening its `TableReader` with the
// updated flag.
// user_defined_timestamps_persisted flag for the file.
file_to_ingest->user_defined_timestamps_persisted = false;
} else if (!s.ok()) {
ROCKS_LOG_WARN(
@@ -1101,84 +1064,22 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
return s;
}
// `TableReader` is initialized with `user_defined_timestamps_persisted` flag
// to be true. If its value changed to false after this sanity check, we
// need to reset the `TableReader`.
if (ucmp_->timestamp_size() > 0 &&
!file_to_ingest->user_defined_timestamps_persisted) {
s = ResetTableReader(external_file, new_file_number,
file_to_ingest->user_defined_timestamps_persisted, sv,
file_to_ingest, table_reader);
}
return s;
}
Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFileInfo(
const std::string& external_file,
const PreparedFileInfo& prepared_file_info,
IngestedFileInfo* file_to_ingest) {
// Boundaries, size, and table properties were obtained without opening the
// file (e.g. produced by SstFileWriter::Finish). We reuse them directly.
file_to_ingest->file_size = prepared_file_info.file_size;
Status status = SanityCheckTableProperties(
external_file, prepared_file_info.table_properties, file_to_ingest);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
"Failed to sanity check table properties for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
const size_t ts_sz = ucmp_->timestamp_size();
if (ts_sz > 0 && !file_to_ingest->user_defined_timestamps_persisted) {
auto pad_timestamp = [ts_sz](std::string* result, const Slice& key) {
assert(result->empty());
if (ExtractValueType(key) == kTypeRangeDeletion) {
PadInternalKeyWithMaxTimestamp(result, key, ts_sz);
} else {
PadInternalKeyWithMinTimestamp(result, key, ts_sz);
}
};
pad_timestamp(file_to_ingest->smallest_internal_key.rep(),
prepared_file_info.smallest.Encode());
pad_timestamp(file_to_ingest->largest_internal_key.rep(),
prepared_file_info.largest.Encode());
} else {
file_to_ingest->smallest_internal_key = prepared_file_info.smallest;
file_to_ingest->largest_internal_key = prepared_file_info.largest;
}
if (ingestion_options_.allow_db_generated_files) {
// Sequence numbers are preserved (not reassigned), so the bounds must be
// known before we skip the GetSeqnoBoundaryForFile scan.
if (!file_to_ingest->table_properties.HasKeyLargestSeqno()) {
return Status::Corruption("Unknown largest seqno for db generated file.");
}
file_to_ingest->largest_seqno =
file_to_ingest->table_properties.key_largest_seqno;
if (file_to_ingest->largest_seqno == 0) {
file_to_ingest->smallest_seqno = 0;
} else {
if (!file_to_ingest->table_properties.HasKeySmallestSeqno()) {
return Status::Corruption(
"Unknown smallest seqno for db generated file.");
}
file_to_ingest->smallest_seqno =
file_to_ingest->table_properties.key_smallest_seqno;
}
} else {
// Normal ingestion reassigns a global sequence number later, so the file's
// keys must currently be at seqno 0 (mirror of the open-and-scan check).
SequenceNumber largest_seqno =
file_to_ingest->table_properties.key_largest_seqno;
// UINT64_MAX means unknown and the file is generated before table property
// `key_largest_seqno` is introduced.
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
return Status::Corruption(
"External file has non zero largest sequence number " +
std::to_string(largest_seqno));
}
}
return Status::OK();
}
Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
const std::string& external_file, uint64_t new_file_number,
IngestedFileInfo* file_to_ingest, SuperVersion* sv,
std::unique_ptr<TableReader>* out_table_reader) {
TEST_SYNC_POINT("ExternalSstFileIngestionJob::GetIngestedFileInfo:ReadPath");
IngestedFileInfo* file_to_ingest, SuperVersion* sv) {
file_to_ingest->external_file_path = external_file;
// Get external file size
Status status = fs_->GetFileSize(external_file, IOOptions(),
@@ -1190,11 +1091,15 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
return status;
}
// Create TableReader for external file.
// Assign FD with number
file_to_ingest->fd =
FileDescriptor(new_file_number, 0, file_to_ingest->file_size);
// Create TableReader for external file
std::unique_ptr<TableReader> table_reader;
// Initially create the `TableReader` with flag
// `user_defined_timestamps_persisted` to be true since that's the most
// common case
// `user_defined_timestamps_persisted` to be true since that's the most common
// case
status = ResetTableReader(external_file, new_file_number,
/*user_defined_timestamps_persisted=*/true, sv,
file_to_ingest, &table_reader);
@@ -1205,8 +1110,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
return status;
}
status = SanityCheckTableProperties(
external_file, *table_reader->GetTableProperties(), file_to_ingest);
status = SanityCheckTableProperties(external_file, new_file_number, sv,
file_to_ingest, &table_reader);
if (!status.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log,
@@ -1215,23 +1120,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
return status;
}
// The `TableReader` above was opened with `user_defined_timestamps_persisted`
// assumed true. If the sanity check determined the file has no persisted
// timestamps (UDT-in-Memtable-only feature), reopen it with the corrected
// flag so keys are parsed properly by the scan below.
if (ucmp_->timestamp_size() > 0 &&
!file_to_ingest->user_defined_timestamps_persisted) {
status = ResetTableReader(external_file, new_file_number,
file_to_ingest->user_defined_timestamps_persisted,
sv, file_to_ingest, &table_reader);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to reset table reader for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
}
const bool allow_data_in_errors = db_options_.allow_data_in_errors;
ParsedInternalKey key;
if (ingestion_options_.allow_db_generated_files) {
@@ -1253,8 +1141,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
} else {
SequenceNumber largest_seqno =
table_reader.get()->GetTableProperties()->key_largest_seqno;
// UINT64_MAX means unknown and the file is generated before table
// property `key_largest_seqno` is introduced.
// UINT64_MAX means unknown and the file is generated before table property
// `key_largest_seqno` is introduced.
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
return Status::Corruption(
"External file has non zero largest sequence number " +
@@ -1262,6 +1150,24 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
}
}
if (ingestion_options_.verify_checksums_before_ingest) {
// If customized readahead size is needed, we can pass a user option
// all the way to here. Right now we just rely on the default readahead
// to keep things simple.
// TODO: plumb Env::IOActivity, Env::IOPriority
ReadOptions ro;
ro.readahead_size = ingestion_options_.verify_checksums_readahead_size;
ro.fill_cache = ingestion_options_.fill_cache;
status = table_reader->VerifyChecksum(
ro, TableReaderCaller::kExternalSSTIngestion);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to verify checksum for table reader: %s",
status.ToString().c_str());
return status;
}
}
// TODO: plumb Env::IOActivity, Env::IOPriority
ReadOptions ro;
ro.fill_cache = ingestion_options_.fill_cache;
@@ -1341,22 +1247,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
"External file has a range deletion with non zero sequence "
"number.");
}
#ifndef NDEBUG
// To keep aligned with the fast path that expects range deletion keys to
// always use max ts.
const size_t ts_sz = ucmp_->timestamp_size();
if (ts_sz > 0) {
const std::string max_ts(ts_sz, '\xff');
assert(key.user_key.size() >= ts_sz);
assert(ucmp_->CompareTimestamp(
ExtractTimestampFromUserKey(key.user_key, ts_sz), max_ts) ==
0);
assert(range_del_iter->value().size() >= ts_sz);
assert(ucmp_->CompareTimestamp(
ExtractTimestampFromUserKey(range_del_iter->value(), ts_sz),
max_ts) == 0);
}
#endif
RangeTombstone tombstone(key, range_del_iter->value());
file_range_checker_.MaybeUpdateRange(tombstone.SerializeKey(),
tombstone.SerializeEndKey(),
@@ -1364,75 +1254,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfoFromFile(
}
}
*out_table_reader = std::move(table_reader);
return Status::OK();
}
Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
const std::string& external_file, uint64_t new_file_number,
const PreparedFileInfo* prepared_file_info,
IngestedFileInfo* file_to_ingest, SuperVersion* sv) {
file_to_ingest->external_file_path = external_file;
std::unique_ptr<TableReader> table_reader;
Status status =
prepared_file_info != nullptr
? GetIngestedFileInfoFromFileInfo(external_file, *prepared_file_info,
file_to_ingest)
: GetIngestedFileInfoFromFile(external_file, new_file_number,
file_to_ingest, sv, &table_reader);
if (!status.ok()) {
return status;
}
assert(file_to_ingest->file_size > 0);
assert(!file_to_ingest->unset());
assert(file_to_ingest->table_properties.num_entries > 0 ||
file_to_ingest->table_properties.num_range_deletions > 0);
if (ingestion_options_.allow_db_generated_files) {
// These files keep their original sequence numbers (derived, not
// reassigned), so the bounds must be valid here.
assert(file_to_ingest->smallest_seqno <= file_to_ingest->largest_seqno);
assert(file_to_ingest->largest_seqno < kMaxSequenceNumber);
}
// Verify the file checksum if requested. The open-and-scan path already has a
// `TableReader` open; the fast-path opens one here only when verification is
// requested -- otherwise it performs no file I/O.
if (ingestion_options_.verify_checksums_before_ingest) {
if (table_reader == nullptr) {
status =
ResetTableReader(external_file, new_file_number,
file_to_ingest->user_defined_timestamps_persisted,
sv, file_to_ingest, &table_reader);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to reset table reader for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
}
// If customized readahead size is needed, we can pass a user option all the
// way to here. Right now we just rely on the default readahead to keep
// things simple.
// TODO: plumb Env::IOActivity, Env::IOPriority
ReadOptions ro;
ro.readahead_size = ingestion_options_.verify_checksums_readahead_size;
ro.fill_cache = ingestion_options_.fill_cache;
status = table_reader->VerifyChecksum(
ro, TableReaderCaller::kExternalSSTIngestion);
if (!status.ok()) {
ROCKS_LOG_WARN(db_options_.info_log,
"Failed to verify checksum for external file %s: %s",
external_file.c_str(), status.ToString().c_str());
return status;
}
}
// Assign FD with number.
file_to_ingest->fd =
FileDescriptor(new_file_number, 0, file_to_ingest->file_size);
const size_t ts_sz = ucmp_->timestamp_size();
Slice smallest = file_to_ingest->smallest_internal_key.user_key();
Slice largest = file_to_ingest->largest_internal_key.user_key();
+5 -47
View File
@@ -181,9 +181,6 @@ struct IngestedFileInfo : public KeyRangeInfo {
// setting.
bool user_defined_timestamps_persisted = true;
// Whether Lmax commit-time table opening should prefetch index/filter blocks.
bool prefetch_lmax_index_and_filter_blocks = true;
SequenceNumber largest_seqno = kMaxSequenceNumber;
SequenceNumber smallest_seqno = kMaxSequenceNumber;
};
@@ -251,7 +248,6 @@ class ExternalSstFileIngestionJob {
Status Prepare(const std::vector<std::string>& external_files_paths,
const std::vector<std::string>& files_checksums,
const std::vector<std::string>& files_checksum_func_names,
const std::vector<const PreparedFileInfo*>& file_infos,
const std::optional<RangeOpt>& atomic_replace_range,
const Temperature& file_temperature, uint64_t next_file_number,
SuperVersion* sv);
@@ -305,20 +301,6 @@ class ExternalSstFileIngestionJob {
return max_assigned_seqno_;
}
// Max threads requested for opening this job's files during commit, from the
// per-CF IngestExternalFileOptions.
int file_opening_threads() const {
return ingestion_options_.file_opening_threads;
}
// Merge another already-Prepare()d job for the SAME column family into this
// one so both sets of files are committed by a single Run(). The other job's
// files are appended after this job's, so for any overlapping keys the other
// job's data wins via a higher assigned sequence number -- the same semantics
// as passing all the files to a single ingestion call in this order. The
// other job is left empty.
Status MergeForSameColumnFamily(ExternalSstFileIngestionJob* other);
private:
Status ResetTableReader(const std::string& external_file,
uint64_t new_file_number,
@@ -333,37 +315,17 @@ class ExternalSstFileIngestionJob {
// different options. For example: when external file does not contain
// timestamps while column family enables UDT in Memtables only feature.
Status SanityCheckTableProperties(const std::string& external_file,
const TableProperties& props,
IngestedFileInfo* file_to_ingest);
uint64_t new_file_number, SuperVersion* sv,
IngestedFileInfo* file_to_ingest,
std::unique_ptr<TableReader>* table_reader);
// Open the external file and populate `file_to_ingest` with all the
// external information we need to ingest this file. When
// `prepared_file_info` is non-null, its caller-supplied metadata is reused
// instead of opening and scanning the file.
// external information we need to ingest this file.
Status GetIngestedFileInfo(const std::string& external_file,
uint64_t new_file_number,
const PreparedFileInfo* prepared_file_info,
IngestedFileInfo* file_to_ingest,
SuperVersion* sv);
// Acquire the per-file metadata from the caller-supplied opaque
// `PreparedFileInfo` (produced by SstFileWriter::Finish) instead of opening
// the file.
Status GetIngestedFileInfoFromFileInfo(
const std::string& external_file,
const PreparedFileInfo& prepared_file_info,
IngestedFileInfo* file_to_ingest);
// Acquire the per-file metadata by opening the external file and scanning it
// (table properties, sequence number bounds, and boundary keys including any
// range-tombstone extensions). Used when no file_info is available. The
// opened `TableReader` is returned via `*table_reader` so the caller can
// reuse it (e.g. to verify the file checksum) without re-opening the file.
Status GetIngestedFileInfoFromFile(
const std::string& external_file, uint64_t new_file_number,
IngestedFileInfo* file_to_ingest, SuperVersion* sv,
std::unique_ptr<TableReader>* out_table_reader);
// If the input files' key range overlaps themselves, this function divides
// them in the user specified order into multiple batches. Where the files
// within a batch do not overlap with each other, but key range could overlap
@@ -372,10 +334,6 @@ class ExternalSstFileIngestionJob {
// make one batch.
void DivideInputFilesIntoBatches();
// Returns whether any two files in `files` have overlapping key ranges, by
// sorting the file ranges and checking adjacent pairs.
bool ComputeFilesOverlap(const autovector<IngestedFileInfo>& files) const;
// Assign level for the files in one batch. The files within one batch are not
// overlapping, and we assign level to each file one after another.
// If `prev_batch_uppermost_level` is specified, all files in this batch will
@@ -449,7 +407,7 @@ class ExternalSstFileIngestionJob {
SnapshotList* db_snapshots_;
autovector<IngestedFileInfo> files_to_ingest_;
std::vector<FileBatchInfo> file_batches_to_ingest_;
const IngestExternalFileOptions ingestion_options_;
const IngestExternalFileOptions& ingestion_options_;
std::optional<KeyRangeInfo> atomic_replace_range_;
Directories* directories_;
EventLogger* event_logger_;
+6 -733
View File
@@ -5,7 +5,6 @@
#include <table/block_based/block_based_table_factory.h>
#include <atomic>
#include <functional>
#include <memory>
#include <sstream>
@@ -18,7 +17,6 @@
#include "port/stack_trace.h"
#include "rocksdb/sst_file_reader.h"
#include "rocksdb/sst_file_writer.h"
#include "table/prepared_file_info.h"
#include "test_util/testutil.h"
#include "util/random.h"
#include "util/thread_guard.h"
@@ -94,17 +92,10 @@ class ExternSSTFileLinkFailFallbackTest
class ExternalSSTFileTest
: public ExternalSSTFileTestBase,
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
public:
ExternalSSTFileTest() = default;
void SetUp() override {
const auto* info = ::testing::UnitTest::GetInstance()->current_test_info();
if (info != nullptr && info->value_param() != nullptr) {
two_phase_ingest_ = std::get<2>(GetParam());
}
}
Status GenerateOneExternalFile(
const Options& options, ColumnFamilyHandle* cfh,
std::vector<std::pair<std::string, std::string>>& data, int file_id,
@@ -161,8 +152,7 @@ class ExternalSSTFileTest
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
bool sort_data = false,
std::map<std::string, std::string>* true_data = nullptr,
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false,
bool ingest_with_file_info = false) {
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false) {
// Generate a file id if not provided
if (file_id == -1) {
file_id = last_file_id_ + 1;
@@ -198,12 +188,7 @@ class ExternalSSTFileTest
return s;
}
}
ExternalSstFileInfo file_info;
if (ingest_with_file_info) {
s = sst_file_writer.Finish(&file_info);
} else {
s = sst_file_writer.Finish();
}
s = sst_file_writer.Finish();
if (s.ok()) {
IngestExternalFileOptions ifo;
@@ -212,21 +197,7 @@ class ExternalSSTFileTest
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
ifo.ingest_behind = ingest_behind;
ifo.fill_cache = fill_cache;
if (two_phase_ingest_) {
std::unique_ptr<FileIngestionHandle> handle;
s = db_->PrepareFileIngestion(cfh ? cfh : db_->DefaultColumnFamily(),
{file_path}, ifo, &handle);
if (s.ok()) {
s = db_->CommitFileIngestionHandle(std::move(handle));
}
} else if (ingest_with_file_info) {
IngestExternalFileArg arg;
arg.column_family = cfh ? cfh : db_->DefaultColumnFamily();
arg.external_files = {file_path};
arg.file_infos = {file_info.prepared_file_info.get()};
arg.options = ifo;
s = db_->IngestExternalFiles({arg});
} else if (cfh) {
if (cfh) {
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
} else {
s = db_->IngestExternalFile({file_path}, ifo);
@@ -272,14 +243,6 @@ class ExternalSSTFileTest
args[i].external_files.push_back(external_file_path);
args[i].options = ifos[i];
}
if (two_phase_ingest_) {
std::unique_ptr<FileIngestionHandle> handle;
Status s = db_->PrepareFileIngestion(args, &handle);
if (!s.ok()) {
return s;
}
return db_->CommitFileIngestionHandle(std::move(handle));
}
return db_->IngestExternalFiles(args);
}
@@ -331,619 +294,10 @@ class ExternalSSTFileTest
return db_->IngestExternalFile(files, opts);
}
// Writes `data` (sorted by user key) into a new external SST and returns its
// path in *file_path WITHOUT ingesting it, so a test can drive the two-phase
// PrepareFileIngestion()/CommitFileIngestion() API directly.
Status GenerateExternalFileOnly(
const Options& options,
std::vector<std::pair<std::string, std::string>> data,
std::string* file_path) {
std::sort(data.begin(), data.end(),
[&](const std::pair<std::string, std::string>& e1,
const std::pair<std::string, std::string>& e2) {
return options.comparator->Compare(e1.first, e2.first) < 0;
});
*file_path = sst_files_dir_ + "only_" + std::to_string(++last_file_id_);
SstFileWriter sst_file_writer(EnvOptions(), options);
Status s = sst_file_writer.Open(*file_path);
if (!s.ok()) {
return s;
}
for (const auto& entry : data) {
s = sst_file_writer.Put(entry.first, entry.second);
if (!s.ok()) {
sst_file_writer.Finish().PermitUncheckedError();
return s;
}
}
return sst_file_writer.Finish();
}
protected:
int last_file_id_ = 0;
bool two_phase_ingest_ = false;
};
TEST_F(ExternalSSTFileTest, IngestionTimingHistogram) {
Options options = CurrentOptions();
options.statistics = CreateDBStatistics();
options.statistics->set_stats_level(StatsLevel::kAll); // enable timers
DestroyAndReopen(options);
ASSERT_OK(GenerateAndAddExternalFile(
options, {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}, /*file_id=*/-1,
/*allow_global_seqno=*/true, /*write_global_seqno=*/false,
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
/*sort_data=*/true));
auto hist = [&](Histograms h) {
HistogramData hd;
options.statistics->histogramData(h, &hd);
return hd;
};
// One sample per call (not per CF) in each phase histogram.
HistogramData prepare_hd = hist(INGEST_EXTERNAL_FILE_PREPARE_TIME);
HistogramData run_hd = hist(INGEST_EXTERNAL_FILE_RUN_TIME);
ASSERT_EQ(1, prepare_hd.count);
ASSERT_EQ(1, run_hd.count);
ASSERT_GT(prepare_hd.max, 0.0);
ASSERT_GT(run_hd.max, 0.0);
// A second call adds exactly one more sample to each histogram.
ASSERT_OK(GenerateAndAddExternalFile(
options, {{"k4", "v4"}, {"k5", "v5"}}, /*file_id=*/-1,
/*allow_global_seqno=*/true, /*write_global_seqno=*/false,
/*verify_checksums_before_ingest=*/true, /*ingest_behind=*/false,
/*sort_data=*/true));
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_PREPARE_TIME).count);
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_RUN_TIME).count);
// A failed ingestion records nothing (latency is logged only on success).
ASSERT_NOK(db_->IngestExternalFile({"/path/does/not/exist.sst"},
IngestExternalFileOptions()));
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_PREPARE_TIME).count);
ASSERT_EQ(2, hist(INGEST_EXTERNAL_FILE_RUN_TIME).count);
}
TEST_F(ExternalSSTFileTest, PrepareThenCommit) {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::string file_path;
ASSERT_OK(GenerateExternalFileOnly(
options, {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}}, &file_path));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {file_path};
std::unique_ptr<FileIngestionHandle> handle;
ASSERT_OK(db_->PrepareFileIngestion({arg}, &handle));
ASSERT_TRUE(handle != nullptr);
// The prepared file is staged in the DB but not yet visible.
ASSERT_EQ("NOT_FOUND", Get("k1"));
ASSERT_OK(db_->CommitFileIngestionHandle(std::move(handle)));
ASSERT_EQ("v1", Get("k1"));
ASSERT_EQ("v2", Get("k2"));
ASSERT_EQ("v3", Get("k3"));
}
TEST_F(ExternalSSTFileTest, ParallelFileOpenWithFileOpeningThreads) {
// Ingest many non-overlapping files with file_opening_threads > 1 so commit
// opens table readers with multiple threads. max_open_files == -1 makes
// commit open every new file.
Options options = CurrentOptions();
options.max_open_files = -1;
DestroyAndReopen(options);
constexpr int kNumFiles = 8;
constexpr int kKeysPerFile = 50;
std::vector<std::string> files;
std::map<std::string, std::string> true_data;
for (int f = 0; f < kNumFiles; f++) {
std::vector<std::pair<std::string, std::string>> data;
for (int k = 0; k < kKeysPerFile; k++) {
const int key = f * kKeysPerFile + k;
const std::string value = "val" + std::to_string(key);
data.emplace_back(Key(key), value);
true_data[Key(key)] = value;
}
std::string file_path;
ASSERT_OK(GenerateExternalFileOnly(options, data, &file_path));
files.push_back(file_path);
}
IngestExternalFileOptions ifo;
ifo.file_opening_threads = 4;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
for (const auto& [key, value] : true_data) {
ASSERT_EQ(value, Get(key));
}
}
TEST_F(ExternalSSTFileTest, LmaxPrefetchSkipDoesNotDisableL0Prefetch) {
LRUCacheOptions co;
co.capacity = 32 << 20;
std::shared_ptr<Cache> cache = NewLRUCache(co);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.cache_index_and_filter_blocks = true;
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
Options options = CurrentOptions();
options.disable_auto_compactions = true;
options.max_open_files = -1;
options.num_levels = 2;
options.optimize_filters_for_hits = false;
options.statistics = CreateDBStatistics();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
DestroyAndReopen(options);
const auto write_file = [&](const std::string& file_path,
const std::string& value,
ExternalSstFileInfo* file_info) {
SstFileWriter writer(EnvOptions(), options);
ASSERT_OK(writer.Open(file_path));
ASSERT_OK(writer.Put(Key(10), value));
ASSERT_OK(writer.Finish(file_info));
};
const auto ingest_file = [&](const std::string& file_path,
const ExternalSstFileInfo& file_info,
bool fail_if_not_bottommost_level) {
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {file_path};
arg.file_infos = {file_info.prepared_file_info.get()};
arg.options.fail_if_not_bottommost_level = fail_if_not_bottommost_level;
arg.options.verify_checksums_before_ingest = false;
arg.options.prefetch_lmax_index_and_filter_blocks = false;
ASSERT_OK(db_->IngestExternalFiles({arg}));
};
const std::string lmax_file_path = sst_files_dir_ + "lazy_lmax.sst";
ExternalSstFileInfo lmax_file_info;
write_file(lmax_file_path, "v10", &lmax_file_info);
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_INDEX_ADD));
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_FILTER_ADD));
ingest_file(lmax_file_path, lmax_file_info,
true /* fail_if_not_bottommost_level */);
ASSERT_EQ("0,1", FilesPerLevel());
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_INDEX_ADD));
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_FILTER_ADD));
const std::string l0_file_path = sst_files_dir_ + "l0.sst";
ExternalSstFileInfo l0_file_info;
write_file(l0_file_path, "v11", &l0_file_info);
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_INDEX_ADD));
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_FILTER_ADD));
ingest_file(l0_file_path, l0_file_info,
false /* fail_if_not_bottommost_level */);
ASSERT_EQ("1,1", FilesPerLevel());
EXPECT_GT(options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_INDEX_ADD),
0);
EXPECT_GT(options.statistics->getAndResetTickerCount(
Tickers::BLOCK_CACHE_FILTER_ADD),
0);
}
TEST_F(ExternalSSTFileTest, AbortPreparedIngestion) {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::string file_path;
ASSERT_OK(GenerateExternalFileOnly(options, {{"x", "1"}}, &file_path));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {file_path};
std::unique_ptr<FileIngestionHandle> handle;
ASSERT_OK(db_->PrepareFileIngestion({arg}, &handle));
ASSERT_OK(handle->Abort());
ASSERT_EQ("NOT_FOUND", Get("x"));
// Committing an already-aborted handle returns an error.
ASSERT_TRUE(
db_->CommitFileIngestionHandle(std::move(handle)).IsInvalidArgument());
// The DB remains healthy after an abort: a fresh prepare + commit works.
std::unique_ptr<FileIngestionHandle> handle2;
ASSERT_OK(db_->PrepareFileIngestion({arg}, &handle2));
ASSERT_OK(db_->CommitFileIngestionHandle(std::move(handle2)));
ASSERT_EQ("1", Get("x"));
}
TEST_F(ExternalSSTFileTest, MultiHandleAtomicCommit) {
Options options = CurrentOptions();
CreateAndReopenWithCF({"cf1", "cf2", "cf3"}, options);
// Two handles whose column families overlap on cf2:
// handle 1 -> cf1, cf2 handle 2 -> cf2, cf3
// Committing them together installs cf1 and cf3 each from one handle, while
// cf2 receives files from BOTH handles, merged into a single per-CF commit.
std::string f_cf1;
std::string f_cf2_h1;
std::string f_cf2_h2;
std::string f_cf3;
ASSERT_OK(GenerateExternalFileOnly(options, {{"a", "1"}}, &f_cf1));
// The cf2 files share key "b": handle 2 is committed later, so its value must
// win via a higher assigned sequence number.
ASSERT_OK(GenerateExternalFileOnly(options, {{"b", "h1"}, {"b1", "10"}},
&f_cf2_h1));
ASSERT_OK(GenerateExternalFileOnly(options, {{"b", "h2"}, {"b2", "20"}},
&f_cf2_h2));
ASSERT_OK(GenerateExternalFileOnly(options, {{"c", "3"}}, &f_cf3));
std::vector<IngestExternalFileArg> args1(2);
args1[0].column_family = handles_[1]; // cf1
args1[0].external_files = {f_cf1};
args1[1].column_family = handles_[2]; // cf2
args1[1].external_files = {f_cf2_h1};
std::vector<IngestExternalFileArg> args2(2);
args2[0].column_family =
handles_[2]; // cf2 -> merges with handle 1's cf2 job
args2[0].external_files = {f_cf2_h2};
args2[1].column_family = handles_[3]; // cf3
args2[1].external_files = {f_cf3};
std::unique_ptr<FileIngestionHandle> h1;
std::unique_ptr<FileIngestionHandle> h2;
ASSERT_OK(db_->PrepareFileIngestion(args1, &h1));
ASSERT_OK(db_->PrepareFileIngestion(args2, &h2));
ASSERT_EQ("NOT_FOUND", Get(1, "a"));
ASSERT_EQ("NOT_FOUND", Get(2, "b1"));
ASSERT_EQ("NOT_FOUND", Get(3, "c"));
// Commit both handles atomically.
std::vector<std::unique_ptr<FileIngestionHandle>> batch;
batch.push_back(std::move(h1));
batch.push_back(std::move(h2));
ASSERT_OK(db_->CommitFileIngestionHandles(std::move(batch)));
ASSERT_EQ("1", Get(1, "a"));
ASSERT_EQ("h2", Get(2, "b")); // overlapping key: later handle (h2) wins
ASSERT_EQ("10", Get(2, "b1")); // cf2 from handle 1
ASSERT_EQ("20", Get(2, "b2")); // cf2 from handle 2 (merged)
ASSERT_EQ("3", Get(3, "c"));
}
TEST_F(ExternalSSTFileTest, MultiHandleSameColumnFamilyIncompatibleOptions) {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::string f1;
std::string f2;
ASSERT_OK(GenerateExternalFileOnly(options, {{"a", "1"}}, &f1));
ASSERT_OK(GenerateExternalFileOnly(options, {{"b", "2"}}, &f2));
// Same column family but with options the merged Run() would consult set
// differently across the two handles.
IngestExternalFileOptions opts_fill;
IngestExternalFileOptions opts_no_fill;
opts_no_fill.fill_cache = false;
std::unique_ptr<FileIngestionHandle> h1;
std::unique_ptr<FileIngestionHandle> h2;
ASSERT_OK(db_->PrepareFileIngestion(db_->DefaultColumnFamily(), {f1},
opts_fill, &h1));
ASSERT_OK(db_->PrepareFileIngestion(db_->DefaultColumnFamily(), {f2},
opts_no_fill, &h2));
std::vector<std::unique_ptr<FileIngestionHandle>> batch;
batch.push_back(std::move(h1));
batch.push_back(std::move(h2));
ASSERT_TRUE(
db_->CommitFileIngestionHandles(std::move(batch)).IsInvalidArgument());
// The rejected commit rolled both handles back; the CF is still usable.
ASSERT_EQ("NOT_FOUND", Get("a"));
ASSERT_EQ("NOT_FOUND", Get("b"));
std::unique_ptr<FileIngestionHandle> h3;
ASSERT_OK(db_->PrepareFileIngestion(db_->DefaultColumnFamily(), {f1},
IngestExternalFileOptions(), &h3));
ASSERT_OK(db_->CommitFileIngestionHandle(std::move(h3)));
ASSERT_EQ("1", Get("a"));
}
TEST_F(ExternalSSTFileTest, PrepareFileIngestionValidationErrors) {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::unique_ptr<FileIngestionHandle> handle;
ASSERT_TRUE(
db_->PrepareFileIngestion(std::vector<IngestExternalFileArg>{}, nullptr)
.IsInvalidArgument());
// Empty arg list.
ASSERT_TRUE(
db_->PrepareFileIngestion(std::vector<IngestExternalFileArg>{}, &handle)
.IsInvalidArgument());
ASSERT_TRUE(handle == nullptr);
// move_files and link_files are mutually exclusive.
std::string file_path;
ASSERT_OK(GenerateExternalFileOnly(options, {{"k", "v"}}, &file_path));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {file_path};
arg.options.move_files = true;
arg.options.link_files = true;
ASSERT_TRUE(db_->PrepareFileIngestion({arg}, &handle).IsInvalidArgument());
ASSERT_TRUE(handle == nullptr);
}
// Ingestion that reuses the SstFileWriter's metadata via
// IngestExternalFileArg::file_infos skips the open-and-scan path and still
// produces correct data, including overlapping data that is reassigned a new
// global sequence number.
TEST_F(ExternalSSTFileTest, IngestWithFileInfo) {
Options options = CurrentOptions();
DestroyAndReopen(options);
std::map<std::string, std::string> true_data;
// Count entries into the open-and-scan path so we can assert it is skipped.
std::atomic<int> read_path_count{0};
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::GetIngestedFileInfo:ReadPath",
[&](void*) { read_path_count.fetch_add(1); });
SyncPoint::GetInstance()->EnableProcessing();
// Reuse the writer's metadata, with the normal ingestion options: a global
// sequence number is assigned (allow_db_generated_files is NOT set) and the
// checksum is verified (the fast-path opens the file only to verify it, which
// is not a metadata scan).
auto ingest_fi = [&](std::vector<std::pair<std::string, std::string>> data) {
return GenerateAndAddExternalFile(
options, std::move(data), /*file_id=*/-1, /*allow_global_seqno=*/true,
/*write_global_seqno=*/false, /*verify_checksums_before_ingest=*/true,
/*ingest_behind=*/false, /*sort_data=*/true, &true_data,
/*cfh=*/nullptr, /*fill_cache=*/false, /*ingest_with_file_info=*/true);
};
// Non-overlapping ingest: data reads back and the file is not scanned.
ASSERT_OK(ingest_fi({{Key(1), "a1"}, {Key(2), "a2"}, {Key(3), "a3"}}));
ASSERT_EQ(read_path_count.load(), 0);
// Overlap existing (flushed) data so a non-zero global seqno is assigned and
// the newer values win -- the normal seqno-reassignment path through
// file_infos.
ASSERT_OK(db_->Put(WriteOptions(), Key(2), "old2"));
true_data[Key(2)] = "old2";
ASSERT_OK(Flush());
ASSERT_OK(ingest_fi({{Key(2), "new2"}, {Key(4), "new4"}}));
ASSERT_EQ(read_path_count.load(), 0);
// Control: ingesting the same way but WITHOUT file_infos does enter the scan.
ASSERT_OK(GenerateAndAddExternalFile(
options, {{Key(8), "c8"}}, /*file_id=*/-1, /*allow_global_seqno=*/true,
/*write_global_seqno=*/false, /*verify_checksums_before_ingest=*/true,
/*ingest_behind=*/false, /*sort_data=*/true, &true_data));
ASSERT_GT(read_path_count.load(), 0);
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
size_t kcnt = 0;
VerifyDBFromMap(true_data, &kcnt, false);
}
// Range deletions are honored on the file_infos fast-path: the writer's
// range-del bounds are carried in the metadata, so a DeleteRange covering
// existing keys still shadows them.
TEST_F(ExternalSSTFileTest, IngestWithFileInfoRangeDeletion) {
Options options = CurrentOptions();
DestroyAndReopen(options);
ASSERT_OK(db_->Put(WriteOptions(), Key(20), "live20"));
ASSERT_OK(db_->Put(WriteOptions(), Key(25), "live25"));
ASSERT_OK(Flush());
const std::string f = sst_files_dir_ + "rangedel.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(30), "v30"));
ASSERT_OK(w.DeleteRange(Key(20), Key(26))); // covers keys 20..25
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {file_info.prepared_file_info.get()};
arg.options.allow_global_seqno = true;
ASSERT_OK(db_->IngestExternalFiles({arg}));
ReadOptions ro;
std::string val;
ASSERT_TRUE(db_->Get(ro, Key(20), &val).IsNotFound());
ASSERT_TRUE(db_->Get(ro, Key(25), &val).IsNotFound());
ASSERT_OK(db_->Get(ro, Key(30), &val));
ASSERT_EQ(val, "v30");
}
// verify_checksums_before_ingest is honored on the file_infos fast-path: it
// opens the file just to verify, so corruption is still detected.
TEST_F(ExternalSSTFileTest, IngestWithFileInfoVerifiesChecksum) {
Options options = CurrentOptions();
DestroyAndReopen(options);
const std::string f = sst_files_dir_ + "corrupt.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
for (int k = 0; k < 100; k++) {
ASSERT_OK(w.Put(Key(k), "v" + Key(k)));
}
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
ASSERT_OK(test::CorruptFile(options.env, f, /*offset=*/64,
/*bytes_to_corrupt=*/8));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {file_info.prepared_file_info.get()};
arg.options.verify_checksums_before_ingest = true;
ASSERT_TRUE(db_->IngestExternalFiles({arg}).IsCorruption());
}
// write_global_seqno is incompatible with file_infos: the file is not opened,
// so the seqno cannot be written back into it.
TEST_F(ExternalSSTFileTest, IngestWithFileInfoRejectsWriteGlobalSeqno) {
Options options = CurrentOptions();
DestroyAndReopen(options);
const std::string f = sst_files_dir_ + "wgs.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(1), "v1"));
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {file_info.prepared_file_info.get()};
arg.options.write_global_seqno = true;
ASSERT_TRUE(db_->IngestExternalFiles({arg}).IsInvalidArgument());
}
TEST_F(ExternalSSTFileTest, GetPreparedFileInfoForExternalSstIngestion) {
Options options = CurrentOptions();
DestroyAndReopen(options);
const std::string source_dbname = dbname_ + "_source";
ASSERT_OK(DestroyDB(source_dbname, options));
std::unique_ptr<DB> source_db;
ASSERT_OK(DB::Open(options, source_dbname, &source_db));
ASSERT_OK(source_db->Put(WriteOptions(), Key(10), "v10"));
ASSERT_OK(source_db->Flush(FlushOptions()));
std::vector<LiveFileMetaData> live_meta;
source_db->GetLiveFilesMetaData(&live_meta);
ASSERT_EQ(live_meta.size(), 1);
ASSERT_GT(live_meta[0].largest_seqno, 0);
const std::string file_path =
live_meta[0].directory + "/" + live_meta[0].relative_filename;
std::shared_ptr<const PreparedFileInfo> prepared_file_info;
ASSERT_TRUE(source_db
->GetPreparedFileInfoForExternalSstIngestion(
live_meta[0].relative_filename, &prepared_file_info)
.IsInvalidArgument());
ASSERT_EQ(prepared_file_info, nullptr);
prepared_file_info = std::make_shared<PreparedFileInfo>();
ASSERT_TRUE(source_db
->GetPreparedFileInfoForExternalSstIngestion(
dbname_ + "_wrong/" + live_meta[0].relative_filename,
&prepared_file_info)
.IsInvalidArgument());
ASSERT_EQ(prepared_file_info, nullptr);
ASSERT_OK(source_db->GetPreparedFileInfoForExternalSstIngestion(
file_path, &prepared_file_info));
ASSERT_TRUE(prepared_file_info != nullptr);
ASSERT_EQ(prepared_file_info->file_size, live_meta[0].size);
ASSERT_EQ(prepared_file_info->table_properties.key_largest_seqno,
live_meta[0].largest_seqno);
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {file_path};
arg.file_infos = {prepared_file_info.get()};
arg.options.allow_db_generated_files = true;
arg.options.snapshot_consistency = false;
SyncPoint::GetInstance()->SetCallBack(
"ExternalSstFileIngestionJob::GetIngestedFileInfo:ReadPath",
[](void*) { FAIL() << "Prepared file info should skip the read path"; });
SyncPoint::GetInstance()->EnableProcessing();
ASSERT_OK(db_->IngestExternalFiles({arg}));
ASSERT_EQ(Get(Key(10)), "v10");
ASSERT_OK(source_db->Close());
source_db.reset();
ASSERT_OK(DestroyDB(source_dbname, options));
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(ExternalSSTFileTest,
IngestWithFileInfoRejectsMissingDbGeneratedSeqnoBounds) {
Options options = CurrentOptions();
DestroyAndReopen(options);
const std::string f = sst_files_dir_ + "missing_seqno_bounds.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(1), "v1"));
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
auto prepared_file_info = file_info.prepared_file_info;
auto missing_seqno_bounds =
std::make_shared<PreparedFileInfo>(*prepared_file_info);
missing_seqno_bounds->table_properties.key_smallest_seqno = UINT64_MAX;
missing_seqno_bounds->table_properties.key_largest_seqno = UINT64_MAX;
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {missing_seqno_bounds.get()};
arg.options.allow_db_generated_files = true;
arg.options.snapshot_consistency = false;
Status s = db_->IngestExternalFiles({arg});
ASSERT_TRUE(s.IsCorruption());
ASSERT_NE(s.ToString().find("Unknown largest seqno"), std::string::npos);
}
TEST_F(ExternalSSTFileTest, IngestWithFileInfoPreparedKeyBoundTypes) {
Options options = CurrentOptions();
DestroyAndReopen(options);
const std::string f = sst_files_dir_ + "prepared_key_bound_types.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(10), "v10"));
ASSERT_OK(w.DeleteRange(Key(20), Key(30)));
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
auto prepared_file_info = file_info.prepared_file_info;
ParsedInternalKey smallest;
ASSERT_OK(ParseInternalKey(prepared_file_info->smallest.Encode(), &smallest,
false /* log_err_key */));
ASSERT_EQ(0, smallest.sequence);
ASSERT_EQ(kTypeValue, smallest.type);
ASSERT_EQ(Key(10), smallest.user_key.ToString());
ParsedInternalKey largest;
ASSERT_OK(ParseInternalKey(prepared_file_info->largest.Encode(), &largest,
false /* log_err_key */));
ASSERT_EQ(kMaxSequenceNumber, largest.sequence);
ASSERT_EQ(kTypeRangeDeletion, largest.type);
ASSERT_EQ(Key(30), largest.user_key.ToString());
}
TEST_F(ExternalSSTFileTest, ComparatorMismatch) {
Options options = CurrentOptions();
Options options_diff_ucmp = options;
@@ -2462,8 +1816,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
for (int i = 0; i < 500; i++) {
std::vector<std::pair<std::string, std::string>> random_data;
for (int j = 0; j < 100; j++) {
std::string k =
rnd.RandomString(rnd.Next() % 20 + 1); // requires non-empty keys
std::string k = rnd.RandomString(rnd.Next() % 20);
std::string v = rnd.RandomString(rnd.Next() % 50);
random_data.emplace_back(k, v);
}
@@ -3894,20 +3247,6 @@ class ExternalSSTFileWithTimestampTest : public ExternalSSTFileTest {
return dst;
}
std::string MaxTimestamp() {
Slice ts = MaxU64Ts();
return ts.ToString();
}
Options UDTMemtableOnlyOptions() {
Options options = CurrentOptions();
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
options.persist_user_defined_timestamps = false;
options.allow_concurrent_memtable_write = false;
options.disable_auto_compactions = true;
return options;
}
Status IngestExternalUDTFile(const std::vector<std::string>& files,
bool allow_global_seqno = true) {
IngestExternalFileOptions opts;
@@ -4476,74 +3815,8 @@ TEST_F(ExternalSSTFileWithTimestampTest, TimestampsNotPersistedBasic) {
kRangeDelSkipConfigs));
}
TEST_F(ExternalSSTFileWithTimestampTest,
TimestampsPersistedIngestWithFileInfoRangeDeletionBounds) {
Options options = CurrentOptions();
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
options.persist_user_defined_timestamps = true;
options.disable_auto_compactions = true;
DestroyAndReopen(options);
const std::string ts = EncodeAsUint64(10);
const std::string f = sst_files_dir_ + "udt_persisted_rangedel_bounds.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(30), ts, "v30"));
ASSERT_OK(w.DeleteRange(Key(20), Key(26), ts));
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
auto prepared_file_info = file_info.prepared_file_info;
ASSERT_EQ(Key(20) + MaxTimestamp(),
prepared_file_info->smallest.user_key().ToString());
ASSERT_EQ(Key(30) + ts, prepared_file_info->largest.user_key().ToString());
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {file_info.prepared_file_info.get()};
ASSERT_OK(db_->IngestExternalFiles({arg}));
std::vector<LiveFileMetaData> metadata;
db_->GetLiveFilesMetaData(&metadata);
ASSERT_EQ(1, metadata.size());
ASSERT_EQ(Key(20) + MaxTimestamp(), metadata[0].smallestkey);
ASSERT_EQ(Key(30) + ts, metadata[0].largestkey);
}
TEST_F(ExternalSSTFileWithTimestampTest,
TimestampsNotPersistedIngestWithFileInfoRangeDeletionBounds) {
Options options = UDTMemtableOnlyOptions();
DestroyAndReopen(options);
const std::string f = sst_files_dir_ + "udt_rangedel_bounds.sst";
SstFileWriter w(EnvOptions(), options);
ASSERT_OK(w.Open(f));
ASSERT_OK(w.Put(Key(30), EncodeAsUint64(0), "v30"));
ASSERT_OK(w.DeleteRange(Key(20), Key(26), EncodeAsUint64(0)));
ExternalSstFileInfo file_info;
ASSERT_OK(w.Finish(&file_info));
auto prepared_file_info = file_info.prepared_file_info;
ASSERT_EQ(Key(20), prepared_file_info->smallest.user_key().ToString());
ASSERT_EQ(Key(30), prepared_file_info->largest.user_key().ToString());
IngestExternalFileArg arg;
arg.column_family = db_->DefaultColumnFamily();
arg.external_files = {f};
arg.file_infos = {file_info.prepared_file_info.get()};
ASSERT_OK(db_->IngestExternalFiles({arg}));
std::vector<LiveFileMetaData> metadata;
db_->GetLiveFilesMetaData(&metadata);
ASSERT_EQ(1, metadata.size());
ASSERT_EQ(Key(20) + MaxTimestamp(), metadata[0].smallestkey);
ASSERT_EQ(Key(30) + EncodeAsUint64(0), metadata[0].largestkey);
}
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
testing::Combine(testing::Bool(), testing::Bool(),
testing::Bool()));
testing::Combine(testing::Bool(), testing::Bool()));
class IngestDBGeneratedFileTest
: public ExternalSSTFileTestBase,
-72
View File
@@ -646,78 +646,6 @@ TEST(FaultInjectionFSTest, ReadUnsyncedData) {
ASSERT_EQ(0, sl.compare(Slice(data.data() + first_read_len, sl.size())));
}
}
TEST(FaultInjectionFSTest, FileOpenContractRejectsReadersWhileOpenForWrite) {
std::shared_ptr<FaultInjectionTestFS> fault_fs =
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
const std::string fname = test::PerThreadDBPath(
"file_open_contract_no_readers_while_open_for_write");
FileOptions writer_options;
writer_options.open_contract = FileOpenContract::kNoReadersWhileOpenForWrite;
std::unique_ptr<FSWritableFile> writer;
ASSERT_OK(fault_fs->NewWritableFile(fname, writer_options, &writer, nullptr));
std::unique_ptr<FSRandomAccessFile> reader;
IOStatus s =
fault_fs->NewRandomAccessFile(fname, FileOptions(), &reader, nullptr);
ASSERT_TRUE(s.IsNotSupported());
ASSERT_OK(writer->Close(IOOptions(), nullptr));
ASSERT_OK(
fault_fs->NewRandomAccessFile(fname, FileOptions(), &reader, nullptr));
}
TEST(FaultInjectionFSTest,
FileOpenContractAllowsSyncFileButRejectsReopenedWrite) {
std::shared_ptr<FaultInjectionTestFS> fault_fs =
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
const std::string fname =
test::PerThreadDBPath("file_open_contract_no_reopen_for_write");
FileOptions writer_options;
writer_options.open_contract = FileOpenContract::kNoReopenForWrite;
std::unique_ptr<FSWritableFile> writer;
ASSERT_OK(fault_fs->NewWritableFile(fname, writer_options, &writer, nullptr));
ASSERT_OK(writer->Append("abc", IOOptions(), nullptr));
ASSERT_OK(writer->Close(IOOptions(), nullptr));
ASSERT_OK(fault_fs->SyncFile(fname, FileOptions(), IOOptions(),
/*use_fsync=*/false, nullptr));
std::unique_ptr<FSWritableFile> reopened_writer;
ASSERT_OK(fault_fs->ReopenWritableFile(fname, FileOptions(), &reopened_writer,
nullptr));
IOStatus s = reopened_writer->Append("d", IOOptions(), nullptr);
ASSERT_TRUE(s.IsNotSupported());
ASSERT_OK(reopened_writer->Close(IOOptions(), nullptr));
}
TEST(FaultInjectionFSTest, FileOpenContractAllowsCreateAfterDelete) {
std::shared_ptr<FaultInjectionTestFS> fault_fs =
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
const std::string fname =
test::PerThreadDBPath("file_open_contract_recreate_after_delete");
FileOptions writer_options;
writer_options.open_contract = FileOpenContract::kNoReopenForWrite;
std::unique_ptr<FSWritableFile> writer;
ASSERT_OK(fault_fs->NewWritableFile(fname, writer_options, &writer, nullptr));
ASSERT_OK(writer->Append("abc", IOOptions(), nullptr));
ASSERT_OK(writer->Close(IOOptions(), nullptr));
ASSERT_OK(FileSystem::Default()->DeleteFile(fname, IOOptions(), nullptr));
std::unique_ptr<FSWritableFile> recreated_writer;
ASSERT_OK(fault_fs->NewWritableFile(fname, writer_options, &recreated_writer,
nullptr));
ASSERT_OK(recreated_writer->Append("def", IOOptions(), nullptr));
ASSERT_OK(recreated_writer->Close(IOOptions(), nullptr));
ASSERT_OK(fault_fs->DeleteFile(fname, IOOptions(), nullptr));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+6 -6
View File
@@ -159,8 +159,8 @@ ImmutableMemTableOptions::ImmutableMemTableOptions(
mutable_cf_options.memtable_protection_bytes_per_key),
allow_data_in_errors(ioptions.allow_data_in_errors),
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks),
memtable_verify_per_key_checksum_on_seek(
mutable_cf_options.memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek(
mutable_cf_options.memtable_veirfy_per_key_checksum_on_seek),
memtable_batch_lookup_optimization(
ioptions.memtable_batch_lookup_optimization) {}
@@ -256,7 +256,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
mutable_cf_options.memtable_max_range_deletions),
key_validation_callback_(
(moptions_.protection_bytes_per_key != 0 &&
moptions_.memtable_verify_per_key_checksum_on_seek)
moptions_.memtable_veirfy_per_key_checksum_on_seek)
? std::bind(&MemTable::ValidateKey, this, std::placeholders::_1,
std::placeholders::_2)
: std::function<Status(const char*, bool)>(nullptr)) {
@@ -528,7 +528,7 @@ class MemTableIterator : public InternalIterator {
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
validate_on_seek_(
mem.moptions_.paranoid_memory_checks ||
mem.moptions_.memtable_verify_per_key_checksum_on_seek),
mem.moptions_.memtable_veirfy_per_key_checksum_on_seek),
allow_data_in_error_(mem.moptions_.allow_data_in_errors),
key_validation_callback_(mem.key_validation_callback_) {
if (kind == kRangeDelEntries) {
@@ -1682,7 +1682,7 @@ void MemTable::GetFromTable(const LookupKey& key,
saver.protection_bytes_per_key = moptions_.protection_bytes_per_key;
if (!moptions_.paranoid_memory_checks &&
!moptions_.memtable_verify_per_key_checksum_on_seek) {
!moptions_.memtable_veirfy_per_key_checksum_on_seek) {
table_->Get(key, &saver, SaveValue);
} else {
Status check_s = table_->GetAndValidate(
@@ -1750,7 +1750,7 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
// Use batch lookup optimization when enabled
bool use_batch_optimization = moptions_.memtable_batch_lookup_optimization;
bool validate = moptions_.paranoid_memory_checks ||
moptions_.memtable_verify_per_key_checksum_on_seek;
moptions_.memtable_veirfy_per_key_checksum_on_seek;
if (use_batch_optimization) {
// Phase 1: Handle range tombstones and set up Savers for batched lookup
+1 -1
View File
@@ -68,7 +68,7 @@ struct ImmutableMemTableOptions {
uint32_t protection_bytes_per_key;
bool allow_data_in_errors;
bool paranoid_memory_checks;
bool memtable_verify_per_key_checksum_on_seek;
bool memtable_veirfy_per_key_checksum_on_seek;
bool memtable_batch_lookup_optimization;
};
+16 -98
View File
@@ -17,7 +17,6 @@
#include "db/version_set.h"
#include "logging/log_buffer.h"
#include "logging/logging.h"
#include "memory/arena.h"
#include "monitoring/thread_status_util.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
@@ -32,78 +31,6 @@ class InternalKeyComparator;
class Mutex;
class VersionSet;
bool MultiScanOverlapsUserKeyRange(const MultiScanArgs* scan_opts,
const Comparator* user_comparator,
const Slice& smallest_user_key,
const Slice& largest_user_key) {
if (scan_opts == nullptr || !scan_opts->HasBoundedScanRanges()) {
return true;
}
for (const ScanOptions& scan_range : scan_opts->GetScanRanges()) {
if (user_comparator->CompareWithoutTimestamp(
scan_range.range.limit.value(), /*a_has_ts=*/false,
smallest_user_key, /*b_has_ts=*/true) <= 0) {
continue;
}
if (user_comparator->CompareWithoutTimestamp(
scan_range.range.start.value(), /*a_has_ts=*/false,
largest_user_key, /*b_has_ts=*/true) > 0) {
continue;
}
return true;
}
return false;
}
bool MultiScanIteratorOverlapsUserKeyRange(InternalIterator* iter,
const MultiScanArgs* scan_opts,
const Comparator* user_comparator) {
iter->SeekToFirst();
if (!iter->status().ok()) {
return true;
}
if (!iter->Valid()) {
return true;
}
const std::string smallest_user_key = ExtractUserKey(iter->key()).ToString();
iter->SeekToLast();
if (!iter->status().ok()) {
return true;
}
if (!iter->Valid()) {
return true;
}
const std::string largest_user_key = ExtractUserKey(iter->key()).ToString();
return MultiScanOverlapsUserKeyRange(scan_opts, user_comparator,
smallest_user_key, largest_user_key);
}
bool MultiScanIntersectsMemTable(
ReadOnlyMemTable* memtable, const ReadOptions& read_options,
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
const SliceTransform* prefix_extractor, const MultiScanArgs* scan_opts,
const Comparator* user_comparator) {
if (scan_opts == nullptr || !scan_opts->HasBoundedScanRanges()) {
return true;
}
if (memtable->NumRangeDeletion() > 0) {
return true;
}
Arena arena;
ReadOptions intersect_read_options(read_options);
intersect_read_options.total_order_seek = true;
intersect_read_options.iterate_lower_bound = nullptr;
intersect_read_options.iterate_upper_bound = nullptr;
InternalIterator* iter =
memtable->NewIterator(intersect_read_options, seqno_to_time_mapping,
&arena, prefix_extractor, /*for_flush=*/false);
ScopedArenaPtr<InternalIterator> iter_guard(iter);
return MultiScanIteratorOverlapsUserKeyRange(iter, scan_opts,
user_comparator);
}
void MemTableListVersion::AddMemTable(ReadOnlyMemTable* m) {
if (!memlist_.empty()) {
// ID can be equal for MemPurge
@@ -302,42 +229,33 @@ void MemTableListVersion::AddIterators(
const ReadOptions& options,
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
const SliceTransform* prefix_extractor,
MergeIteratorBuilder* merge_iter_builder, bool add_range_tombstone_iter,
SequenceNumber read_seq, const MultiScanArgs* scan_opts,
const Comparator* user_comparator) {
MergeIteratorBuilder* merge_iter_builder, bool add_range_tombstone_iter) {
for (auto& m : memlist_) {
const bool should_probe_scan_intersection =
scan_opts != nullptr && scan_opts->HasBoundedScanRanges() &&
m->NumRangeDeletion() == 0;
if (should_probe_scan_intersection &&
!MultiScanIntersectsMemTable(m, options, seqno_to_time_mapping,
prefix_extractor, scan_opts,
user_comparator)) {
continue;
}
auto mem_iter =
m->NewIterator(options, seqno_to_time_mapping,
merge_iter_builder->GetArena(), prefix_extractor,
/*for_flush=*/false);
ScopedArenaPtr<InternalIterator> mem_iter_guard(mem_iter);
if (!add_range_tombstone_iter || options.ignore_range_deletions) {
merge_iter_builder->AddIterator(mem_iter_guard.release());
merge_iter_builder->AddIterator(mem_iter);
} else {
const SequenceNumber range_del_read_seq =
read_seq != kMaxSequenceNumber || options.snapshot == nullptr
? read_seq
: options.snapshot->GetSequenceNumber();
// Except for snapshot read, using kMaxSequenceNumber is OK because these
// are immutable memtables.
SequenceNumber read_seq = options.snapshot != nullptr
? options.snapshot->GetSequenceNumber()
: kMaxSequenceNumber;
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
m->NewRangeTombstoneIterator(options, range_del_read_seq,
true /* immutale_memtable */));
if (range_del_iter != nullptr && !range_del_iter->empty()) {
auto range_del_iter = m->NewRangeTombstoneIterator(
options, read_seq, true /* immutale_memtable */);
if (range_del_iter == nullptr || range_del_iter->empty()) {
delete range_del_iter;
} else {
mem_tombstone_iter = std::make_unique<TruncatedRangeDelIterator>(
std::move(range_del_iter), &m->GetInternalKeyComparator(),
nullptr /* smallest */, nullptr /* largest */);
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
&m->GetInternalKeyComparator(), nullptr /* smallest */,
nullptr /* largest */);
}
merge_iter_builder->AddPointAndTombstoneIterator(
mem_iter_guard.release(), std::move(mem_tombstone_iter));
mem_iter, std::move(mem_tombstone_iter));
}
}
}
+1 -22
View File
@@ -34,21 +34,6 @@ class MemTableList;
struct FlushJobInfo;
// Returns true when at least one bounded scan range overlaps the user-key
// range. Unbounded scan options conservatively overlap.
bool MultiScanOverlapsUserKeyRange(const MultiScanArgs* scan_opts,
const Comparator* user_comparator,
const Slice& smallest_user_key,
const Slice& largest_user_key);
// Returns true when the memtable may contain keys or range deletions relevant
// to the scan ranges. Iterator/status errors conservatively overlap.
bool MultiScanIntersectsMemTable(
ReadOnlyMemTable* memtable, const ReadOptions& read_options,
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
const SliceTransform* prefix_extractor, const MultiScanArgs* scan_opts,
const Comparator* user_comparator);
// keeps a list of immutable memtables (ReadOnlyMemtable*) in a vector.
// The list is immutable if refcount is bigger than one. It is used as
// a state for Get() and iterator code paths.
@@ -137,17 +122,11 @@ class MemTableListVersion {
std::vector<InternalIterator*>* iterator_list,
Arena* arena);
// read_seq controls range tombstone visibility. It may be captured before
// lazy iterator initialization. scan_opts can prune immutable memtables that
// cannot intersect the requested scan ranges.
void AddIterators(const ReadOptions& options,
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
const SliceTransform* prefix_extractor,
MergeIteratorBuilder* merge_iter_builder,
bool add_range_tombstone_iter,
SequenceNumber read_seq = kMaxSequenceNumber,
const MultiScanArgs* scan_opts = nullptr,
const Comparator* user_comparator = nullptr);
bool add_range_tombstone_iter);
uint64_t GetTotalNumEntries() const;
-3
View File
@@ -497,9 +497,6 @@ FragmentedRangeTombstoneIterator::SplitBySnapshot(
splits;
SequenceNumber lower = 0;
SequenceNumber upper;
// Safe to dereference icmp_ below: SplitBySnapshot is construction-time work
// (called synchronously from the range-del aggregator while the reader that
// owns *icmp_ is alive). See the icmp_ note in the header.
for (size_t i = 0; i <= snapshots.size(); i++) {
if (i >= snapshots.size()) {
upper = kMaxSequenceNumber;
+1 -9
View File
@@ -199,9 +199,7 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
RangeTombstone Tombstone() const {
assert(Valid());
// Use ucmp_ (long-lived; see the icmp_ note below) rather than
// icmp_->user_comparator(). They are equal for all constructors.
if (ucmp_->timestamp_size()) {
if (icmp_->user_comparator()->timestamp_size()) {
return RangeTombstone(start_key(), end_key(), seq(), timestamp());
}
return RangeTombstone(start_key(), end_key(), seq());
@@ -318,12 +316,6 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
const RangeTombstoneStackStartComparator tombstone_start_cmp_;
const RangeTombstoneStackEndComparator tombstone_end_cmp_;
// icmp_ is a borrowed pointer and is not guaranteed to outlive this iterator:
// it may be built from a comparator with a shorter lifetime than an iterator
// some owner keeps alive. Only dereference it during construction-time work
// (e.g. SplitBySnapshot), when the referent is known alive. Post-construction
// navigation must use ucmp_ (the long-lived user comparator) and tombstones_;
// do not add new icmp_ dereferences in Next()/Seek()/Tombstone().
const InternalKeyComparator* icmp_;
const Comparator* ucmp_;
std::shared_ptr<FragmentedRangeTombstoneList> tombstones_ref_;
+9 -97
View File
@@ -98,7 +98,7 @@ Status TableCache::GetTableReader(
const MutableCFOptions& mutable_cf_options, bool skip_filters, int level,
bool prefetch_index_and_filter_in_cache,
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature,
std::string* file_open_metadata, bool avoid_shared_metadata_cache) {
std::string* file_open_metadata) {
std::string fname = TableFileName(
ioptions_.cf_paths, file_meta.fd.GetNumber(), file_meta.fd.GetPathId());
std::unique_ptr<FSRandomAccessFile> file;
@@ -190,8 +190,7 @@ Status TableCache::GetTableReader(
block_cache_tracer_, max_file_size_for_l0_meta_pin, db_session_id_,
file_meta.fd.GetNumber(), expected_unique_id,
file_meta.fd.largest_seqno, file_meta.tail_size,
file_meta.user_defined_timestamps_persisted,
avoid_shared_metadata_cache),
file_meta.user_defined_timestamps_persisted),
std::move(file_reader), file_meta.fd.GetFileSize(), table_reader,
prefetch_index_and_filter_in_cache);
TEST_SYNC_POINT("TableCache::GetTableReader:0");
@@ -214,53 +213,11 @@ Status TableCache::FindTable(
const bool no_io, HistogramImpl* file_read_hist, bool skip_filters,
int level, bool prefetch_index_and_filter_in_cache,
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature,
bool pin_table_handle, std::string* file_open_metadata,
std::unique_ptr<TableReader>* fresh_table_reader_owner,
const TableCacheOpenOptions& open_options) {
bool pin_table_handle, std::string* file_open_metadata) {
assert(out_table_reader != nullptr && *out_table_reader == nullptr);
assert(handle != nullptr && *handle == nullptr);
// open_ephemeral_table_reader requests a fresh reader;
// fresh_table_reader_owner is where we return it. The two must agree.
assert(open_options.open_ephemeral_table_reader ==
(fresh_table_reader_owner != nullptr));
PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock);
// Bypass path: open a fresh TableReader, skipping the pinned-reader fast path
// and the shared cache. The caller takes ownership via
// fresh_table_reader_owner. no_io is not allowed here since opening a new
// reader always needs I/O.
if (fresh_table_reader_owner != nullptr) {
assert(!no_io);
if (no_io) {
// Defensive in release builds: the caller violated the contract.
return Status::Incomplete(
"fresh TableReader requested but no_io is set; cannot open file");
}
std::unique_ptr<TableReader> table_reader;
const bool effective_skip_filters =
skip_filters || open_options.skip_filters;
TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:FreshTableReader",
const_cast<TableCacheOpenOptions*>(&open_options));
Status s = GetTableReader(
ro, file_options, internal_comparator, file_meta,
false /* sequential mode */, file_read_hist, &table_reader,
mutable_cf_options, effective_skip_filters, level,
prefetch_index_and_filter_in_cache &&
!open_options.avoid_shared_metadata_cache,
max_file_size_for_l0_meta_pin, file_temperature, file_open_metadata,
open_options.avoid_shared_metadata_cache);
if (!s.ok()) {
assert(table_reader == nullptr);
RecordTick(ioptions_.stats, NO_FILE_ERRORS);
IGNORE_STATUS_IF_ERROR(s);
return s;
}
*out_table_reader = table_reader.get();
*fresh_table_reader_owner = std::move(table_reader);
*handle = nullptr;
return s;
}
// Fast path: if table reader is already pinned, return it directly without a
// cache lookup.
auto pinned_reader = file_meta.fd.pinned_reader.Get();
@@ -356,23 +313,15 @@ InternalIterator* TableCache::NewIterator(
const InternalKey* largest_compaction_key, bool allow_unprepared_value,
const SequenceNumber* read_seqno,
std::unique_ptr<TruncatedRangeDelIterator>* range_del_iter,
bool maybe_pin_table_handle, std::string* file_open_metadata,
const TableCacheOpenOptions& open_options) {
bool maybe_pin_table_handle, std::string* file_open_metadata) {
PERF_TIMER_GUARD(new_table_iterator_nanos);
Status s;
TableReader* table_reader = nullptr;
TypedHandle* handle = nullptr;
assert(!open_options.open_ephemeral_table_reader ||
table_reader_ptr == nullptr);
// Holds ownership of a freshly-opened TableReader when the caller asked us
// to bypass the shared cache. When non-empty, the iterator we hand back must
// arrange to free it on destruction.
std::unique_ptr<TableReader> ephemeral_reader;
if (table_reader_ptr != nullptr) {
*table_reader_ptr = nullptr;
}
const bool effective_skip_filters = skip_filters || open_options.skip_filters;
bool for_compaction = caller == TableReaderCaller::kCompaction;
TEST_SYNC_POINT_CALLBACK("TableCache::NewIterator::BeforeFindTable",
const_cast<FileDescriptor*>(&file_meta.fd));
@@ -380,13 +329,9 @@ InternalIterator* TableCache::NewIterator(
options, file_options, icomparator, file_meta, &handle,
mutable_cf_options, &table_reader,
options.read_tier == kBlockCacheTier /* no_io */, file_read_hist,
effective_skip_filters, level,
/*prefetch_index_and_filter_in_cache=*/
!open_options.avoid_shared_metadata_cache, max_file_size_for_l0_meta_pin,
file_meta.temperature,
maybe_pin_table_handle && should_pin_table_handles_, file_open_metadata,
open_options.open_ephemeral_table_reader ? &ephemeral_reader : nullptr,
open_options);
skip_filters, level, true /* prefetch_index_and_filter_in_cache */,
max_file_size_for_l0_meta_pin, file_meta.temperature,
maybe_pin_table_handle && should_pin_table_handles_, file_open_metadata);
InternalIterator* result = nullptr;
if (s.ok()) {
if (options.table_filter &&
@@ -395,17 +340,13 @@ InternalIterator* TableCache::NewIterator(
} else {
result = table_reader->NewIterator(
options, mutable_cf_options.prefix_extractor.get(), arena,
effective_skip_filters, caller,
file_options.compaction_readahead_size, allow_unprepared_value);
skip_filters, caller, file_options.compaction_readahead_size,
allow_unprepared_value);
}
if (handle != nullptr) {
cache_.RegisterReleaseAsCleanup(handle, *result);
handle = nullptr; // prevent from releasing below
}
// Don't hand ephemeral_reader to result's cleanup yet: range-del
// processing below can set s to non-OK, in which case result is replaced
// by an error iterator at function end. Transfer ownership only once we
// know s stays OK; otherwise ephemeral_reader's destructor frees it.
if (for_compaction) {
table_reader->SetupForCompaction();
@@ -457,37 +398,8 @@ InternalIterator* TableCache::NewIterator(
if (handle != nullptr) {
cache_.Release(handle);
}
// Range-del processing is done and s is final. Hand the ephemeral reader's
// lifetime to the returned iterator; if s is non-OK, leave it for
// ephemeral_reader's destructor. RegisterCleanup gets the raw pointer before
// release(), so if its allocation throws the reader is still owned by
// ephemeral_reader and freed on unwind.
if (s.ok() && ephemeral_reader && result != nullptr) {
TableReader* raw = ephemeral_reader.get();
result->RegisterCleanup(
[](void* arg1, void* /*arg2*/) {
delete static_cast<TableReader*>(arg1);
},
raw, nullptr);
// release() returns raw, which the cleanup now owns; drop the unique_ptr's
// ownership so the reader isn't freed twice.
[[maybe_unused]] TableReader* released = ephemeral_reader.release();
assert(released == raw);
}
if (!s.ok()) {
// Today result is always null here: it is only set when s was OK, and the
// only later change to s, new_range_del_iter->status(), is always OK for a
// FragmentedRangeTombstoneIterator. The assert documents that; the cleanup
// still disposes of any stray result before the error iterator replaces it.
assert(result == nullptr);
if (result != nullptr) {
if (arena != nullptr) {
result->~InternalIterator();
} else {
delete result;
}
result = nullptr;
}
result = NewErrorInternalIterator<Slice>(s, arena);
}
return result;
+17 -49
View File
@@ -35,18 +35,6 @@ struct FileDescriptor;
class GetContext;
class HistogramImpl;
struct TableCacheOpenOptions {
// Open a new TableReader owned by the returned iterator instead of reusing
// the pinned reader or shared TableCache entry.
bool open_ephemeral_table_reader = false;
// Avoid inserting open-time metadata blocks into the shared block cache.
bool avoid_shared_metadata_cache = false;
// Disable loading/accessing filter blocks for this open/iterator.
bool skip_filters = false;
};
// Manages caching for TableReader objects for a column family. The actual
// cache is allocated separately and passed to the constructor. TableCache
// wraps around the underlying SST file readers by providing Get(),
@@ -85,9 +73,7 @@ class TableCache {
// underlying the returned iterator, or nullptr if no Table object underlies
// the returned iterator. The returned "*table_reader_ptr" object is owned
// by the cache and should not be deleted, and is valid for as long as the
// returned iterator is live. `table_reader_ptr` must be nullptr when
// `open_options.open_ephemeral_table_reader` is set because that reader is
// owned by iterator cleanup rather than by the cache.
// returned iterator is live.
// If !options.ignore_range_deletions, and range_del_iter is non-nullptr,
// then range_del_iter is set to a TruncatedRangeDelIterator for range
// tombstones in the SST file corresponding to the specified file number. The
@@ -96,16 +82,12 @@ class TableCache {
// @param options Must outlive the returned iterator.
// @param range_del_agg If non-nullptr, adds range deletions to the
// aggregator. If an error occurs, returns it in a NewErrorInternalIterator
// @param caller Identifies the high-level caller for table-reader behavior
// such as compaction preparation.
// @param for_compaction If true, a new TableReader may be allocated (but
// not cached), depending on the CF options
// @param skip_filters Disables loading/accessing the filter block
// @param level The level this table is at, -1 for "not set / don't know"
// @param range_del_read_seqno If non-nullptr, will be used to create
// *range_del_iter.
// @param open_options Optional table-open policy overrides. The caller must
// not combine `open_ephemeral_table_reader` with
// `ReadOptions::read_tier = kBlockCacheTier` (no_io);
// opening an ephemeral reader inherently requires I/O.
InternalIterator* NewIterator(
const ReadOptions& options, const FileOptions& toptions,
const InternalKeyComparator& internal_comparator,
@@ -121,8 +103,7 @@ class TableCache {
bool maybe_pin_table_handle = false,
// If non-null, and the table reader is newly opened (not cached),
// retrieves file open metadata via GetFileOpenMetadata().
std::string* file_open_metadata = nullptr,
const TableCacheOpenOptions& open_options = TableCacheOpenOptions());
std::string* file_open_metadata = nullptr);
// If a seek to internal key "k" in specified file finds an entry,
// call get_context->SaveValue() repeatedly until
@@ -209,30 +190,18 @@ class TableCache {
// @param pin_table_handle If true, pins the table reader on file_meta so
// future lookups bypass the cache. *handle is set to nullptr
// on return in this case.
// @param fresh_table_reader_owner If non-null, FindTable always opens a new
// TableReader (skipping the pinned-reader fast path and the
// shared cache) and writes ownership into this unique_ptr.
// `*handle` will be nullptr on return and `*table_reader` will
// point to the freshly allocated reader. Callers that pass this
// are responsible for keeping the unique_ptr alive for the
// lifetime of any iterators built on top.
// @param open_options Optional table-open policy overrides (e.g.
// avoid_shared_metadata_cache, skip_filters).
// `open_options.open_ephemeral_table_reader` must be set if and
// only if `fresh_table_reader_owner` is non-null.
Status FindTable(
const ReadOptions& ro, const FileOptions& toptions,
const InternalKeyComparator& internal_comparator,
const FileMetaData& file_meta, TypedHandle**,
const MutableCFOptions& mutable_cf_options, TableReader** table_reader,
const bool no_io = false, HistogramImpl* file_read_hist = nullptr,
bool skip_filters = false, int level = -1,
bool prefetch_index_and_filter_in_cache = true,
size_t max_file_size_for_l0_meta_pin = 0,
Temperature file_temperature = Temperature::kUnknown,
bool pin_table_handle = false, std::string* file_open_metadata = nullptr,
std::unique_ptr<TableReader>* fresh_table_reader_owner = nullptr,
const TableCacheOpenOptions& open_options = TableCacheOpenOptions());
Status FindTable(const ReadOptions& ro, const FileOptions& toptions,
const InternalKeyComparator& internal_comparator,
const FileMetaData& file_meta, TypedHandle**,
const MutableCFOptions& mutable_cf_options,
TableReader** table_reader, const bool no_io = false,
HistogramImpl* file_read_hist = nullptr,
bool skip_filters = false, int level = -1,
bool prefetch_index_and_filter_in_cache = true,
size_t max_file_size_for_l0_meta_pin = 0,
Temperature file_temperature = Temperature::kUnknown,
bool pin_table_handle = false,
std::string* file_open_metadata = nullptr);
// Get the table properties of a given table.
// @no_io: indicates if we should load table to the cache if it is not present
@@ -317,8 +286,7 @@ class TableCache {
bool prefetch_index_and_filter_in_cache = true,
size_t max_file_size_for_l0_meta_pin = 0,
Temperature file_temperature = Temperature::kUnknown,
std::string* file_open_metadata = nullptr,
bool avoid_shared_metadata_cache = false);
std::string* file_open_metadata = nullptr);
// Update the max_covering_tombstone_seq in the GetContext for each key based
// on the range deletions in the table
-5
View File
@@ -337,11 +337,6 @@ struct FileMetaData {
// via FileOptions::file_metadata on subsequent opens. Empty if not available.
std::string file_open_metadata;
// Skips prefetching index and filter blocks into block cache on file open,
// not persisted in MANIFEST. NOTE: false does not guarantee prefetching
// either (e.g. when index and filter blocks are not cached in block cache).
bool skip_index_and_filter_blocks_prefetch = false;
FileMetaData() = default;
FileMetaData(uint64_t file, uint32_t file_path_id, uint64_t file_size,
+134 -352
View File
@@ -18,9 +18,7 @@
#include <set>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "db/blob/blob_fetcher.h"
@@ -71,6 +69,7 @@
#include "table/multiget_context.h"
#include "table/plain/plain_table_factory.h"
#include "table/table_reader.h"
#include "table/two_level_iterator.h"
#include "table/unique_id_impl.h"
#include "test_util/sync_point.h"
#include "util/cast_util.h"
@@ -111,181 +110,6 @@ int FindFileInRange(const InternalKeyComparator& icmp,
return static_cast<int>(std::lower_bound(b + left, b + right, key, cmp) - b);
}
InternalKey MultiScanInternalKey(const Slice& user_key,
const Comparator* user_comparator) {
const size_t timestamp_size = user_comparator->timestamp_size();
if (timestamp_size == 0) {
return InternalKey(user_key, kMaxSequenceNumber, kValueTypeForSeek);
}
std::string key_with_ts;
AppendKeyWithMaxTimestamp(&key_with_ts, user_key, timestamp_size);
return InternalKey(key_with_ts, kMaxSequenceNumber, kValueTypeForSeek);
}
bool MultiScanRangeOverlapsFile(const UserComparatorWrapper& user_comparator,
const ScanOptions& scan_opts,
const FdWithKeyRange& file) {
assert(scan_opts.range.start.has_value());
assert(scan_opts.range.limit.has_value());
if (user_comparator.CompareWithoutTimestamp(
scan_opts.range.limit.value(), /*a_has_ts=*/false,
ExtractUserKey(file.smallest_key), /*b_has_ts=*/true) <= 0) {
return false;
}
if (user_comparator.CompareWithoutTimestamp(
scan_opts.range.start.value(), /*a_has_ts=*/false,
ExtractUserKey(file.largest_key), /*b_has_ts=*/true) > 0) {
return false;
}
return true;
}
void AddMultiScanFileIndex(std::vector<size_t>* file_indexes,
std::vector<char>* selected, size_t file_index) {
if ((*selected)[file_index]) {
return;
}
(*selected)[file_index] = true;
file_indexes->push_back(file_index);
}
template <typename OnOverlappingFile>
bool NotifyMultiScanOverlappingFile(OnOverlappingFile& on_overlapping_file,
size_t file_index,
const ScanOptions& scan_range) {
if constexpr (std::is_same_v<std::invoke_result_t<OnOverlappingFile, size_t,
const ScanOptions&>,
bool>) {
return on_overlapping_file(file_index, scan_range);
} else {
on_overlapping_file(file_index, scan_range);
return true;
}
}
template <typename OnOverlappingFile>
void ForEachMultiScanOverlappingFile(const InternalKeyComparator& icmp,
const UserComparatorWrapper& ucmp,
const LevelFilesBrief& file_level,
int level, const MultiScanArgs& scan_opts,
OnOverlappingFile on_overlapping_file) {
assert(scan_opts.HasBoundedScanRanges());
if (file_level.num_files == 0) {
return;
}
if (level == 0) {
for (size_t i = 0; i < file_level.num_files; ++i) {
for (const ScanOptions& scan_range : scan_opts.GetScanRanges()) {
if (MultiScanRangeOverlapsFile(ucmp, scan_range, file_level.files[i])) {
if (!NotifyMultiScanOverlappingFile(on_overlapping_file, i,
scan_range)) {
return;
}
}
if (ucmp.CompareWithoutTimestamp(
scan_range.range.start.value(), /*a_has_ts=*/false,
ExtractUserKey(file_level.files[i].largest_key),
/*b_has_ts=*/true) > 0) {
break;
}
}
}
return;
}
for (const ScanOptions& scan_range : scan_opts.GetScanRanges()) {
const InternalKey start_key = MultiScanInternalKey(
scan_range.range.start.value(), ucmp.user_comparator());
size_t first_file = FindFile(icmp, file_level, start_key.Encode());
if (first_file >= file_level.num_files) {
continue;
}
const InternalKey limit_key = MultiScanInternalKey(
scan_range.range.limit.value(), ucmp.user_comparator());
size_t last_file = FindFile(icmp, file_level, limit_key.Encode());
if (last_file >= file_level.num_files) {
last_file = file_level.num_files - 1;
}
for (size_t i = first_file; i <= last_file; ++i) {
if (MultiScanRangeOverlapsFile(ucmp, scan_range, file_level.files[i])) {
if (!NotifyMultiScanOverlappingFile(on_overlapping_file, i,
scan_range)) {
return;
}
}
}
}
}
std::vector<size_t> GetMultiScanOverlappingFiles(
const InternalKeyComparator& icmp, const UserComparatorWrapper& ucmp,
const LevelFilesBrief& file_level, int level,
const MultiScanArgs& scan_opts,
size_t max_file_indexes = std::numeric_limits<size_t>::max()) {
std::vector<size_t> file_indexes;
if (!scan_opts.HasBoundedScanRanges() || file_level.num_files == 0) {
return file_indexes;
}
file_indexes.reserve(
std::min(static_cast<size_t>(file_level.num_files), max_file_indexes));
std::vector<char> selected(file_level.num_files, false);
ForEachMultiScanOverlappingFile(
icmp, ucmp, file_level, level, scan_opts,
[&](size_t file_index, const ScanOptions& /*scan_range*/) {
if (file_indexes.size() >= max_file_indexes) {
return false;
}
AddMultiScanFileIndex(&file_indexes, &selected, file_index);
return file_indexes.size() < max_file_indexes;
});
return file_indexes;
}
void AddTableIteratorForLevel(
ColumnFamilyData* cfd, const ReadOptions& read_options,
const FileOptions& soptions, const MutableCFOptions& mutable_cf_options,
MergeIteratorBuilder* merge_iter_builder, Arena* arena,
const LevelFilesBrief& file_level, int level, size_t file_index,
bool skip_filters, size_t max_file_size_for_l0_meta_pin,
bool allow_unprepared_value, SequenceNumber read_seq) {
std::unique_ptr<TruncatedRangeDelIterator> tombstone_iter = nullptr;
const auto& file = file_level.files[file_index];
auto table_iter = cfd->table_cache()->NewIterator(
read_options, soptions, cfd->internal_comparator(), *file.file_metadata,
/*range_del_agg=*/nullptr, mutable_cf_options, nullptr,
cfd->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, arena, skip_filters, level,
max_file_size_for_l0_meta_pin,
/*smallest_compaction_key=*/nullptr,
/*largest_compaction_key=*/nullptr, allow_unprepared_value, &read_seq,
&tombstone_iter,
/*maybe_pin_table_handle=*/true);
#ifndef NDEBUG
TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:AddedFile",
file.file_metadata);
std::pair<bool, bool> iterator_type(true /* is_block_based_table_iterator */,
false /* is_level_iterator */);
TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:IteratorType",
&iterator_type);
#endif
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(table_iter);
} else {
merge_iter_builder->AddPointAndTombstoneIterator(table_iter,
std::move(tombstone_iter));
}
}
Status OverlapWithIterator(const Comparator* ucmp,
const Slice& smallest_user_key,
const Slice& largest_user_key,
@@ -1142,15 +966,6 @@ bool SomeFileOverlapsRange(const InternalKeyComparator& icmp,
namespace {
TableCacheOpenOptions GetCompactionTableCacheOpenOptions(
bool open_ephemeral_table_reader) {
TableCacheOpenOptions options;
options.open_ephemeral_table_reader = open_ephemeral_table_reader;
options.avoid_shared_metadata_cache = open_ephemeral_table_reader;
options.skip_filters = open_ephemeral_table_reader;
return options;
}
class LevelIterator final : public InternalIterator {
public:
// NOTE: many of the const& parameters are saved in this object (so
@@ -1166,9 +981,7 @@ class LevelIterator final : public InternalIterator {
bool allow_unprepared_value = false,
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
nullptr,
SequenceNumber read_seq = kMaxSequenceNumber,
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr,
bool open_ephemeral_table_reader = false)
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
: table_cache_(table_cache),
read_options_(read_options),
file_options_(file_options),
@@ -1184,11 +997,9 @@ class LevelIterator final : public InternalIterator {
pinned_iters_mgr_(nullptr),
compaction_boundaries_(compaction_boundaries),
range_tombstone_iter_(nullptr),
range_tombstone_iter_required_(range_tombstone_iter_ptr_ != nullptr),
read_seq_(read_seq != kMaxSequenceNumber ||
read_options.snapshot == nullptr
? read_seq
: read_options.snapshot->GetSequenceNumber()),
read_seq_(read_options.snapshot
? read_options.snapshot->GetSequenceNumber()
: kMaxSequenceNumber),
level_(level),
skip_filters_(skip_filters),
allow_unprepared_value_(allow_unprepared_value),
@@ -1196,9 +1007,7 @@ class LevelIterator final : public InternalIterator {
to_return_sentinel_(false),
scan_opts_(nullptr),
db_statistics_(db_statistics),
clock_(clock),
table_cache_open_options_(
GetCompactionTableCacheOpenOptions(open_ephemeral_table_reader)) {
clock_(clock) {
// Empty level is not supported.
assert(flevel_ != nullptr && flevel_->num_files > 0);
if (range_tombstone_iter_ptr_) {
@@ -1337,18 +1146,69 @@ class LevelIterator final : public InternalIterator {
assert(so->GetComparator() == user_comparator_.user_comparator());
file_to_scan_opts_ = std::make_unique<ScanOptionsMap>();
ForEachMultiScanOverlappingFile(
icomparator_, user_comparator_, *flevel_, level_, *scan_opts_,
[&](size_t file_index, const ScanOptions& scan_range) {
auto const metadata = flevel_->files[file_index].file_metadata;
for (size_t k = 0; k < scan_opts_->size(); k++) {
const ScanOptions& opt = scan_opts_->GetScanRanges().at(k);
auto start = opt.range.start;
auto end = opt.range.limit;
if (!start.has_value()) {
continue;
}
// We can capture this case in the future, but for now lets skip this.
if (!end.has_value()) {
continue;
}
const size_t timestamp_size =
user_comparator_.user_comparator()->timestamp_size();
InternalKey istart, iend;
if (timestamp_size == 0) {
istart =
InternalKey(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
// end key is exclusive for multiscan
iend = InternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
} else {
std::string start_key_with_ts, end_key_with_ts;
AppendKeyWithMaxTimestamp(&start_key_with_ts, start.value(),
timestamp_size);
AppendKeyWithMaxTimestamp(&end_key_with_ts, end.value(),
timestamp_size);
istart = InternalKey(start_key_with_ts, kMaxSequenceNumber,
kValueTypeForSeek);
// end key is exclusive for multiscan
iend =
InternalKey(end_key_with_ts, kMaxSequenceNumber, kValueTypeForSeek);
}
// TODO: This needs to be optimized, right now we iterate twice, which
// we dont need to. We can do this in N rather than 2N.
size_t fstart = FindFile(icomparator_, *flevel_, istart.Encode());
size_t fend = FindFile(icomparator_, *flevel_, iend.Encode());
// We need to check the relevant cases
// Cases:
// 1. [ S E ]
// 2. [ S ] [ E ]
// 3. [ S ] ...... [ E ]
for (auto i = fstart; i <= fend; i++) {
if (i < flevel_->num_files) {
// FindFile only compares against the largest_key, so we need this
// additional check to ensure the scan range overlaps the file
if (icomparator_.InternalKeyComparator::Compare(
iend.Encode(), flevel_->files[i].smallest_key) < 0) {
continue;
}
auto const metadata = flevel_->files[i].file_metadata;
if (metadata->FileIsStandAloneRangeTombstone()) {
// Skip stand alone range deletion files.
return;
continue;
}
auto& args = GetMultiScanArgForFile(file_index);
args.insert(scan_range.range.start.value(),
scan_range.range.limit.value(), scan_range.property_bag);
});
auto& args = GetMultiScanArgForFile(i);
args.insert(start.value(), end.value(), opt.property_bag);
}
}
}
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
@@ -1361,8 +1221,7 @@ class LevelIterator final : public InternalIterator {
file_to_arg.first));
}
if (so->use_async_io &&
(!range_tombstone_iter_required_ || range_tombstone_iter_ != nullptr)) {
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_) {
@@ -1455,9 +1314,7 @@ class LevelIterator final : public InternalIterator {
/*max_file_size_for_l0_meta_pin=*/0, smallest_compaction_key,
largest_compaction_key, allow_unprepared_value_, &read_seq_,
range_tombstone_iter_,
/*maybe_pin_table_handle=*/
!table_cache_open_options_.open_ephemeral_table_reader,
/*file_open_metadata=*/nullptr, table_cache_open_options_);
/*maybe_pin_table_handle=*/true);
}
// Check if current file being fully within iterate_lower_bound.
@@ -1514,7 +1371,6 @@ class LevelIterator final : public InternalIterator {
//
// *range_tombstone_iter_ points to range tombstones of the current SST file
std::unique_ptr<TruncatedRangeDelIterator>* range_tombstone_iter_;
bool range_tombstone_iter_required_;
// The sentinel key to be returned
Slice sentinel_;
@@ -1535,7 +1391,6 @@ class LevelIterator final : public InternalIterator {
Statistics* db_statistics_ = nullptr;
SystemClock* clock_ = nullptr;
TableCacheOpenOptions table_cache_open_options_;
// Our stored scan_opts for each prefix
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
@@ -1825,20 +1680,22 @@ bool LevelIterator::SkipEmptyFileForward() {
if (scan_opts_ && FileHasMultiScanArg(file_index_)) {
const ScanOptions& opts =
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
assert(opts.range.start.has_value());
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);
if (opts.range.start.has_value()) {
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());
}
file_iter_.Seek(target.Encode());
} else {
file_iter_.SeekToFirst();
}
@@ -1885,8 +1742,14 @@ void LevelIterator::SkipEmptyFileBackward() {
#ifndef NDEBUG
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
return MultiScanRangeOverlapsFile(user_comparator_, opts,
flevel_->files[file_index]);
return (user_comparator_.CompareWithoutTimestamp(
opts.range.start.value(), /*a_has_ts=*/false,
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),
/*b_has_ts=*/true) > 0);
}
#endif
@@ -2408,9 +2271,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
mutable_cf_options_, cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
allow_unprepared_value,
read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr,
kMaxSequenceNumber, db_statistics_, clock_);
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2481,22 +2342,19 @@ double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel(
void Version::AddIterators(const ReadOptions& read_options,
const FileOptions& soptions,
MergeIteratorBuilder* merge_iter_builder,
bool allow_unprepared_value, SequenceNumber read_seq,
const MultiScanArgs* scan_opts) {
bool allow_unprepared_value) {
assert(storage_info_.finalized_);
for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) {
AddIteratorsForLevel(read_options, soptions, merge_iter_builder, level,
allow_unprepared_value, read_seq, scan_opts);
allow_unprepared_value);
}
}
void Version::AddIteratorsForLevel(const ReadOptions& read_options,
const FileOptions& soptions,
MergeIteratorBuilder* merge_iter_builder,
int level, bool allow_unprepared_value,
SequenceNumber read_seq,
const MultiScanArgs* scan_opts) {
int level, bool allow_unprepared_value) {
assert(storage_info_.finalized_);
if (level >= storage_info_.num_non_empty_levels()) {
// This is an empty level
@@ -2507,95 +2365,37 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
}
auto* arena = merge_iter_builder->GetArena();
const LevelFilesBrief& file_level = storage_info_.LevelFilesBrief(level);
const UserComparatorWrapper user_comparator(
cfd_->internal_comparator().user_comparator());
if (scan_opts != nullptr) {
const std::vector<size_t> file_indexes = GetMultiScanOverlappingFiles(
cfd_->internal_comparator(), user_comparator, file_level, level,
*scan_opts, level == 0 ? std::numeric_limits<size_t>::max() : 2);
if (file_indexes.empty()) {
return;
}
if (level == 0) {
const bool should_sample = should_sample_file_read();
for (size_t file_index : file_indexes) {
AddTableIteratorForLevel(
cfd_, read_options, soptions, mutable_cf_options_,
merge_iter_builder, arena, file_level, level, file_index,
/*skip_filters=*/false, max_file_size_for_l0_meta_pin_,
allow_unprepared_value, read_seq);
if (should_sample) {
sample_file_read_inc(file_level.files[file_index].file_metadata);
}
}
return;
}
if (file_indexes.size() == 1) {
AddTableIteratorForLevel(cfd_, read_options, soptions,
mutable_cf_options_, merge_iter_builder, arena,
file_level, level, file_indexes.front(),
IsFilterSkipped(level),
/*max_file_size_for_l0_meta_pin=*/0,
allow_unprepared_value, read_seq);
return;
}
#ifndef NDEBUG
for (size_t file_index = 0; file_index < file_level.num_files;
++file_index) {
TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:AddedFile",
file_level.files[file_index].file_metadata);
}
#endif
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr;
auto level_iter = new (mem) LevelIterator(
cfd_->table_cache(), read_options, soptions,
cfd_->internal_comparator(), &file_level, mutable_cf_options_,
cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
/*range_del_agg=*/nullptr,
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr,
read_seq, db_statistics_, clock_);
#ifndef NDEBUG
std::pair<bool, bool> iterator_type(
false /* is_block_based_table_iterator */,
true /* is_level_iterator */);
TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:IteratorType",
&iterator_type);
#endif
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
assert(tombstone_iter_ptr);
merge_iter_builder->AddPointAndTombstoneIterator(
level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr);
}
return;
}
if (level == 0) {
// Merge all level zero files together since they may overlap
const bool should_sample = should_sample_file_read();
for (size_t i = 0; i < file_level.num_files; i++) {
AddTableIteratorForLevel(
cfd_, read_options, soptions, mutable_cf_options_, merge_iter_builder,
arena, file_level, level, i,
/*skip_filters=*/false, max_file_size_for_l0_meta_pin_,
allow_unprepared_value, read_seq);
if (should_sample) {
// Count once for every L0 file. This is done per iterator creation
// rather than Seek(), while files in other levels are sampled on
// seek/next/prev.
sample_file_read_inc(file_level.files[i].file_metadata);
std::unique_ptr<TruncatedRangeDelIterator> tombstone_iter = nullptr;
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
auto table_iter = cfd_->table_cache()->NewIterator(
read_options, soptions, cfd_->internal_comparator(),
*file.file_metadata, /*range_del_agg=*/nullptr, mutable_cf_options_,
nullptr, cfd_->internal_stats()->GetFileReadHist(0),
TableReaderCaller::kUserIterator, arena,
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
/*smallest_compaction_key=*/nullptr,
/*largest_compaction_key=*/nullptr, allow_unprepared_value,
/*range_del_read_seqno=*/nullptr, &tombstone_iter,
/*maybe_pin_table_handle=*/true);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(table_iter);
} else {
merge_iter_builder->AddPointAndTombstoneIterator(
table_iter, std::move(tombstone_iter));
}
}
} else if (file_level.num_files > 0) {
if (should_sample_file_read()) {
// Count ones for every L0 files. This is done per iterator creation
// rather than Seek(), while files in other levels are sampled on
// seek/next/prev.
for (FileMetaData* meta : storage_info_.LevelFiles(0)) {
sample_file_read_inc(meta);
}
}
} else if (storage_info_.LevelFilesBrief(level).num_files > 0) {
// For levels > 0, we can use a concatenating iterator that sequentially
// walks through the non-overlapping files in the level, opening them
// lazily.
@@ -2603,13 +2403,12 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr;
auto level_iter = new (mem) LevelIterator(
cfd_->table_cache(), read_options, soptions,
cfd_->internal_comparator(), &file_level, mutable_cf_options_,
cfd_->internal_stats()->GetFileReadHist(level),
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
mutable_cf_options_, cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
/*range_del_agg=*/nullptr,
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr,
read_seq, db_statistics_, clock_);
&tombstone_iter_ptr, db_statistics_, clock_);
if (read_options.ignore_range_deletions) {
merge_iter_builder->AddIterator(level_iter);
} else {
@@ -2665,8 +2464,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
mutable_cf_options_, cfd_->internal_stats()->GetFileReadHist(level),
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
&range_del_agg, nullptr, false, nullptr, kMaxSequenceNumber,
db_statistics_, clock_));
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
iter.get(), overlap);
}
@@ -5722,19 +5520,16 @@ struct VersionSet::ManifestWriter {
ColumnFamilyData* cfd;
const autovector<VersionEdit*>& edit_list;
const std::function<void(const Status&)> manifest_write_callback;
int max_file_opening_threads;
explicit ManifestWriter(
InstrumentedMutex* mu, ColumnFamilyData* _cfd,
const autovector<VersionEdit*>& e,
const std::function<void(const Status&)>& manifest_wcb,
int _max_file_opening_threads = 1)
const std::function<void(const Status&)>& manifest_wcb)
: done(false),
cv(mu),
cfd(_cfd),
edit_list(e),
manifest_write_callback(manifest_wcb),
max_file_opening_threads(_max_file_opening_threads) {}
manifest_write_callback(manifest_wcb) {}
~ManifestWriter() { status.PermitUncheckedError(); }
bool IsAllWalEdits() const {
@@ -6109,10 +5904,6 @@ Status VersionSet::ProcessManifestWrites(
ManifestWriter& first_writer = writers.front();
ManifestWriter* last_writer = &first_writer;
// Supports opening table readers with multiple threads, mostly useful for
// batched external sst file ingestion.
int batch_max_file_opening_threads = 1;
assert(!manifest_writers_.empty());
assert(manifest_writers_.front() == &first_writer);
@@ -6149,9 +5940,6 @@ Status VersionSet::ProcessManifestWrites(
for (;;) {
assert(!(*it)->edit_list.front()->IsColumnFamilyManipulation());
last_writer = *it;
batch_max_file_opening_threads =
std::max(batch_max_file_opening_threads,
last_writer->max_file_opening_threads);
assert(last_writer != nullptr);
assert(last_writer->cfd != nullptr);
if (last_writer->cfd->IsDropped()) {
@@ -6410,7 +6198,7 @@ Status VersionSet::ProcessManifestWrites(
builder_guards.size() == versions.size());
ColumnFamilyData* cfd = versions[i]->cfd_;
s = builder_guards[i]->version_builder()->LoadTableHandlers(
cfd->internal_stats(), batch_max_file_opening_threads,
cfd->internal_stats(), 1 /* max_threads */,
true /* prefetch_index_and_filter_in_cache */,
false /* is_initial_load */, versions[i]->GetMutableCFOptions(),
MaxFileSizeForL0MetaPin(versions[i]->GetMutableCFOptions()),
@@ -6773,7 +6561,7 @@ Status VersionSet::LogAndApply(
InstrumentedMutex* mu, FSDirectory* dir_contains_current_file,
bool new_descriptor_log, const ColumnFamilyOptions* new_cf_options,
const std::vector<std::function<void(const Status&)>>& manifest_wcbs,
const std::function<Status()>& pre_cb, int max_file_opening_threads) {
const std::function<Status()>& pre_cb) {
mu->AssertHeld();
int num_edits = 0;
for (const auto& elist : edit_lists) {
@@ -6805,8 +6593,7 @@ Status VersionSet::LogAndApply(
for (int i = 0; i < num_cfds; ++i) {
const auto wcb =
manifest_wcbs.empty() ? [](const Status&) {} : manifest_wcbs[i];
writers.emplace_back(mu, column_family_datas[i], edit_lists[i], wcb,
max_file_opening_threads);
writers.emplace_back(mu, column_family_datas[i], edit_lists[i], wcb);
manifest_writers_.push_back(&writers[i]);
}
assert(!writers.empty());
@@ -8072,7 +7859,7 @@ InternalIterator* VersionSet::MakeInputIterator(
RangeDelAggregator* range_del_agg,
const FileOptions& file_options_compactions,
const std::optional<const Slice>& start,
const std::optional<const Slice>& end, bool open_ephemeral_table_reader) {
const std::optional<const Slice>& end) {
auto cfd = c->column_family_data();
// Level-0 files have to be merged together. For other levels,
// we will make a concatenating iterator per level.
@@ -8091,8 +7878,6 @@ InternalIterator* VersionSet::MakeInputIterator(
range_tombstones;
size_t num = 0;
[[maybe_unused]] size_t num_input_files = 0;
const TableCacheOpenOptions table_cache_open_options =
GetCompactionTableCacheOpenOptions(open_ephemeral_table_reader);
for (size_t which = 0; which < c->num_input_levels(); which++) {
const LevelFilesBrief* flevel = c->input_levels(which);
num_input_files += flevel->num_files;
@@ -8129,9 +7914,7 @@ InternalIterator* VersionSet::MakeInputIterator(
/*largest_compaction_key=*/nullptr,
/*allow_unprepared_value=*/false,
/*range_del_read_seqno=*/nullptr,
/*range_del_iter=*/&range_tombstone_iter,
/*maybe_pin_table_handle=*/false,
/*file_open_metadata=*/nullptr, table_cache_open_options);
/*range_del_iter=*/&range_tombstone_iter);
range_tombstones.emplace_back(std::move(range_tombstone_iter),
nullptr);
}
@@ -8146,8 +7929,7 @@ InternalIterator* VersionSet::MakeInputIterator(
TableReaderCaller::kCompaction, /*skip_filters=*/false,
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
c->boundaries(which), false, &tombstone_iter_ptr,
kMaxSequenceNumber, db_options_->statistics.get(), clock_,
open_ephemeral_table_reader);
db_options_->statistics.get(), clock_);
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
}
}
+5 -22
View File
@@ -85,7 +85,6 @@ class MergeIteratorBuilder;
class SystemClock;
class ManifestTailer;
class FilePickerMultiGet;
class MultiScanArgs;
// VersionEdit is always supposed to be valid and it is used to point at
// entries in Manifest. Ideally it should not be used as a container to
@@ -917,27 +916,17 @@ class Version {
// yield the contents of this Version when merged together.
// @param read_options Must outlive any iterator built by
// `merger_iter_builder`.
// @param read_seq Snapshot sequence to use for range tombstone visibility.
// This is passed separately because lazy iterator initialization may happen
// after read_options.snapshot has been released by the caller.
// @param scan_opts Optional bounded scan ranges used to prune levels/files
// while building the iterator tree.
void AddIterators(const ReadOptions& read_options,
const FileOptions& soptions,
MergeIteratorBuilder* merger_iter_builder,
bool allow_unprepared_value, SequenceNumber read_seq,
const MultiScanArgs* scan_opts = nullptr);
bool allow_unprepared_value);
// @param read_options Must outlive any iterator built by
// `merger_iter_builder`.
// @param read_seq Snapshot sequence to use for range tombstone visibility.
// @param scan_opts Optional bounded scan ranges used to prune this level.
void AddIteratorsForLevel(const ReadOptions& read_options,
const FileOptions& soptions,
MergeIteratorBuilder* merger_iter_builder,
int level, bool allow_unprepared_value,
SequenceNumber read_seq,
const MultiScanArgs* scan_opts = nullptr);
int level, bool allow_unprepared_value);
Status OverlapWithLevelIterator(const ReadOptions&, const FileOptions&,
const Slice& smallest_user_key,
@@ -1330,8 +1319,7 @@ class VersionSet {
bool new_descriptor_log = false,
const ColumnFamilyOptions* new_cf_options = nullptr,
const std::vector<std::function<void(const Status&)>>& manifest_wcbs = {},
const std::function<Status()>& pre_cb = {},
int max_file_opening_threads = 1);
const std::function<Status()>& pre_cb = {});
void WakeUpWaitingManifestWriters();
@@ -1570,16 +1558,12 @@ class VersionSet {
// The caller should delete the iterator when no longer needed.
// @param read_options Must outlive the returned iterator.
// @param start, end indicates compaction range
// @param open_ephemeral_table_reader When true, the per-file iterators
// bypass the shared TableCache and open fresh TableReaders
// using `file_options_compactions`.
InternalIterator* MakeInputIterator(
const ReadOptions& read_options, const Compaction* c,
RangeDelAggregator* range_del_agg,
const FileOptions& file_options_compactions,
const std::optional<const Slice>& start,
const std::optional<const Slice>& end,
bool open_ephemeral_table_reader = false);
const std::optional<const Slice>& end);
// Add all files listed in any live version to *live_table_files and
// *live_blob_files. Note that these lists may contain duplicates.
@@ -1966,8 +1950,7 @@ class ReactiveVersionSet : public VersionSet {
InstrumentedMutex* /*mu*/, FSDirectory* /*dir_contains_current_file*/,
bool /*new_descriptor_log*/, const ColumnFamilyOptions* /*new_cf_option*/,
const std::vector<std::function<void(const Status&)>>& /*manifest_wcbs*/,
const std::function<Status()>& /*pre_cb*/,
int /*max_file_opening_threads*/) override {
const std::function<Status()>& /*pre_cb*/) override {
return Status::NotSupported("not supported in reactive mode");
}
+1 -4
View File
@@ -62,14 +62,11 @@ Status LoadTableHandlersHelper(
TableCache::TypedHandle* handle = nullptr;
TableReader* table_reader = nullptr;
const bool prefetch_index_and_filter_for_file =
prefetch_index_and_filter_in_cache &&
!file_meta->skip_index_and_filter_blocks_prefetch;
auto status = table_cache->FindTable(
read_options, file_options, internal_comparator, *file_meta, &handle,
mutable_cf_options, &table_reader, false /* no_io */,
internal_stats->GetFileReadHist(level), false /* skip_filters */,
level, prefetch_index_and_filter_for_file,
level, prefetch_index_and_filter_in_cache,
max_file_size_for_l0_meta_pin, file_meta->temperature,
true /* pin_table_handle */);
+1 -4
View File
@@ -201,7 +201,6 @@ DECLARE_bool(verify_checksum);
DECLARE_bool(mmap_read);
DECLARE_bool(mmap_write);
DECLARE_bool(use_direct_reads);
DECLARE_bool(use_direct_io_for_compaction_reads);
DECLARE_bool(use_direct_io_for_flush_and_compaction);
DECLARE_bool(mock_direct_io);
DECLARE_bool(statistics);
@@ -229,8 +228,6 @@ DECLARE_uint64(backup_max_size);
DECLARE_int32(checkpoint_one_in);
DECLARE_int32(ingest_external_file_one_in);
DECLARE_int32(ingest_external_file_width);
DECLARE_int32(ingest_external_file_prepare_commit_one_in);
DECLARE_int32(ingest_external_file_use_file_info_one_in);
DECLARE_int32(compact_files_one_in);
DECLARE_int32(compact_range_one_in);
DECLARE_int32(promote_l0_one_in);
@@ -306,7 +303,7 @@ DECLARE_string(default_write_temperature);
DECLARE_string(default_temperature);
DECLARE_uint32(verify_output_flags);
DECLARE_bool(paranoid_memory_checks);
DECLARE_bool(memtable_verify_per_key_checksum_on_seek);
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
DECLARE_bool(memtable_batch_lookup_optimization);
// Options for transaction dbs.
+4 -23
View File
@@ -732,11 +732,6 @@ DEFINE_bool(mmap_write, ROCKSDB_NAMESPACE::Options().allow_mmap_writes,
DEFINE_bool(use_direct_reads, ROCKSDB_NAMESPACE::Options().use_direct_reads,
"Use O_DIRECT for reading data");
DEFINE_bool(use_direct_io_for_compaction_reads,
ROCKSDB_NAMESPACE::Options().use_direct_io_for_compaction_reads,
"Use O_DIRECT for compaction-input SST reads only, while keeping "
"user reads buffered");
DEFINE_bool(use_direct_io_for_flush_and_compaction,
ROCKSDB_NAMESPACE::Options().use_direct_io_for_flush_and_compaction,
"Use O_DIRECT for writing data");
@@ -867,19 +862,6 @@ DEFINE_int32(ingest_external_file_one_in, 0,
DEFINE_int32(ingest_external_file_width, 100,
"The width of the ingested external files.");
DEFINE_int32(ingest_external_file_prepare_commit_one_in, 0,
"If non-zero, an ingestion that would call IngestExternalFile() "
"instead uses the two-phase PrepareFileIngestion()/"
"CommitFileIngestion() API once for every N such ingestions on "
"average, occasionally dropping the prepared handle without "
"committing to exercise the rollback path. 0 disables it.");
DEFINE_int32(ingest_external_file_use_file_info_one_in, 0,
"If non-zero, the ingestexternalfile flow reuses each file's "
"metadata via IngestExternalFileArg::file_infos (from "
"SstFileWriter::Finish) once every N ingestions on average, so "
"ingestion skips re-opening and scanning the files.");
DEFINE_int32(compact_files_one_in, 0,
"If non-zero, then CompactFiles() will be called once for every N "
"operations on average. 0 indicates CompactFiles() is disabled.");
@@ -1039,8 +1021,7 @@ DEFINE_int32(iterpercent, 10,
static const bool FLAGS_iterpercent_dummy __attribute__((__unused__)) =
RegisterFlagValidator(&FLAGS_iterpercent, &ValidateInt32Percent);
DEFINE_uint64(num_iterations, 10,
"Number of iterations per iterator or MultiScan run");
DEFINE_uint64(num_iterations, 10, "Number of iterations per MultiIterate run");
static const bool FLAGS_num_iterations_dummy __attribute__((__unused__)) =
RegisterFlagValidator(&FLAGS_num_iterations, &ValidateUint32Range);
@@ -1649,9 +1630,9 @@ DEFINE_bool(paranoid_memory_checks,
"Sets CF option paranoid_memory_checks.");
DEFINE_bool(
memtable_verify_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_verify_per_key_checksum_on_seek,
"Sets CF option memtable_verify_per_key_checksum_on_seek.");
memtable_veirfy_per_key_checksum_on_seek,
ROCKSDB_NAMESPACE::Options().memtable_veirfy_per_key_checksum_on_seek,
"Sets CF option memtable_veirfy_per_key_checksum_on_seek.");
DEFINE_bool(memtable_batch_lookup_optimization,
ROCKSDB_NAMESPACE::Options().memtable_batch_lookup_optimization,
+7 -31
View File
@@ -752,7 +752,7 @@ void StressTest::FinishInitDb(SharedState* shared) {
if (!s.ok()) {
fprintf(stderr, "Error restoring historical expected values: %s\n",
s.ToString().c_str());
port::ImmediateExit(1);
exit(1);
}
}
if (FLAGS_use_txn && !FLAGS_use_optimistic_txn) {
@@ -785,7 +785,7 @@ void StressTest::TrackExpectedState(SharedState* shared) {
if (!s.ok()) {
fprintf(stderr, "Error enabling history tracing: %s\n",
s.ToString().c_str());
port::ImmediateExit(1);
exit(1);
}
}
}
@@ -2088,16 +2088,6 @@ Status StressTest::TestMultiScan(ThreadState* thread,
return true;
};
// Sometimes stop before draining all prefetched blocks, matching applications
// that stop after a bounded number of results. In one mode the whole prepared
// MultiScan is abandoned. In the other mode only the current range is
// stopped, so the next range's Seek() exercises releasing skipped blocks on
// the same prepared iterator.
const bool early_exit = thread->rand.OneIn(2);
const bool abandon_scan_after_early_exit =
early_exit && thread->rand.OneIn(2);
bool abandon_prepared_scan = false;
for (const ScanOptions& scan_opt : scan_opts.GetScanRanges()) {
if (op_logs.size() > kOpLogsLimit) {
// Shouldn't take too much memory for the history log. Clear it.
@@ -2158,25 +2148,13 @@ Status StressTest::TestMultiScan(ThreadState* thread,
VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op,
key, rand_column_families, op_logs, verify_func, &diverged);
uint64_t range_iterations = 0;
while (iter->Valid()) {
if (early_exit && range_iterations >= FLAGS_num_iterations) {
if (abandon_scan_after_early_exit) {
op_logs += "E";
abandon_prepared_scan = true;
} else {
op_logs += "R";
}
break;
}
iter->Next();
if (!diverged) {
assert(cmp_iter->Valid());
cmp_iter->Next();
}
op_logs += "N";
++range_iterations;
if (iter->Valid() && ro.allow_unprepared_value) {
op_logs += "*";
@@ -2215,7 +2193,7 @@ Status StressTest::TestMultiScan(ThreadState* thread,
thread->stats.AddIterations(1);
op_logs += "; ";
if (diverged || abandon_prepared_scan) {
if (diverged) {
break;
}
}
@@ -4543,7 +4521,7 @@ void StressTest::Open(SharedState* shared, bool reopen) {
if (!s.ok()) {
fprintf(stderr, "open error: %s\n", s.ToString().c_str());
port::ImmediateExit(1);
exit(1);
}
if (db_->GetLatestSequenceNumber() < shared->GetPersistedSeqno()) {
@@ -4552,7 +4530,7 @@ void StressTest::Open(SharedState* shared, bool reopen) {
"did not recover to the persisted "
"sequence number %" PRIu64 " from last DB session\n",
db_->GetLatestSequenceNumber(), shared->GetPersistedSeqno());
port::ImmediateExit(1);
exit(1);
}
}
@@ -5188,8 +5166,6 @@ void InitializeOptionsFromFlags(
options.allow_mmap_reads = FLAGS_mmap_read;
options.allow_mmap_writes = FLAGS_mmap_write;
options.use_direct_reads = FLAGS_use_direct_reads;
options.use_direct_io_for_compaction_reads =
FLAGS_use_direct_io_for_compaction_reads;
options.use_direct_io_for_flush_and_compaction =
FLAGS_use_direct_io_for_flush_and_compaction;
options.recycle_log_file_num =
@@ -5277,8 +5253,8 @@ void InitializeOptionsFromFlags(
options.verify_output_flags =
static_cast<VerifyOutputFlags>(FLAGS_verify_output_flags);
options.paranoid_memory_checks = FLAGS_paranoid_memory_checks;
options.memtable_verify_per_key_checksum_on_seek =
FLAGS_memtable_verify_per_key_checksum_on_seek;
options.memtable_veirfy_per_key_checksum_on_seek =
FLAGS_memtable_veirfy_per_key_checksum_on_seek;
options.memtable_batch_lookup_optimization =
FLAGS_memtable_batch_lookup_optimization;
+59 -153
View File
@@ -14,7 +14,6 @@
#include "rocksdb/status.h"
#ifdef GFLAGS
#include <cinttypes>
#include <deque>
#include <unordered_map>
#include "db/wide/wide_columns_helper.h"
@@ -2327,16 +2326,60 @@ class NonBatchedOpsStressTest : public StressTest {
// deletion file's compaction input optimization.
bool test_standalone_range_deletion = thread->rand.OneInOpt(
FLAGS_test_ingest_standalone_range_deletion_one_in);
// When true, reuse the writer's metadata via IngestExternalFileArg's
// file_infos so ingestion skips re-opening and scanning the file. Not
// combined with the standalone range deletion mode (a range-del-only file).
bool use_file_info =
!test_standalone_range_deletion &&
thread->rand.OneInOpt(FLAGS_ingest_external_file_use_file_info_one_in);
std::vector<std::string> external_files;
const std::string sst_filename =
GetDbPath() + "/." + std::to_string(thread->tid) + ".sst";
external_files.push_back(sst_filename);
std::string standalone_rangedel_filename;
if (test_standalone_range_deletion) {
standalone_rangedel_filename = GetDbPath() + "/." +
std::to_string(thread->tid) +
"_standalone_rangedel.sst";
external_files.push_back(standalone_rangedel_filename);
}
Status s;
std::ostringstream ingest_options_oss;
// Temporarily disable error injection for preparation
if (db_fault_injection_fs_) {
db_fault_injection_fs_->DisableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
db_fault_injection_fs_->DisableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataWrite);
}
for (const auto& filename : external_files) {
if (raw_env->FileExists(filename).ok()) {
// Maybe we terminated abnormally before, so cleanup to give this file
// ingestion a clean slate
s = raw_env->DeleteFile(filename);
}
if (!s.ok()) {
return;
}
}
if (db_fault_injection_fs_) {
db_fault_injection_fs_->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
db_fault_injection_fs_->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataWrite);
}
SstFileWriter sst_file_writer(EnvOptions(options_), options_);
SstFileWriter standalone_rangedel_sst_file_writer(EnvOptions(options_),
options_);
if (s.ok()) {
s = sst_file_writer.Open(sst_filename);
}
if (s.ok() && test_standalone_range_deletion) {
s = standalone_rangedel_sst_file_writer.Open(
standalone_rangedel_filename);
}
if (!s.ok()) {
return;
}
int64_t key_base = rand_keys[0];
int column_family = rand_column_families[0];
std::vector<std::unique_ptr<MutexLock>> range_locks;
@@ -2385,76 +2428,13 @@ class NonBatchedOpsStressTest : public StressTest {
}
}
if (keys.empty()) {
return;
}
size_t total_keys = keys.size();
const size_t data_file_count =
std::min<size_t>(1 + thread->rand.Uniform(3), total_keys);
std::vector<std::string> external_files;
std::vector<std::string> data_filenames;
data_filenames.reserve(data_file_count);
for (size_t file_idx = 0; file_idx < data_file_count; ++file_idx) {
data_filenames.push_back(GetDbPath() + "/." +
std::to_string(thread->tid) + "_" +
std::to_string(file_idx) + ".sst");
external_files.push_back(data_filenames.back());
}
std::string standalone_rangedel_filename;
if (test_standalone_range_deletion) {
standalone_rangedel_filename = GetDbPath() + "/." +
std::to_string(thread->tid) +
"_standalone_rangedel.sst";
external_files.push_back(standalone_rangedel_filename);
}
// Temporarily disable error injection for preparation
if (db_fault_injection_fs_) {
db_fault_injection_fs_->DisableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
db_fault_injection_fs_->DisableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataWrite);
}
for (const auto& filename : external_files) {
if (raw_env->FileExists(filename).ok()) {
// Maybe we terminated abnormally before, so cleanup to give this file
// ingestion a clean slate
s = raw_env->DeleteFile(filename);
}
if (!s.ok()) {
return;
}
}
if (db_fault_injection_fs_) {
db_fault_injection_fs_->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataRead);
db_fault_injection_fs_->EnableThreadLocalErrorInjection(
FaultInjectionIOType::kMetadataWrite);
}
std::vector<ExternalSstFileInfo> file_infos(data_file_count);
std::deque<SstFileWriter> sst_file_writers;
for (size_t file_idx = 0; s.ok() && file_idx < data_file_count;
++file_idx) {
sst_file_writers.emplace_back(EnvOptions(options_), options_);
s = sst_file_writers.back().Open(data_filenames[file_idx]);
}
SstFileWriter standalone_rangedel_sst_file_writer(EnvOptions(options_),
options_);
if (s.ok() && test_standalone_range_deletion) {
s = standalone_rangedel_sst_file_writer.Open(
standalone_rangedel_filename);
}
if (!s.ok()) {
if (s.ok() && keys.empty()) {
return;
}
// set pending state on expected values, create and ingest files.
size_t total_keys = keys.size();
for (size_t i = 0; s.ok() && i < total_keys; i++) {
auto& sst_file_writer = sst_file_writers[i % sst_file_writers.size()];
int64_t key = keys.at(i);
char value[100];
auto key_str = Key(key);
@@ -2482,9 +2462,7 @@ class NonBatchedOpsStressTest : public StressTest {
}
}
if (s.ok() && !keys.empty()) {
for (size_t i = 0; s.ok() && i < sst_file_writers.size(); i++) {
s = sst_file_writers[i].Finish(&file_infos[i]);
}
s = sst_file_writer.Finish();
}
if (s.ok() && total_keys != 0 && test_standalone_range_deletion) {
@@ -2502,7 +2480,6 @@ class NonBatchedOpsStressTest : public StressTest {
s = standalone_rangedel_sst_file_writer.Finish();
}
}
bool dropped_without_commit = false;
if (s.ok()) {
IngestExternalFileOptions ingest_options;
ingest_options.move_files = thread->rand.OneInOpt(2);
@@ -2510,95 +2487,24 @@ class NonBatchedOpsStressTest : public StressTest {
ingest_options.verify_checksums_readahead_size =
thread->rand.OneInOpt(2) ? 1024 * 1024 : 0;
ingest_options.fill_cache = thread->rand.OneInOpt(4);
ingest_options.file_opening_threads = 1 + thread->rand.Uniform(4);
ingest_options.prefetch_lmax_index_and_filter_blocks =
!thread->rand.OneInOpt(4);
const bool use_prepare_commit = thread->rand.OneInOpt(
FLAGS_ingest_external_file_prepare_commit_one_in);
const bool use_separate_prepare_calls = use_prepare_commit &&
external_files.size() > 1 &&
thread->rand.OneInOpt(2);
ingest_options_oss << "move_files: " << ingest_options.move_files
<< ", verify_checksums_before_ingest: "
<< ingest_options.verify_checksums_before_ingest
<< ", verify_checksums_readahead_size: "
<< ingest_options.verify_checksums_readahead_size
<< ", fill_cache: " << ingest_options.fill_cache
<< ", file_opening_threads: "
<< ingest_options.file_opening_threads
<< ", prefetch_lmax_index_and_filter_blocks: "
<< ingest_options.prefetch_lmax_index_and_filter_blocks
<< ", ingest_external_file_data_file_count: "
<< data_file_count
<< ", num_external_files: " << external_files.size()
<< ", test_standalone_range_deletion: "
<< test_standalone_range_deletion
<< ", use_prepare_commit: " << use_prepare_commit
<< ", use_separate_prepare_calls: "
<< use_separate_prepare_calls
<< ", use_file_info: " << use_file_info;
IngestExternalFileArg arg;
arg.column_family = column_families_[column_family];
arg.external_files = external_files;
arg.options = ingest_options;
if (use_file_info) {
for (const auto& file_info : file_infos) {
arg.file_infos.push_back(file_info.prepared_file_info.get());
}
}
if (use_prepare_commit) {
std::vector<std::unique_ptr<FileIngestionHandle>> handles;
handles.reserve(use_separate_prepare_calls ? external_files.size() : 1);
if (use_separate_prepare_calls) {
for (const auto& external_file : external_files) {
IngestExternalFileArg file_arg;
file_arg.column_family = column_families_[column_family];
file_arg.external_files = {external_file};
file_arg.options = ingest_options;
std::unique_ptr<FileIngestionHandle> handle;
s = db_->PrepareFileIngestion({file_arg}, &handle);
if (!s.ok()) {
break;
}
handles.push_back(std::move(handle));
}
} else {
std::unique_ptr<FileIngestionHandle> handle;
s = db_->PrepareFileIngestion({arg}, &handle);
if (s.ok()) {
handles.push_back(std::move(handle));
}
}
if (s.ok()) {
// Occasionally cancel instead of committing, covering both rollback
// paths.
if (thread->rand.OneInOpt(4)) {
if (thread->rand.OneInOpt(2)) {
for (auto& handle : handles) {
Status abort_status = handle->Abort();
if (!abort_status.ok() && s.ok()) {
s = abort_status;
}
}
} else {
handles.clear(); // RAII rollback via destructors
}
dropped_without_commit = true;
} else {
s = db_->CommitFileIngestionHandles(std::move(handles));
}
}
} else {
s = db_->IngestExternalFiles({arg});
}
<< test_standalone_range_deletion;
s = db_->IngestExternalFile(column_families_[column_family],
external_files, ingest_options);
}
if (!s.ok() || dropped_without_commit) {
if (!s.ok()) {
for (PendingExpectedValue& pending_expected_value :
pending_expected_values) {
pending_expected_value.Rollback();
}
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
if (!IsErrorInjectedAndRetryable(s)) {
fprintf(stderr,
"file ingestion error: %s under specified "
"IngestExternalFileOptions: %s (Empty string or "
-7
View File
@@ -142,13 +142,6 @@ class CompositeEnv : public Env {
return file_system_->LinkFile(s, t, io_opts, &dbg);
}
Status SyncFile(const std::string& fname, const EnvOptions& env_options,
bool use_fsync) override {
IODebugContext dbg;
return file_system_->SyncFile(fname, FileOptions(env_options), IOOptions(),
use_fsync, &dbg);
}
Status NumFileLinks(const std::string& fname, uint64_t* count) override {
IOOptions io_opts;
IODebugContext dbg;
Vendored
-25
View File
@@ -26,7 +26,6 @@
#include "rocksdb/utilities/customizable_util.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_type.h"
#include "test_util/sync_point.h"
#include "util/autovector.h"
#include "util/string_util.h"
@@ -531,13 +530,6 @@ class LegacyFileSystemWrapper : public FileSystem {
return status_to_io_status(target_->LinkFile(s, t));
}
IOStatus SyncFile(const std::string& fname, const FileOptions& file_options,
const IOOptions& /*io_options*/, bool use_fsync,
IODebugContext* /*dbg*/) override {
return status_to_io_status(
target_->SyncFile(fname, file_options, use_fsync));
}
IOStatus NumFileLinks(const std::string& fname, const IOOptions& /*options*/,
uint64_t* count, IODebugContext* /*dbg*/) override {
return status_to_io_status(target_->NumFileLinks(fname, count));
@@ -804,23 +796,6 @@ Status Env::ReuseWritableFile(const std::string& fname,
return NewWritableFile(fname, result, options);
}
Status Env::SyncFile(const std::string& fname, const EnvOptions& options,
bool use_fsync) {
std::unique_ptr<WritableFile> file_to_sync;
Status status = ReopenWritableFile(fname, &file_to_sync, options);
TEST_SYNC_POINT_CALLBACK("Env::SyncFile:Open", &status);
if (status.ok()) {
status = use_fsync ? file_to_sync->Fsync() : file_to_sync->Sync();
Status close_status = file_to_sync->Close();
if (status.ok()) {
status = close_status;
} else {
close_status.PermitUncheckedError();
}
}
return status;
}
Status Env::GetChildrenFileAttributes(const std::string& dir,
std::vector<FileAttributes>* result) {
assert(result != nullptr);
-10
View File
@@ -814,16 +814,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
return status;
}
IOStatus SyncFile(const std::string& fname, const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) override {
// SyncFile does not read or write file contents, so it can delegate
// directly to the underlying filesystem without constructing an encrypted
// writable wrapper.
return FileSystemWrapper::SyncFile(fname, file_opts, io_opts, use_fsync,
dbg);
}
private:
std::shared_ptr<EncryptionProvider> provider_;
};
+3 -343
View File
@@ -4165,12 +4165,10 @@ TEST_F(TestAsyncRead, ReadAsyncQueueFull) {
std::unique_ptr<FSRandomAccessFile> file;
ASSERT_OK(fs->NewRandomAccessFile(fname, FileOptions(), &file, nullptr));
// Force the queue-full path without consuming an SQ slot. Overwriting the
// SQE pointer after io_uring_get_sqe() would leave a stale submission in
// the ring and pollute later tests using the same thread-local io_uring.
// Force io_uring_get_sqe to appear to return null via SyncPoint.
SyncPoint::GetInstance()->SetCallBack(
"PosixRandomAccessFile::ReadAsync:skip_io_uring_get_sqe",
[](void* arg) { *static_cast<bool*>(arg) = true; });
"PosixRandomAccessFile::ReadAsync:io_uring_get_sqe",
[](void* arg) { *static_cast<io_uring_sqe**>(arg) = nullptr; });
SyncPoint::GetInstance()->EnableProcessing();
IOOptions opts;
@@ -4655,344 +4653,6 @@ TEST_F(EnvTest, WriteStringToFileClosesFile) {
ASSERT_OK(counted_fs->DeleteFile(fname, IOOptions(), nullptr));
}
enum class SyncFileTestResult {
kOk,
kNotSupported,
kSyncError,
kFsyncError,
kCloseError,
};
Status MakeSyncFileTestStatus(SyncFileTestResult result) {
switch (result) {
case SyncFileTestResult::kOk:
return Status::OK();
case SyncFileTestResult::kNotSupported:
return Status::NotSupported("injected reopen failure");
case SyncFileTestResult::kSyncError:
return Status::IOError("injected sync failure");
case SyncFileTestResult::kFsyncError:
return Status::IOError("injected fsync failure");
case SyncFileTestResult::kCloseError:
return Status::IOError("injected close failure");
}
assert(false);
return Status::Corruption("unexpected sync file test result");
}
IOStatus MakeSyncFileTestIOStatus(SyncFileTestResult result) {
switch (result) {
case SyncFileTestResult::kOk:
return IOStatus::OK();
case SyncFileTestResult::kNotSupported:
return IOStatus::NotSupported("injected reopen failure");
case SyncFileTestResult::kSyncError:
return IOStatus::IOError("injected sync failure");
case SyncFileTestResult::kFsyncError:
return IOStatus::IOError("injected fsync failure");
case SyncFileTestResult::kCloseError:
return IOStatus::IOError("injected close failure");
}
assert(false);
return IOStatus::Corruption("unexpected sync file test result");
}
struct SyncFileTestState {
SyncFileTestResult reopen_result = SyncFileTestResult::kOk;
SyncFileTestResult sync_result = SyncFileTestResult::kOk;
SyncFileTestResult fsync_result = SyncFileTestResult::kOk;
SyncFileTestResult close_result = SyncFileTestResult::kOk;
int reopen_count = 0;
int sync_count = 0;
int fsync_count = 0;
int close_count = 0;
};
class SyncFileTestWritableFile : public WritableFileWrapper {
public:
SyncFileTestWritableFile(std::unique_ptr<WritableFile>&& target,
SyncFileTestState* state)
: WritableFileWrapper(target.get()),
target_guard_(std::move(target)),
state_(state) {}
Status Sync() override {
++state_->sync_count;
return MakeSyncFileTestStatus(state_->sync_result);
}
Status Fsync() override {
++state_->fsync_count;
return MakeSyncFileTestStatus(state_->fsync_result);
}
Status Close() override {
++state_->close_count;
if (state_->close_result == SyncFileTestResult::kOk) {
return WritableFileWrapper::Close();
}
Status status = WritableFileWrapper::Close();
status.PermitUncheckedError();
return MakeSyncFileTestStatus(state_->close_result);
}
private:
std::unique_ptr<WritableFile> target_guard_;
SyncFileTestState* state_;
};
class SyncFileTestEnv : public EnvWrapper {
public:
SyncFileTestEnv(Env* target, SyncFileTestState* state)
: EnvWrapper(target), state_(state) {}
Status ReopenWritableFile(const std::string& fname,
std::unique_ptr<WritableFile>* result,
const EnvOptions& options) override {
++state_->reopen_count;
Status status = MakeSyncFileTestStatus(state_->reopen_result);
if (!status.ok()) {
return status;
}
status = target()->ReopenWritableFile(fname, result, options);
if (status.ok()) {
result->reset(new SyncFileTestWritableFile(std::move(*result), state_));
}
return status;
}
Status SyncFile(const std::string& fname, const EnvOptions& options,
bool use_fsync) override {
return Env::SyncFile(fname, options, use_fsync);
}
private:
SyncFileTestState* state_;
};
class SyncFileTestFSWritableFile : public FSWritableFileOwnerWrapper {
public:
SyncFileTestFSWritableFile(std::unique_ptr<FSWritableFile>&& target,
SyncFileTestState* state)
: FSWritableFileOwnerWrapper(std::move(target)), state_(state) {}
IOStatus Sync(const IOOptions& /*options*/,
IODebugContext* /*dbg*/) override {
++state_->sync_count;
return MakeSyncFileTestIOStatus(state_->sync_result);
}
IOStatus Fsync(const IOOptions& /*options*/,
IODebugContext* /*dbg*/) override {
++state_->fsync_count;
return MakeSyncFileTestIOStatus(state_->fsync_result);
}
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
++state_->close_count;
if (state_->close_result == SyncFileTestResult::kOk) {
return FSWritableFileOwnerWrapper::Close(options, dbg);
}
IOStatus status = FSWritableFileOwnerWrapper::Close(options, dbg);
status.PermitUncheckedError();
return MakeSyncFileTestIOStatus(state_->close_result);
}
private:
SyncFileTestState* state_;
};
class SyncFileTestFileSystem : public FileSystemWrapper {
public:
SyncFileTestFileSystem(const std::shared_ptr<FileSystem>& target,
SyncFileTestState* state)
: FileSystemWrapper(target), state_(state) {}
const char* Name() const override { return "SyncFileTestFileSystem"; }
IOStatus ReopenWritableFile(const std::string& fname,
const FileOptions& file_opts,
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
++state_->reopen_count;
IOStatus status = MakeSyncFileTestIOStatus(state_->reopen_result);
if (!status.ok()) {
return status;
}
status = target()->ReopenWritableFile(fname, file_opts, result, dbg);
if (status.ok()) {
result->reset(new SyncFileTestFSWritableFile(std::move(*result), state_));
}
return status;
}
IOStatus SyncFile(const std::string& fname, const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) override {
return FileSystem::SyncFile(fname, file_opts, io_opts, use_fsync, dbg);
}
private:
SyncFileTestState* state_;
};
TEST_F(EnvTest, EnvSyncFileDefaultUsesSyncAndFsync) {
const std::string fname = test::PerThreadDBPath("env_sync_file_default");
ASSERT_OK(WriteStringToFile(Env::Default(), "sync-file-test", fname));
SyncFileTestState state;
SyncFileTestEnv env(Env::Default(), &state);
ASSERT_OK(env.SyncFile(fname, EnvOptions(), /*use_fsync=*/false));
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.fsync_count, 0);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(env.SyncFile(fname, EnvOptions(), /*use_fsync=*/true));
ASSERT_EQ(state.reopen_count, 2);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.fsync_count, 1);
ASSERT_EQ(state.close_count, 2);
ASSERT_OK(Env::Default()->DeleteFile(fname));
}
TEST_F(EnvTest, EnvSyncFileDefaultReturnsReopenError) {
SyncFileTestState state;
state.reopen_result = SyncFileTestResult::kNotSupported;
SyncFileTestEnv env(Env::Default(), &state);
const Status status =
env.SyncFile("unused", EnvOptions(), /*use_fsync=*/false);
ASSERT_TRUE(status.IsNotSupported()) << status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 0);
ASSERT_EQ(state.fsync_count, 0);
ASSERT_EQ(state.close_count, 0);
}
TEST_F(EnvTest, EnvSyncFileDefaultReturnsCloseErrorAfterSuccessfulSync) {
const std::string fname = test::PerThreadDBPath("env_sync_file_close_error");
ASSERT_OK(WriteStringToFile(Env::Default(), "sync-file-test", fname));
SyncFileTestState state;
state.close_result = SyncFileTestResult::kCloseError;
SyncFileTestEnv env(Env::Default(), &state);
const Status status = env.SyncFile(fname, EnvOptions(), /*use_fsync=*/false);
ASSERT_TRUE(status.IsIOError()) << status.ToString();
ASSERT_NE(status.ToString().find("close failure"), std::string::npos)
<< status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(Env::Default()->DeleteFile(fname));
}
TEST_F(EnvTest, EnvSyncFileDefaultReturnsSyncErrorBeforeCloseError) {
const std::string fname = test::PerThreadDBPath("env_sync_file_sync_error");
ASSERT_OK(WriteStringToFile(Env::Default(), "sync-file-test", fname));
SyncFileTestState state;
state.sync_result = SyncFileTestResult::kSyncError;
state.close_result = SyncFileTestResult::kCloseError;
SyncFileTestEnv env(Env::Default(), &state);
const Status status = env.SyncFile(fname, EnvOptions(), /*use_fsync=*/false);
ASSERT_TRUE(status.IsIOError()) << status.ToString();
ASSERT_NE(status.ToString().find("sync failure"), std::string::npos)
<< status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(Env::Default()->DeleteFile(fname));
}
TEST_F(EnvTest, FileSystemSyncFileDefaultUsesSyncAndFsync) {
const std::string fname = test::PerThreadDBPath("fs_sync_file_default");
ASSERT_OK(
WriteStringToFile(FileSystem::Default().get(), "sync-file-test", fname));
SyncFileTestState state;
SyncFileTestFileSystem fs(FileSystem::Default(), &state);
ASSERT_OK(fs.SyncFile(fname, FileOptions(), IOOptions(), /*use_fsync=*/false,
nullptr));
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.fsync_count, 0);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(fs.SyncFile(fname, FileOptions(), IOOptions(), /*use_fsync=*/true,
nullptr));
ASSERT_EQ(state.reopen_count, 2);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.fsync_count, 1);
ASSERT_EQ(state.close_count, 2);
ASSERT_OK(FileSystem::Default()->DeleteFile(fname, IOOptions(), nullptr));
}
TEST_F(EnvTest, FileSystemSyncFileDefaultReturnsReopenError) {
SyncFileTestState state;
state.reopen_result = SyncFileTestResult::kNotSupported;
SyncFileTestFileSystem fs(FileSystem::Default(), &state);
const IOStatus status = fs.SyncFile("unused", FileOptions(), IOOptions(),
/*use_fsync=*/false, nullptr);
ASSERT_TRUE(status.IsNotSupported()) << status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 0);
ASSERT_EQ(state.fsync_count, 0);
ASSERT_EQ(state.close_count, 0);
}
TEST_F(EnvTest, FileSystemSyncFileDefaultReturnsCloseErrorAfterSuccessfulSync) {
const std::string fname = test::PerThreadDBPath("fs_sync_file_close_error");
ASSERT_OK(
WriteStringToFile(FileSystem::Default().get(), "sync-file-test", fname));
SyncFileTestState state;
state.close_result = SyncFileTestResult::kCloseError;
SyncFileTestFileSystem fs(FileSystem::Default(), &state);
const IOStatus status = fs.SyncFile(fname, FileOptions(), IOOptions(),
/*use_fsync=*/false, nullptr);
ASSERT_TRUE(status.IsIOError()) << status.ToString();
ASSERT_NE(status.ToString().find("close failure"), std::string::npos)
<< status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(FileSystem::Default()->DeleteFile(fname, IOOptions(), nullptr));
}
TEST_F(EnvTest, FileSystemSyncFileDefaultReturnsSyncErrorBeforeCloseError) {
const std::string fname = test::PerThreadDBPath("fs_sync_file_sync_error");
ASSERT_OK(
WriteStringToFile(FileSystem::Default().get(), "sync-file-test", fname));
SyncFileTestState state;
state.sync_result = SyncFileTestResult::kSyncError;
state.close_result = SyncFileTestResult::kCloseError;
SyncFileTestFileSystem fs(FileSystem::Default(), &state);
const IOStatus status = fs.SyncFile(fname, FileOptions(), IOOptions(),
/*use_fsync=*/false, nullptr);
ASSERT_TRUE(status.IsIOError()) << status.ToString();
ASSERT_NE(status.ToString().find("sync failure"), std::string::npos)
<< status.ToString();
ASSERT_EQ(state.reopen_count, 1);
ASSERT_EQ(state.sync_count, 1);
ASSERT_EQ(state.close_count, 1);
ASSERT_OK(FileSystem::Default()->DeleteFile(fname, IOOptions(), nullptr));
}
// Writable file wrapper that injects a Close() failure.
// Uses FSWritableFileOwnerWrapper to properly take ownership of the wrapped
// file.
-21
View File
@@ -16,7 +16,6 @@
#include "rocksdb/utilities/customizable_util.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/utilities/options_type.h"
#include "test_util/sync_point.h"
#include "util/string_util.h"
#include "utilities/counted_fs.h"
#include "utilities/env_timed.h"
@@ -108,26 +107,6 @@ IOStatus FileSystem::ReuseWritableFile(const std::string& fname,
return NewWritableFile(fname, opts, result, dbg);
}
IOStatus FileSystem::SyncFile(const std::string& fname,
const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) {
std::unique_ptr<FSWritableFile> file_to_sync;
IOStatus status = ReopenWritableFile(fname, file_opts, &file_to_sync, dbg);
TEST_SYNC_POINT_CALLBACK("FileSystem::SyncFile:Open", &status);
if (status.ok()) {
status = use_fsync ? file_to_sync->Fsync(io_opts, dbg)
: file_to_sync->Sync(io_opts, dbg);
IOStatus close_status = file_to_sync->Close(io_opts, dbg);
if (status.ok()) {
status = close_status;
} else {
close_status.PermitUncheckedError();
}
}
return status;
}
IOStatus FileSystem::NewLogger(const std::string& fname,
const IOOptions& io_opts,
std::shared_ptr<Logger>* result,
+5 -4
View File
@@ -950,14 +950,15 @@ class PosixFileSystem : public FileSystem {
FileOptions OptimizeForCompactionTableRead(
const FileOptions& file_options,
const ImmutableDBOptions& db_options) const override {
FileOptions fo =
FileSystem::OptimizeForCompactionTableRead(file_options, db_options);
FileOptions fo = FileOptions(file_options);
#ifdef OS_LINUX
// To fix https://github.com/facebook/rocksdb/issues/12038
if (!fo.use_direct_reads && fo.compaction_readahead_size > 0) {
if (!file_options.use_direct_reads &&
file_options.compaction_readahead_size > 0) {
size_t system_limit =
GetCompactionReadaheadSizeSystemLimit(db_options.db_paths);
if (system_limit > 0 && fo.compaction_readahead_size > system_limit) {
if (system_limit > 0 &&
file_options.compaction_readahead_size > system_limit) {
fo.compaction_readahead_size = system_limit;
}
}
-6
View File
@@ -89,12 +89,6 @@ class ReadOnlyFileSystem : public FileSystemWrapper {
IODebugContext* /*dbg*/) override {
return FailReadOnly();
}
IOStatus SyncFile(const std::string& /*fname*/,
const FileOptions& /*file_opts*/,
const IOOptions& /*io_opts*/, bool /*use_fsync*/,
IODebugContext* /*dbg*/) override {
return FailReadOnly();
}
IOStatus LockFile(const std::string& /*fname*/, const IOOptions& /*options*/,
FileLock** /*lock*/, IODebugContext* /*dbg*/) override {
return FailReadOnly();
-12
View File
@@ -309,18 +309,6 @@ IOStatus RemapFileSystem::LinkFile(const std::string& src,
dbg);
}
IOStatus RemapFileSystem::SyncFile(const std::string& fname,
const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) {
auto status_and_enc_path = EncodePathWithNewBasename(fname);
if (!status_and_enc_path.first.ok()) {
return status_and_enc_path.first;
}
return FileSystemWrapper::SyncFile(status_and_enc_path.second, file_opts,
io_opts, use_fsync, dbg);
}
IOStatus RemapFileSystem::LockFile(const std::string& fname,
const IOOptions& options, FileLock** lock,
IODebugContext* dbg) {
-4
View File
@@ -125,10 +125,6 @@ class RemapFileSystem : public FileSystemWrapper {
IOStatus LinkFile(const std::string& src, const std::string& dest,
const IOOptions& options, IODebugContext* dbg) override;
IOStatus SyncFile(const std::string& fname, const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) override;
IOStatus LockFile(const std::string& fname, const IOOptions& options,
FileLock** lock, IODebugContext* dbg) override;
+4 -9
View File
@@ -1116,15 +1116,10 @@ IOStatus PosixRandomAccessFile::ReadAsync(
posix_handle->iov.iov_len = req.len;
// Step 3: io_uring_sqe_set_data
bool skip_get_sqe = false;
TEST_SYNC_POINT_CALLBACK(
"PosixRandomAccessFile::ReadAsync:skip_io_uring_get_sqe", &skip_get_sqe);
struct io_uring_sqe* sqe = nullptr;
if (!skip_get_sqe) {
sqe = io_uring_get_sqe(iu);
TEST_SYNC_POINT_CALLBACK(
"PosixRandomAccessFile::ReadAsync:io_uring_get_sqe", &sqe);
}
struct io_uring_sqe* sqe;
sqe = io_uring_get_sqe(iu);
TEST_SYNC_POINT_CALLBACK("PosixRandomAccessFile::ReadAsync:io_uring_get_sqe",
&sqe);
if (sqe == nullptr) {
// Submission queue is full, so outstanding completions have not been
// reaped yet. Submission never succeeded, so clean up the local handle and
-7
View File
@@ -957,13 +957,6 @@ IOStatus MockFileSystem::LinkFile(const std::string& src,
return IOStatus::OK();
}
IOStatus MockFileSystem::SyncFile(const std::string& /*fname*/,
const FileOptions& /*file_opts*/,
const IOOptions& /*io_opts*/,
bool /*use_fsync*/, IODebugContext* /*dbg*/) {
return IOStatus::OK();
}
IOStatus MockFileSystem::NewLogger(const std::string& fname,
const IOOptions& io_opts,
std::shared_ptr<Logger>* result,
-4
View File
@@ -86,10 +86,6 @@ class MockFileSystem : public FileSystem {
IOStatus LinkFile(const std::string& /*src*/, const std::string& /*target*/,
const IOOptions& /*options*/,
IODebugContext* /*dbg*/) override;
IOStatus SyncFile(const std::string& /*fname*/,
const FileOptions& /*file_opts*/,
const IOOptions& /*io_opts*/, bool /*use_fsync*/,
IODebugContext* /*dbg*/) override;
IOStatus LockFile(const std::string& fname, const IOOptions& options,
FileLock** lock, IODebugContext* dbg) override;
IOStatus UnlockFile(FileLock* lock, const IOOptions& options,
-24
View File
@@ -9,7 +9,6 @@
#include "file/read_write_util.h"
#include <cassert>
#include <sstream>
#include "test_util/sync_point.h"
@@ -26,29 +25,6 @@ IOStatus NewWritableFile(FileSystem* fs, const std::string& fname,
return s;
}
IOStatus GetFileSizeFromOpenFileOrPath(
FSRandomAccessFile* file, FileSystem* fs, const std::string& fname,
uint64_t* file_size, IODebugContext* dbg, FileSizeFallback fallback,
BeforePathFileSizeFallback before_path_fallback) {
assert(file != nullptr);
assert(fs != nullptr);
assert(file_size != nullptr);
IOStatus s = file->GetFileSize(file_size);
if (s.ok()) {
return s;
}
if (fallback == FileSizeFallback::kAnyOpenFileError || s.IsNotSupported()) {
if (before_path_fallback != nullptr) {
before_path_fallback();
}
return fs->GetFileSize(fname, IOOptions(), file_size, dbg);
}
return s;
}
#ifndef NDEBUG
bool IsFileSectorAligned(const size_t off, size_t sector_size) {
return off % sector_size == 0;
-12
View File
@@ -25,18 +25,6 @@ IOStatus NewWritableFile(FileSystem* fs, const std::string& fname,
std::unique_ptr<FSWritableFile>* result,
const FileOptions& options);
enum class FileSizeFallback {
kNotSupportedOnly,
kAnyOpenFileError,
};
using BeforePathFileSizeFallback = void (*)();
IOStatus GetFileSizeFromOpenFileOrPath(
FSRandomAccessFile* file, FileSystem* fs, const std::string& fname,
uint64_t* file_size, IODebugContext* dbg, FileSizeFallback fallback,
BeforePathFileSizeFallback before_path_fallback = nullptr);
#ifndef NDEBUG
bool IsFileSectorAligned(const size_t off, size_t sector_size);
#endif // NDEBUG
+1 -1
View File
@@ -1364,7 +1364,7 @@ struct AdvancedColumnFamilyOptions {
// operation.
// This option depends on memtable_protection_bytes_per_key to be non zero.
// If memtable_protection_bytes_per_key is zero, no validation is performed.
bool memtable_verify_per_key_checksum_on_seek = false;
bool memtable_veirfy_per_key_checksum_on_seek = false;
// When an iterator scans this number of invisible entries (tombstones or
// hidden puts) from the active memtable during a single iterator operation,
-35
View File
@@ -273,12 +273,6 @@ extern ROCKSDB_LIBRARY_API uint32_t rocksdb_backup_engine_info_number_files(
extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_info_destroy(
const rocksdb_backup_engine_info_t* info);
/* Cancels any in-progress backup. This is a one-way operation: once called,
all future CreateNewBackup requests on this engine will fail with
Status::Incomplete. Open a new BackupEngine instance to resume backups. */
extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_stop_backup(
rocksdb_backup_engine_t* be);
extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_close(
rocksdb_backup_engine_t* be);
@@ -385,18 +379,6 @@ extern ROCKSDB_LIBRARY_API uint64_t
rocksdb_backup_engine_options_get_restore_rate_limit(
rocksdb_backup_engine_options_t* options);
/* Sets a RateLimiter for backup transfers. When non-null, overrides the
uint64_t backup_rate_limit value. */
extern ROCKSDB_LIBRARY_API void
rocksdb_backup_engine_options_set_backup_rate_limiter(
rocksdb_backup_engine_options_t* options, rocksdb_ratelimiter_t* limiter);
/* Sets a RateLimiter for restore transfers. When non-null, overrides the
uint64_t restore_rate_limit value. */
extern ROCKSDB_LIBRARY_API void
rocksdb_backup_engine_options_set_restore_rate_limiter(
rocksdb_backup_engine_options_t* options, rocksdb_ratelimiter_t* limiter);
extern ROCKSDB_LIBRARY_API void
rocksdb_backup_engine_options_set_max_background_operations(
rocksdb_backup_engine_options_t* options, int val);
@@ -1452,10 +1434,6 @@ extern ROCKSDB_LIBRARY_API void rocksdb_set_options(rocksdb_t* db, int count,
const char* const values[],
char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_set_db_options(
rocksdb_t* db, int count, const char* const keys[],
const char* const values[], char** errptr);
extern ROCKSDB_LIBRARY_API void rocksdb_set_options_cf(
rocksdb_t* db, rocksdb_column_family_handle_t* handle, int count,
const char* const keys[], const char* const values[], char** errptr);
@@ -1912,11 +1890,6 @@ rocksdb_options_set_use_direct_io_for_flush_and_compaction(rocksdb_options_t*,
unsigned char);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_use_direct_io_for_flush_and_compaction(rocksdb_options_t*);
extern ROCKSDB_LIBRARY_API void
rocksdb_options_set_use_direct_io_for_compaction_reads(rocksdb_options_t*,
unsigned char);
extern ROCKSDB_LIBRARY_API unsigned char
rocksdb_options_get_use_direct_io_for_compaction_reads(rocksdb_options_t*);
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_is_fd_close_on_exec(
rocksdb_options_t*, unsigned char);
extern ROCKSDB_LIBRARY_API unsigned char
@@ -3473,14 +3446,6 @@ extern ROCKSDB_LIBRARY_API void
rocksdb_transactiondb_options_set_default_lock_timeout(
rocksdb_transactiondb_options_t* opt, int64_t default_lock_timeout);
enum {
rocksdb_txndb_write_policy_write_committed = 0,
rocksdb_txndb_write_policy_write_prepared = 1,
rocksdb_txndb_write_policy_write_unprepared = 2
};
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_options_set_write_policy(
rocksdb_transactiondb_options_t* opt, int write_policy);
extern ROCKSDB_LIBRARY_API rocksdb_transaction_options_t*
rocksdb_transaction_options_create(void);
+4 -22
View File
@@ -22,8 +22,6 @@ enum CompressionType : unsigned char {
kSnappyCompression = 0x01,
kZlibCompression = 0x02,
kBZip2Compression = 0x03,
// NOTE: LZ4 and LZ4HC should be considered variants of the same compression
// type. See CompressionOptions::level
kLZ4Compression = 0x04,
kLZ4HCCompression = 0x05,
kXpressCompression = 0x06,
@@ -190,26 +188,10 @@ struct CompressionOptions {
// `kDefaultCompressionLevel` values will either favor speed over
// compression ratio or have no effect.
//
// LZ4 and LZ4HC share one on-disk format, so they expose a single monotonic
// `level` axis: `level <= 0` selects LZ4 fast with `acceleration = -level`
// (e.g. -10 means acceleration 10, 0 is clamped to minimum acceleration 1),
// and `level >= 1` selects LZ4HC at that level (1..12). The configured type
// (kLZ4Compression vs kLZ4HCCompression) only sets the default `level` and
// the compression type byte recorded per block. Defaults are acceleration 1
// (level -1) for LZ4 and level 9 for LZ4HC. Levels outside the
// algorithm-recognized ranges are clamped to the nearest effective value:
// below -65537 acts like -65537 (max acceleration) and above 12 like 12 (max
// compression effort).
//
// For ZSTD (the other generally recommended compression alongside LZ4),
// `level` follows zstd's own scale, where higher values trade more CPU for a
// better compression ratio. The standard range is 1 (fastest) through 19,
// with 20..22 being "ultra" levels that require substantially more memory to
// compress (and decompress). zstd also supports negative levels (-1 and
// below) that favor speed over ratio, analogous to LZ4 acceleration.
// RocksDB's default (kDefaultCompressionLevel) maps to zstd's default of 3.
// (zstd itself treats a `level` of 0 as "use the default", but prefer
// kDefaultCompressionLevel to request that.)
// In LZ4 specifically, the absolute value of a negative `level` internally
// configures the `acceleration` parameter. For example, set `level=-10` for
// `acceleration=10`. This negation is necessary to ensure decreasing `level`
// values favor speed over compression ratio.
int level = kDefaultCompressionLevel;
// zlib only: strategy parameter. See https://www.zlib.net/manual.html
-88
View File
@@ -91,32 +91,6 @@ class ColumnFamilyHandle {
virtual const Comparator* GetComparator() const = 0;
};
// Opaque handle to a prepared-but-not-committed external file ingestion,
// produced by DB::PrepareFileIngestion(). Pass it to
// DB::CommitFileIngestionHandle(s)() to make the prepared files visible, or
// call Abort() (or simply destroy the handle) to roll the ingestion back: that
// deletes the staged files and releases the reserved internal file numbers.
//
// A handle MUST NOT outlive the DB that produced it.
class FileIngestionHandle {
public:
virtual ~FileIngestionHandle() = default;
// Cancel this prepared ingestion: delete the staged files and release the
// reserved file numbers, leaving the DB unchanged. The handle is spent
// afterwards. Equivalent to simply destroying the handle, but explicit and
// returns a Status.
virtual Status Abort() = 0;
FileIngestionHandle(const FileIngestionHandle&) = delete;
FileIngestionHandle& operator=(const FileIngestionHandle&) = delete;
FileIngestionHandle(FileIngestionHandle&&) = delete;
FileIngestionHandle& operator=(FileIngestionHandle&&) = delete;
protected:
FileIngestionHandle() = default;
};
static const int kMajorVersion = ROCKSDB_MAJOR;
static const int kMinorVersion = ROCKSDB_MINOR;
@@ -478,8 +452,6 @@ class DB {
// Set the database entry for "key" in the column family specified by
// "column_family" to the wide-column entity defined by "columns". If the key
// already exists in the column family, it will be overwritten.
// `columns` is a non-owning view. The backing storage for each column name
// and value must remain valid until this method returns.
//
// Returns OK on success, and a non-OK status on error.
virtual Status PutEntity(const WriteOptions& options,
@@ -1952,14 +1924,6 @@ class DB {
const LiveFilesStorageInfoOptions& opts,
std::vector<LiveFileStorageInfo>* files) = 0;
// Prepares a live DB-generated table file for use with IngestExternalFiles().
// See `PreparedFileInfo` for details. The path must exactly match a live
// table file owned by this DB. This may open the table file and populate this
// DB's table cache if the table reader is not already cached.
virtual Status GetPreparedFileInfoForExternalSstIngestion(
const std::string& /*file_path*/,
std::shared_ptr<const PreparedFileInfo>* /*file_info*/) = 0;
// Obtains the LSM-tree meta data of the specified column family of the DB,
// including metadata for each live table (SST) file in that column family.
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
@@ -2093,58 +2057,6 @@ class DB {
virtual Status IngestExternalFiles(
const std::vector<IngestExternalFileArg>& args) = 0;
// Two-phase external file ingestion. PrepareFileIngestion() performs all of
// the work that does not require the DB mutex (validating the args, reserving
// internal file numbers, reading each file's metadata, and
// linking/copying/fsyncing the files into the DB), returning an opaque
// FileIngestionHandle. DB::CommitFileIngestionHandle(s)() then commits the
// external file into the DB.
//
// The main advantage here is that the application has more flexibility to
// "Prepare" the file in advance, which makes the actual Commit phase much
// shorter than a typical DB::IngestExternalFiles call.
//
// `args` has the same requirements as IngestExternalFiles(). On success
// *handle holds the prepared ingestion; on failure *handle is reset and no DB
// state is changed. PrepareFileIngestion() is safe to call concurrently with
// other DB operations and from a background thread.
virtual Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& args,
std::unique_ptr<FileIngestionHandle>* handle) = 0;
// Single-column-family convenience overload of PrepareFileIngestion().
virtual Status PrepareFileIngestion(
ColumnFamilyHandle* column_family,
const std::vector<std::string>& external_files,
const IngestExternalFileOptions& options,
std::unique_ptr<FileIngestionHandle>* handle) {
IngestExternalFileArg arg;
arg.column_family = column_family;
arg.external_files = external_files;
arg.options = options;
return PrepareFileIngestion({arg}, handle);
}
// Commits one or more prepared ingestions (from PrepareFileIngestion())
// atomically: all of their files become visible together under a single DB
// mutex acquisition and a single atomic MANIFEST write, or none do. Consumes
// the handles. Multiple handles MAY target the same column family; that
// column family's files are committed together as if all of them had been
// passed to a single IngestExternalFiles() call, in the order the handles
// appear in `handles` (so for overlapping keys a later handle's data wins).
// Handles that target the same column family must have been prepared with the
// same IngestExternalFileOptions. On failure all handles are rolled back.
virtual Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> handles) = 0;
// Single-handle convenience for CommitFileIngestionHandles().
virtual Status CommitFileIngestionHandle(
std::unique_ptr<FileIngestionHandle> handle) {
std::vector<std::unique_ptr<FileIngestionHandle>> handles;
handles.push_back(std::move(handle));
return CommitFileIngestionHandles(std::move(handles));
}
// CreateColumnFamilyWithImport() will create a new column family with
// column_family_name and import external SST files specified in `metadata`
// into this column family.
-13
View File
@@ -385,14 +385,6 @@ class Env : public Customizable {
return Status::NotSupported("LinkFile is not supported for this Env");
}
// Syncs file data and, when requested, file metadata for `fname`.
//
// See FileSystem::SyncFile() for the corresponding path-level sync contract.
// The default implementation reopens the file as writable, calls Sync() or
// Fsync(), then closes it.
virtual Status SyncFile(const std::string& fname, const EnvOptions& options,
bool use_fsync);
virtual Status NumFileLinks(const std::string& /*fname*/,
uint64_t* /*count*/) {
return Status::NotSupported(
@@ -1702,11 +1694,6 @@ class EnvWrapper : public Env {
return target_.env->LinkFile(s, t);
}
Status SyncFile(const std::string& fname, const EnvOptions& options,
bool use_fsync) override {
return target_.env->SyncFile(fname, options, use_fsync);
}
Status NumFileLinks(const std::string& fname, uint64_t* count) override {
return target_.env->NumFileLinks(fname, count);
}
+4 -59
View File
@@ -119,8 +119,7 @@ struct IOOptions {
// custom contract between a FileSystem user and the provider. This is only
// useful in cases where a RocksDB user directly uses the FileSystem or file
// object for their own purposes, and wants to pass extra options to APIs
// such as NewRandomAccessFile and NewWritableFile. Prefer typed fields on
// IOOptions/FileOptions when a standardized semantic exists.
// such as NewRandomAccessFile and NewWritableFile.
std::unordered_map<std::string, std::string> property_bag;
// Force directory fsync, some file systems like btrfs may skip directory
@@ -177,37 +176,9 @@ struct DirFsyncOptions {
explicit DirFsyncOptions(FsyncReason fsync_reason);
};
// File-scope contracts that describe how RocksDB will open/create and access a
// file. A FileSystem that detects a contract violation must return a non-OK
// status or preserve its normal valid semantics; it must not expose undefined
// or corrupt data.
enum class FileOpenContract : uint8_t {
kDefault = 0,
// RocksDB will not call ReopenWritableFile for this file to overwrite,
// append, or truncate data. SyncFile is a separate durability API.
kNoReopenForWrite = 1 << 0,
// RocksDB will not open readers while this file is open for write.
kNoReadersWhileOpenForWrite = 1 << 1,
};
constexpr FileOpenContract operator|(FileOpenContract lhs,
FileOpenContract rhs) {
return static_cast<FileOpenContract>(static_cast<uint8_t>(lhs) |
static_cast<uint8_t>(rhs));
}
inline FileOpenContract& operator|=(FileOpenContract& lhs,
FileOpenContract rhs) {
lhs = lhs | rhs;
return lhs;
}
constexpr bool HasFileOpenContract(FileOpenContract contracts,
FileOpenContract contract) {
return (static_cast<uint8_t>(contracts) & static_cast<uint8_t>(contract)) ==
static_cast<uint8_t>(contract);
}
// File scope options that control how a file is opened/created and accessed
// while its open. We may add more options here in the future such as
// redundancy level, media to use etc.
struct FileOptions : EnvOptions {
// Embedded IOOptions to control the parameters for any IOs that need
// to be issued for the file open/creation
@@ -220,9 +191,6 @@ struct FileOptions : EnvOptions {
// coding.
Temperature temperature = Temperature::kUnknown;
// File-open contract. kDefault uses the provider's standard semantics.
FileOpenContract open_contract = FileOpenContract::kDefault;
// The checksum type that is used to calculate the checksum value for
// handoff during file writes.
ChecksumType handoff_checksum_type;
@@ -268,7 +236,6 @@ struct FileOptions : EnvOptions {
: EnvOptions(opts),
io_options(opts.io_options),
temperature(opts.temperature),
open_contract(opts.open_contract),
handoff_checksum_type(opts.handoff_checksum_type),
write_hint(opts.write_hint),
file_checksum(opts.file_checksum),
@@ -658,22 +625,6 @@ class FileSystem : public Customizable {
"LinkFile is not supported for this FileSystem");
}
// Syncs file data and, when requested, file metadata for `fname`.
//
// The default implementation reopens the file as writable, calls Sync() or
// Fsync(), then closes it. Filesystems that already make file contents
// durable on file flush/close can override this as a no-op to avoid the
// reopen overhead.
//
// RocksDB code that needs to sync a named file should use this API instead of
// hand-rolling ReopenWritableFile()+Sync()/Fsync(). This lets filesystems
// reject ReopenWritableFile() for post-close data writes while still
// providing a cleaner path-level sync implementation.
virtual IOStatus SyncFile(const std::string& fname,
const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg);
virtual IOStatus NumFileLinks(const std::string& /*fname*/,
const IOOptions& /*options*/,
uint64_t* /*count*/, IODebugContext* /*dbg*/) {
@@ -1691,12 +1642,6 @@ class FileSystemWrapper : public FileSystem {
return target_->LinkFile(s, t, options, dbg);
}
IOStatus SyncFile(const std::string& fname, const FileOptions& file_opts,
const IOOptions& io_opts, bool use_fsync,
IODebugContext* dbg) override {
return target_->SyncFile(fname, file_opts, io_opts, use_fsync, dbg);
}
IOStatus NumFileLinks(const std::string& fname, const IOOptions& options,
uint64_t* count, IODebugContext* dbg) override {
return target_->NumFileLinks(fname, options, count, dbg);
+18 -98
View File
@@ -192,24 +192,28 @@ struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {
// Compress blocks using the specified compression algorithm.
//
// Default: kLZ4Compression if support is compiled in, else kSnappyCompression
// if support is compiled in, else kNoCompression
// Default: kSnappyCompression, if it's supported. If snappy is not linked
// with the library, the default is kNoCompression.
//
// Typical single-core speed of kLZ4Compression on an AMD EPYC-Genoa
// ~800 - 1200 MB/s compression
// ~8000 - 16000 MB/s decompression
// and with a 160 threads to saturate the cores:
// ~60 - 90 GB/s compression
// ~900 - 1000 GB/s decompression
// using db_bench compress/uncompress benchmarks.
// Typical speeds of kSnappyCompression on an Intel(R) Core(TM)2 2.4GHz:
// ~200-500MB/s compression
// ~400-800MB/s decompression
//
// Note that these speeds are significantly faster than most
// persistent storage speeds, and therefore it is typically never
// worth switching to kNoCompression. Even if the input data is
// incompressible, the compression implementation will
// efficiently detect that and fall back on no compression.
// incompressible, the kSnappyCompression implementation will
// efficiently detect that and will switch to uncompressed mode.
//
// See CompressionOptions for more options.
// If you do not set `compression_opts.level`, or set it to
// `CompressionOptions::kDefaultCompressionLevel`, we will attempt to pick the
// default corresponding to `compression` as follows:
//
// - kZSTD: 3
// - kZlibCompression: Z_DEFAULT_COMPRESSION (currently -1)
// - kLZ4HCCompression: 0
// - kLZ4: -1 (i.e., `acceleration=1`; see `CompressionOptions::level` doc)
// - For all others, we do not specify a compression level
//
// Dynamically changeable through SetOptions() API
CompressionType compression;
@@ -618,7 +622,7 @@ struct DBOptions {
// checking for corruption, including
// * paranoid_file_checks
// * paranoid_memory_checks
// * memtable_verify_per_key_checksum_on_seek
// * memtable_veirfy_per_key_checksum_on_seek
// * DB::VerifyChecksum()
//
// Default: true
@@ -1147,46 +1151,7 @@ struct DBOptions {
// Default: false
bool use_direct_reads = false;
// Use O_DIRECT for compaction-input SST reads only, leaving user reads
// buffered. Useful when sequential compaction reads would otherwise evict
// the hot user-read working set from the OS page cache. When this is true
// and use_direct_reads is false, compaction opens short-lived O_DIRECT
// readers for its input files instead of reusing the buffered readers cached
// for user reads. This is the read-side analogue of
// use_direct_io_for_flush_and_compaction, and the two are often paired on
// write-heavy workloads.
//
// Scope and limits:
// * DBOption scope (applies to all column families); no per-CF setting.
// * Covers compaction inputs only. Blob-file reads and compaction-output
// verification (paranoid_file_checks) still use the buffered path.
// * The ephemeral readers bypass the TableCache and are not counted against
// max_open_files. Non-L0 levels keep one reader open at a time; L0 opens
// all of a subcompaction's overlapping inputs at once, so with large L0
// fan-in and many subcompactions, watch RLIMIT_NOFILE.
// * Every input file is reopened per compaction, so NO_FILE_OPENS and
// TABLE_OPEN_IO_MICROS rise while this is enabled.
//
// The same SST can be open through both a buffered handle (user reads) and an
// O_DIRECT handle (the compaction scan) at once; modern Linux handles this
// fine. The flag is neutral or slightly negative for in-memory DBs or
// uniform random reads, so measure before enabling.
//
// Has no effect when use_direct_reads is true (all reads are already
// O_DIRECT). Rejected at DB::Open when allow_mmap_reads is set.
//
// On a filesystem without O_DIRECT support (e.g. tmpfs), DB::Open fails: it
// probes by opening the MANIFEST with O_DIRECT. The probe only checks the
// filesystem holding the DB directory, so if SST files live elsewhere (via
// db_paths/cf_paths) without O_DIRECT, Open succeeds and the first compaction
// fails instead.
//
// Default: false
bool use_direct_io_for_compaction_reads = false;
// Use O_DIRECT for writes in background flush and compactions. See also
// use_direct_io_for_compaction_reads, the read-side analogue often paired
// with this on write-heavy workloads.
// Use O_DIRECT for writes in background flush and compactions.
// Default: false
bool use_direct_io_for_flush_and_compaction = false;
@@ -2050,21 +2015,6 @@ class MultiScanArgs {
size_t size() const { return original_ranges_.size(); }
bool empty() const { return original_ranges_.empty(); }
bool HasBoundedScanRanges() const {
if (empty()) {
return false;
}
for (const ScanOptions& scan_range : original_ranges_) {
if (!scan_range.range.start.has_value() ||
!scan_range.range.limit.has_value()) {
return false;
}
}
return true;
}
void reserve(size_t size) { original_ranges_.reserve(size); }
operator std::vector<ScanOptions>*() { return &original_ranges_; }
@@ -2951,30 +2901,8 @@ struct IngestExternalFileOptions {
// When ingesting to multiple families, this option should be the same across
// ingestion options.
bool fill_cache = true;
// Controls whether external file ingestion should prefetch index and filter
// blocks while opening table readers during commit. Setting this to false can
// reduce commit latency for bulk loads into Lmax when
// (BlockBasedTableOptions::cache_index_and_filter_blocks=true or partitioned
// filters/indexes are enabled).
bool prefetch_lmax_index_and_filter_blocks = true;
// Maximum number of threads used to open table readers for the files being
// ingested during commit, can speed up ingestion performance, when ingesting
// multiple files at once.
int file_opening_threads = 1;
bool operator==(const IngestExternalFileOptions& rhs) const = default;
};
// Opaque per-file metadata accepted through IngestExternalFileArg::file_infos.
// Supplying it lets DB::IngestExternalFiles() and DB::PrepareFileIngestion()
// prepare ingestion without re-opening and scanning the SST to recompute file
// metadata. It is produced by SstFileWriter::Finish() for writer-created files
// or by DB::GetPreparedFileInfoForExternalSstIngestion() for live DB-generated
// files.
struct PreparedFileInfo;
// It is valid that files_checksums and files_checksum_func_names are both
// empty (no checksum information is provided for ingestion). Otherwise,
// their sizes should be the same as external_files. The file order should
@@ -3011,14 +2939,6 @@ struct IngestExternalFileArg {
// exclusive, so it is best not to depend on one or the other until it is
// sorted out.
std::optional<RangeOpt> atomic_replace_range;
// Optimizes for prepare performance, see `PreparedFileInfo` for details.
// The owning handle must stay alive until ingestion completes.
// Not compatible with options.write_global_seqno (the file is not opened, so
// a global seqno cannot be written back into it). Because the file is not
// opened, ingestion does not read its storage temperature and trusts the
// caller-supplied `file_temperature` hint as-is.
std::vector<const PreparedFileInfo*> file_infos;
};
enum TraceFilterType : uint64_t {
+3 -10
View File
@@ -34,8 +34,7 @@ struct ExternalSstFileInfo {
file_size(0),
num_entries(0),
num_range_del_entries(0),
version(0),
prepared_file_info(nullptr) {}
version(0) {}
ExternalSstFileInfo(const std::string& _file_path,
const std::string& _smallest_key,
@@ -53,8 +52,7 @@ struct ExternalSstFileInfo {
file_size(_file_size),
num_entries(_num_entries),
num_range_del_entries(0),
version(_version),
prepared_file_info(nullptr) {}
version(_version) {}
std::string file_path; // external sst file path
std::string smallest_key; // smallest user key in file
@@ -69,9 +67,6 @@ struct ExternalSstFileInfo {
uint64_t num_entries; // number of entries in file
uint64_t num_range_del_entries; // number of range deletion entries in file
int32_t version; // file version
// Opaque metadata that can be passed to IngestExternalFileArg::file_infos to
// skip re-opening and scanning the file during ingestion.
std::shared_ptr<const PreparedFileInfo> prepared_file_info;
};
// SstFileWriter is used to create sst files that can be added to database later
@@ -122,9 +117,7 @@ class SstFileWriter {
Status Put(const Slice& user_key, const Slice& timestamp, const Slice& value);
// Add a PutEntity (key with the wide-column entity defined by "columns") to
// the currently opened file. `columns` is a non-owning view, so the backing
// storage for each column name and value must remain valid until this method
// returns.
// the currently opened file
Status PutEntity(const Slice& user_key, const WideColumns& columns);
// Add a Merge key with value to currently opened file
-10
View File
@@ -765,16 +765,6 @@ enum Histograms : uint32_t {
// blocks for uniform key distribution tracking.
BLOCK_KEY_DISTRIBUTION_CV,
// Time (microseconds) in the "prepare" phase of an IngestExternalFile(s) call
// (validation and reading/linking the files; does not block writes). One
// sample per successful call; requires stats level > kExceptTimers.
INGEST_EXTERNAL_FILE_PREPARE_TIME,
// Time (microseconds) in the "run" phase of an IngestExternalFile(s) call
// (work under the DB mutex while writes are blocked). One sample per
// successful call; requires stats level > kExceptTimers.
INGEST_EXTERNAL_FILE_RUN_TIME,
HISTOGRAM_ENUM_MAX
};
@@ -31,9 +31,8 @@ struct SortedRunBuilderOptions {
uint64_t target_file_size_bytes = 64 * 1024 * 1024;
// Compression type for output SST files.
// Default: kNoCompression (always available). Set to kLZ4Compression,
// kSnappyCompression, kZSTD, etc. if the desired library is linked with
// your binary.
// Default: kNoCompression (always available). Set to kSnappyCompression,
// kZSTD, etc. if the desired library is linked with your binary.
CompressionType compression = kNoCompression;
// Max number of background compaction threads.
-20
View File
@@ -176,19 +176,6 @@ class StackableDB : public DB {
return db_->IngestExternalFiles(args);
}
using DB::PrepareFileIngestion;
Status PrepareFileIngestion(
const std::vector<IngestExternalFileArg>& args,
std::unique_ptr<FileIngestionHandle>* handle) override {
return db_->PrepareFileIngestion(args, handle);
}
using DB::CommitFileIngestionHandles;
Status CommitFileIngestionHandles(
std::vector<std::unique_ptr<FileIngestionHandle>> handles) override {
return db_->CommitFileIngestionHandles(std::move(handles));
}
using DB::CreateColumnFamilyWithImport;
Status CreateColumnFamilyWithImport(
const ColumnFamilyOptions& options, const std::string& column_family_name,
@@ -461,13 +448,6 @@ class StackableDB : public DB {
return db_->GetLiveFilesStorageInfo(opts, files);
}
Status GetPreparedFileInfoForExternalSstIngestion(
const std::string& file_path,
std::shared_ptr<const PreparedFileInfo>* file_info) override {
return db_->GetPreparedFileInfoForExternalSstIngestion(file_path,
file_info);
}
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
ColumnFamilyMetaData* cf_meta) override {
db_->GetColumnFamilyMetaData(column_family, cf_meta);
-2
View File
@@ -549,8 +549,6 @@ class Transaction {
const bool assume_tracked = false) = 0;
virtual Status Put(const SliceParts& key, const SliceParts& value) = 0;
// `columns` is a non-owning view, so the backing storage for each column
// name and value must remain valid until this method returns.
virtual Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns,
bool assume_tracked = false) = 0;
+2 -2
View File
@@ -12,8 +12,8 @@
// NOTE: in 'main' development branch, this should be the *next*
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 11
#define ROCKSDB_MINOR 5
#define ROCKSDB_PATCH 2
#define ROCKSDB_MINOR 4
#define ROCKSDB_PATCH 3
// Make it easy to do conditional compilation based on version checks, i.e.
// #if ROCKSDB_VERSION_GE(4, 5, 6)
+9 -31
View File
@@ -21,47 +21,25 @@ class DBImpl;
// Class representing a wide column, which is defined as a pair of column name
// and column value.
//
// WideColumn is a non-owning view. Both the column name and column value are
// stored as Slices that reference caller-managed memory. The backing storage
// must remain valid for as long as the WideColumn is used. In particular,
// passing temporary std::string objects is unsafe. When passing WideColumn or
// WideColumns to APIs like PutEntity(), keep the backing storage alive until
// that method returns.
class WideColumn {
public:
WideColumn() = default;
// Initializes a WideColumn object by forwarding the name and value
// arguments to the corresponding member Slices without copying the bytes.
// The resulting WideColumn does not own the referenced data. Construction is
// decoupled from the eventual PutEntity() call, but lifetime is not: any
// buffers referenced here must remain valid until the PutEntity() call that
// consumes this WideColumn or WideColumns collection returns. This makes it
// possible to construct a WideColumn using combinations of const char*,
// const std::string&, const Slice& etc., for example:
// arguments to the corresponding member Slices. This makes it possible to
// construct a WideColumn using combinations of const char*, const
// std::string&, const Slice& etc., for example:
//
// std::string column_name = "attr";
// std::string column_value = "value";
// WideColumn column(column_name, column_value);
// WideColumns columns{column};
// ASSERT_OK(db->PutEntity(write_options, column_family, key, columns));
// // column_name and column_value must stay alive until PutEntity() returns.
//
// // Unsafe: the temporary std::string storage is destroyed before
// PutEntity(). WideColumns bad_columns{
// {std::string("attr"), std::string("value")},
// };
// constexpr char foo[] = "foo";
// const std::string bar("bar");
// WideColumn column(foo, bar);
template <typename N, typename V>
WideColumn(N&& name, V&& value)
: name_(std::forward<N>(name)), value_(std::forward<V>(value)) {}
// Initializes a WideColumn object by forwarding the elements of name_tuple
// and value_tuple to the constructors of the corresponding member Slices
// without copying the bytes. As above, the caller retains ownership of the
// referenced bytes, so any buffers used here must stay alive for as long as
// the WideColumn is used and until any consuming PutEntity() call returns.
// This makes it possible to initialize the Slices using the Slice
// Initializes a WideColumn object by forwarding the elements of
// name_tuple and value_tuple to the constructors of the corresponding member
// Slices. This makes it possible to initialize the Slices using the Slice
// constructors that take more than one argument, for example:
//
// constexpr char foo_name[] = "foo_name";
+1 -2
View File
@@ -109,8 +109,7 @@ class WriteBatch : public WriteBatchBase {
const Slice& value, uint64_t write_unix_time) override;
// Store the mapping "key->{column1:value1, column2:value2, ...}" in the
// column family specified by "column_family". See
// WriteBatchBase::PutEntity() for the lifetime requirements on `columns`.
// column family specified by "column_family".
using WriteBatchBase::PutEntity;
Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) override;
+1 -3
View File
@@ -59,9 +59,7 @@ class WriteBatchBase {
const Slice& value, uint64_t write_unix_time) = 0;
// Store the mapping "key->{column1:value1, column2:value2, ...}" in the
// column family specified by "column_family". `columns` is a non-owning
// view, so the backing storage for each column name and value must remain
// valid until this method returns.
// column family specified by "column_family".
virtual Status PutEntity(ColumnFamilyHandle* column_family, const Slice& key,
const WideColumns& columns) = 0;
-8
View File
@@ -5998,10 +5998,6 @@ class HistogramTypeJni {
return 0x41;
case ROCKSDB_NAMESPACE::Histograms::BLOCK_KEY_DISTRIBUTION_CV:
return 0x42;
case ROCKSDB_NAMESPACE::Histograms::INGEST_EXTERNAL_FILE_PREPARE_TIME:
return 0x43;
case ROCKSDB_NAMESPACE::Histograms::INGEST_EXTERNAL_FILE_RUN_TIME:
return 0x44;
case ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX:
// 0x3E is reserved for backwards compatibility on current minor
// version.
@@ -6155,10 +6151,6 @@ class HistogramTypeJni {
return ROCKSDB_NAMESPACE::Histograms::MULTISCAN_BLOCKS_PER_PREPARE;
case 0x42:
return ROCKSDB_NAMESPACE::Histograms::BLOCK_KEY_DISTRIBUTION_CV;
case 0x43:
return ROCKSDB_NAMESPACE::Histograms::INGEST_EXTERNAL_FILE_PREPARE_TIME;
case 0x44:
return ROCKSDB_NAMESPACE::Histograms::INGEST_EXTERNAL_FILE_RUN_TIME;
case 0x3E:
// 0x3E is reserved for backwards compatibility on current minor
// version.
@@ -33,7 +33,7 @@ public class CompactionOptions extends RocksObject {
/**
* Set the compaction output compression type.
*
* Default: lz4 if available, otherwise snappy if available, otherwise none
* Default: snappy
*
* If set to {@link CompressionType#DISABLE_COMPRESSION_OPTION},
* RocksDB will choose compression type according to the
@@ -231,16 +231,6 @@ public enum HistogramType {
*/
BLOCK_KEY_DISTRIBUTION_CV((byte) 0x42),
/**
* Time (us) in the prepare phase of an IngestExternalFile(s) call.
*/
INGEST_EXTERNAL_FILE_PREPARE_TIME((byte) 0x43),
/**
* Time (us) in the run phase of an IngestExternalFile(s) call.
*/
INGEST_EXTERNAL_FILE_RUN_TIME((byte) 0x44),
// 0x3E is reserved for backwards compatibility on current minor version.
HISTOGRAM_ENUM_MAX((byte) 0x3E);
@@ -136,8 +136,7 @@ public interface MutableColumnFamilyOptionsInterface<
* Compress blocks using the specified compression algorithm. This
* parameter can be changed dynamically.
* <p>
* Default: LZ4_COMPRESSION if available, otherwise SNAPPY_COMPRESSION if
* available, otherwise NO_COMPRESSION.
* Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression.
*
* @param compressionType Compression Type.
* @return the reference to the current option.
@@ -149,8 +148,7 @@ public interface MutableColumnFamilyOptionsInterface<
* Compress blocks using the specified compression algorithm. This
* parameter can be changed dynamically.
* <p>
* Default: LZ4_COMPRESSION if available, otherwise SNAPPY_COMPRESSION if
* available, otherwise NO_COMPRESSION.
* Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression.
*
* @return Compression type.
*/
-3
View File
@@ -386,9 +386,6 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
{MULTISCAN_PREPARE_MICROS, "rocksdb.multiscan.prepare.micros"},
{MULTISCAN_BLOCKS_PER_PREPARE, "rocksdb.multiscan.blocks.per.prepare"},
{BLOCK_KEY_DISTRIBUTION_CV, "rocksdb.block.key.distribution.cv"},
{INGEST_EXTERNAL_FILE_PREPARE_TIME,
"rocksdb.ingest.external.file.prepare.micros"},
{INGEST_EXTERNAL_FILE_RUN_TIME, "rocksdb.ingest.external.file.run.micros"},
};
std::shared_ptr<Statistics> CreateDBStatistics() {
+3 -9
View File
@@ -702,15 +702,9 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct MutableCFOptions, paranoid_memory_checks),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
// Keep parsing the old misspelled name for existing OPTIONS files.
{"memtable_veirfy_per_key_checksum_on_seek",
{offsetof(struct MutableCFOptions,
memtable_verify_per_key_checksum_on_seek),
OptionType::kBoolean, OptionVerificationType::kAlias,
OptionTypeFlags::kMutable}},
{"memtable_verify_per_key_checksum_on_seek",
{offsetof(struct MutableCFOptions,
memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kMutable}},
{kOptNameCompOpts,
@@ -1264,8 +1258,8 @@ void MutableCFOptions::Dump(Logger* log) const {
preserve_internal_time_seconds);
ROCKS_LOG_INFO(log, " paranoid_memory_checks: %d",
paranoid_memory_checks);
ROCKS_LOG_INFO(log, "memtable_verify_per_key_checksum_on_seek: %d",
memtable_verify_per_key_checksum_on_seek);
ROCKS_LOG_INFO(log, "memtable_veirfy_per_key_checksum_on_seek: %d",
memtable_veirfy_per_key_checksum_on_seek);
std::string result;
char buf[10];
for (const auto m : max_bytes_for_level_multiplier_additional) {
+5 -5
View File
@@ -191,8 +191,8 @@ struct MutableCFOptions {
options.memtable_protection_bytes_per_key),
block_protection_bytes_per_key(options.block_protection_bytes_per_key),
paranoid_memory_checks(options.paranoid_memory_checks),
memtable_verify_per_key_checksum_on_seek(
options.memtable_verify_per_key_checksum_on_seek),
memtable_veirfy_per_key_checksum_on_seek(
options.memtable_veirfy_per_key_checksum_on_seek),
sample_for_compression(
options.sample_for_compression), // TODO: is 0 fine here?
compression_per_level(options.compression_per_level),
@@ -253,14 +253,14 @@ struct MutableCFOptions {
max_sequential_skip_in_iterations(0),
paranoid_file_checks(false),
report_bg_io_stats(false),
compression(GetDefaultCompressionType()),
compression(Snappy_Supported() ? kSnappyCompression : kNoCompression),
bottommost_compression(kDisableCompressionOption),
last_level_temperature(Temperature::kUnknown),
default_write_temperature(Temperature::kUnknown),
memtable_protection_bytes_per_key(0),
block_protection_bytes_per_key(0),
paranoid_memory_checks(false),
memtable_verify_per_key_checksum_on_seek(false),
memtable_veirfy_per_key_checksum_on_seek(false),
sample_for_compression(0),
memtable_max_range_deletions(0),
bottommost_file_compaction_delay(0),
@@ -372,7 +372,7 @@ struct MutableCFOptions {
uint32_t memtable_protection_bytes_per_key;
uint8_t block_protection_bytes_per_key;
bool paranoid_memory_checks;
bool memtable_verify_per_key_checksum_on_seek;
bool memtable_veirfy_per_key_checksum_on_seek;
uint64_t sample_for_compression;
std::vector<CompressionType> compression_per_level;
+2 -11
View File
@@ -192,11 +192,6 @@ static std::unordered_map<std::string, OptionTypeInfo>
{offsetof(struct ImmutableDBOptions, use_direct_reads),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"use_direct_io_for_compaction_reads",
{offsetof(struct ImmutableDBOptions,
use_direct_io_for_compaction_reads),
OptionType::kBoolean, OptionVerificationType::kNormal,
OptionTypeFlags::kNone}},
{"use_direct_writes",
{0, OptionType::kBoolean, OptionVerificationType::kDeprecated,
OptionTypeFlags::kNone}},
@@ -790,8 +785,6 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
allow_mmap_reads(options.allow_mmap_reads),
allow_mmap_writes(options.allow_mmap_writes),
use_direct_reads(options.use_direct_reads),
use_direct_io_for_compaction_reads(
options.use_direct_io_for_compaction_reads),
use_direct_io_for_flush_and_compaction(
options.use_direct_io_for_flush_and_compaction),
allow_fallocate(options.allow_fallocate),
@@ -918,10 +911,8 @@ void ImmutableDBOptions::Dump(Logger* log) const {
ROCKS_LOG_HEADER(log, " Options.use_direct_reads: %d",
use_direct_reads);
ROCKS_LOG_HEADER(log,
" Options.use_direct_io_for_compaction_reads: %d",
use_direct_io_for_compaction_reads);
ROCKS_LOG_HEADER(log,
" Options.use_direct_io_for_flush_and_compaction: %d",
" "
"Options.use_direct_io_for_flush_and_compaction: %d",
use_direct_io_for_flush_and_compaction);
ROCKS_LOG_HEADER(log, " Options.create_missing_column_families: %d",
create_missing_column_families);
-1
View File
@@ -57,7 +57,6 @@ struct ImmutableDBOptions {
bool allow_mmap_reads;
bool allow_mmap_writes;
bool use_direct_reads;
bool use_direct_io_for_compaction_reads;
bool use_direct_io_for_flush_and_compaction;
bool allow_fallocate;
bool is_fd_close_on_exec;
+1 -1
View File
@@ -129,7 +129,7 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
}
ColumnFamilyOptions::ColumnFamilyOptions()
: compression(GetDefaultCompressionType()),
: compression(Snappy_Supported() ? kSnappyCompression : kNoCompression),
table_factory(
std::shared_ptr<TableFactory>(new BlockBasedTableFactory())) {}
+2 -4
View File
@@ -114,8 +114,6 @@ void BuildDBOptions(const ImmutableDBOptions& immutable_db_options,
options.allow_mmap_reads = immutable_db_options.allow_mmap_reads;
options.allow_mmap_writes = immutable_db_options.allow_mmap_writes;
options.use_direct_reads = immutable_db_options.use_direct_reads;
options.use_direct_io_for_compaction_reads =
immutable_db_options.use_direct_io_for_compaction_reads;
options.use_direct_io_for_flush_and_compaction =
immutable_db_options.use_direct_io_for_flush_and_compaction;
options.allow_fallocate = immutable_db_options.allow_fallocate;
@@ -246,8 +244,8 @@ void UpdateColumnFamilyOptions(const MutableCFOptions& moptions,
cf_opts->block_protection_bytes_per_key =
moptions.block_protection_bytes_per_key;
cf_opts->paranoid_memory_checks = moptions.paranoid_memory_checks;
cf_opts->memtable_verify_per_key_checksum_on_seek =
moptions.memtable_verify_per_key_checksum_on_seek;
cf_opts->memtable_veirfy_per_key_checksum_on_seek =
moptions.memtable_veirfy_per_key_checksum_on_seek;
cf_opts->bottommost_file_compaction_delay =
moptions.bottommost_file_compaction_delay;
+1 -2
View File
@@ -447,7 +447,6 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) {
"allow_fallocate=true;"
"allow_mmap_reads=false;"
"use_direct_reads=false;"
"use_direct_io_for_compaction_reads=false;"
"use_direct_io_for_flush_and_compaction=false;"
"max_log_file_size=4607;"
"advise_random_on_open=true;"
@@ -716,7 +715,7 @@ TEST_F(OptionsSettableTest, ColumnFamilyOptionsAllFieldsSettable) {
"bottommost_file_compaction_delay=7200;"
"uncache_aggressiveness=1234;"
"paranoid_memory_checks=1;"
"memtable_verify_per_key_checksum_on_seek=1;"
"memtable_veirfy_per_key_checksum_on_seek=1;"
"memtable_op_scan_flush_trigger=123;"
"memtable_avg_op_scan_flush_trigger=12;"
"min_tombstones_for_range_conversion=8;"
-17
View File
@@ -171,7 +171,6 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) {
{"allow_mmap_reads", "true"},
{"allow_mmap_writes", "false"},
{"use_direct_reads", "false"},
{"use_direct_io_for_compaction_reads", "false"},
{"use_direct_io_for_flush_and_compaction", "false"},
{"is_fd_close_on_exec", "true"},
{"skip_log_error_on_recovery", "false"},
@@ -358,7 +357,6 @@ TEST_F(OptionsTest, GetOptionsFromMapTest) {
ASSERT_EQ(new_db_opt.allow_mmap_reads, true);
ASSERT_EQ(new_db_opt.allow_mmap_writes, false);
ASSERT_EQ(new_db_opt.use_direct_reads, false);
ASSERT_EQ(new_db_opt.use_direct_io_for_compaction_reads, false);
ASSERT_EQ(new_db_opt.use_direct_io_for_flush_and_compaction, false);
ASSERT_EQ(new_db_opt.is_fd_close_on_exec, true);
ASSERT_EQ(new_db_opt.stats_dump_period_sec, 46U);
@@ -418,10 +416,6 @@ TEST_F(OptionsTest, GetColumnFamilyOptionsFromStringTest) {
ASSERT_OK(GetColumnFamilyOptionsFromString(
config_options, base_cf_opt, "write_buffer_size=6;", &new_cf_opt));
ASSERT_EQ(new_cf_opt.write_buffer_size, 6U);
ASSERT_OK(GetColumnFamilyOptionsFromString(
config_options, base_cf_opt,
"memtable_verify_per_key_checksum_on_seek=true", &new_cf_opt));
ASSERT_TRUE(new_cf_opt.memtable_verify_per_key_checksum_on_seek);
ASSERT_OK(GetColumnFamilyOptionsFromString(
config_options, base_cf_opt, " write_buffer_size = 7 ", &new_cf_opt));
ASSERT_EQ(new_cf_opt.write_buffer_size, 7U);
@@ -2674,7 +2668,6 @@ TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) {
{"default_temperature", "kHot"},
{"persist_user_defined_timestamps", "true"},
{"memtable_max_range_deletions", "0"},
{"memtable_veirfy_per_key_checksum_on_seek", "true"},
};
std::unordered_map<std::string, std::string> db_options_map = {
@@ -2708,7 +2701,6 @@ TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) {
{"allow_mmap_reads", "true"},
{"allow_mmap_writes", "false"},
{"use_direct_reads", "false"},
{"use_direct_io_for_compaction_reads", "false"},
{"use_direct_io_for_flush_and_compaction", "false"},
{"is_fd_close_on_exec", "true"},
{"skip_log_error_on_recovery", "false"},
@@ -2831,7 +2823,6 @@ TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) {
ASSERT_EQ(new_cf_opt.default_write_temperature, Temperature::kCold);
ASSERT_EQ(new_cf_opt.default_temperature, Temperature::kHot);
ASSERT_EQ(new_cf_opt.persist_user_defined_timestamps, true);
ASSERT_TRUE(new_cf_opt.memtable_verify_per_key_checksum_on_seek);
ASSERT_EQ(new_cf_opt.memtable_max_range_deletions, 0);
cf_options_map["write_buffer_size"] = "hello";
@@ -2900,7 +2891,6 @@ TEST_F(OptionsOldApiTest, GetOptionsFromMapTest) {
ASSERT_EQ(new_db_opt.allow_mmap_reads, true);
ASSERT_EQ(new_db_opt.allow_mmap_writes, false);
ASSERT_EQ(new_db_opt.use_direct_reads, false);
ASSERT_EQ(new_db_opt.use_direct_io_for_compaction_reads, false);
ASSERT_EQ(new_db_opt.use_direct_io_for_flush_and_compaction, false);
ASSERT_EQ(new_db_opt.is_fd_close_on_exec, true);
ASSERT_EQ(new_db_opt.stats_dump_period_sec, 46U);
@@ -3567,18 +3557,11 @@ TEST_F(OptionsOldApiTest, ColumnFamilyOptionsSerialization) {
// Phase 1: randomly assign base_opt
// custom type options
test::RandomInitCFOptions(&base_opt, options, &rnd);
base_opt.memtable_verify_per_key_checksum_on_seek = true;
// Phase 2: obtain a string from base_opt
std::string base_options_file_content;
ASSERT_OK(
GetStringFromColumnFamilyOptions(&base_options_file_content, base_opt));
ASSERT_EQ(base_options_file_content.find(
"memtable_veirfy_per_key_checksum_on_seek="),
std::string::npos);
ASSERT_NE(base_options_file_content.find(
"memtable_verify_per_key_checksum_on_seek="),
std::string::npos);
// Phase 3: Set new_opt from the derived string and expect
// new_opt == base_opt
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more