mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17b624b5c0 | ||
|
|
21a7975dfd | ||
|
|
bf1a9a1a20 | ||
|
|
163db5ee28 | ||
|
|
533ba40a62 | ||
|
|
834b2c21cb | ||
|
|
c7c3047b90 | ||
|
|
babce11705 | ||
|
|
ee305ac1c3 | ||
|
|
074134d98f | ||
|
|
7454655969 | ||
|
|
a7f6f33ccc | ||
|
|
61831d47be | ||
|
|
8121c7e254 | ||
|
|
54732b0c8f | ||
|
|
a3b0f187ff | ||
|
|
a215c38a8e | ||
|
|
df90749560 | ||
|
|
014e1c43c1 | ||
|
|
956c7042da | ||
|
|
9198b11b46 | ||
|
|
77eef9fa2e | ||
|
|
4b983da055 | ||
|
|
ddac37a780 | ||
|
|
76d7c29389 | ||
|
|
1334e4dd52 | ||
|
|
9fb698e218 | ||
|
|
baa4d373a6 | ||
|
|
4b44c2b4ba | ||
|
|
da82001d81 | ||
|
|
72beaf8dfb | ||
|
|
9b4b980c6a | ||
|
|
928527b869 | ||
|
|
f9b62a0567 | ||
|
|
2e689e3c00 | ||
|
|
78a9d3c98c | ||
|
|
39d56d5ad7 |
No files matched your search
@@ -650,6 +650,9 @@ if(USE_FOLLY)
|
||||
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../glog* OUTPUT_VARIABLE
|
||||
GLOG_INST_PATH)
|
||||
set(Boost_DIR ${BOOST_INST_PATH}/lib/cmake/Boost-1.83.0)
|
||||
# Boost's per-library CMake configs find their dependencies (e.g.
|
||||
# boost_assert) only through the package search path.
|
||||
set(CMAKE_PREFIX_PATH ${BOOST_INST_PATH} ${CMAKE_PREFIX_PATH})
|
||||
if(EXISTS ${FMT_INST_PATH}/lib64)
|
||||
set(fmt_DIR ${FMT_INST_PATH}/lib64/cmake/fmt)
|
||||
else()
|
||||
@@ -696,7 +699,13 @@ if(USE_FOLLY)
|
||||
exec_program(sed ARGS -i 's/gflags_shared//g'
|
||||
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
|
||||
|
||||
# folly-config.cmake only checks for gflags under a target name that this
|
||||
# install doesn't define, then falls back to our Findgflags.cmake, which
|
||||
# mistakes the gflags_static target name for a library path. gflags is
|
||||
# resolved below instead.
|
||||
set(CMAKE_DISABLE_FIND_PACKAGE_gflags TRUE)
|
||||
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
|
||||
unset(CMAKE_DISABLE_FIND_PACKAGE_gflags)
|
||||
|
||||
# Fix gflags library name for debug builds. The getdeps gflags shared
|
||||
# library is versioned (e.g. libgflags_debug.so.2.3), so resolve the
|
||||
|
||||
+81
@@ -1,6 +1,87 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 11.13.2 (10/28/2026)
|
||||
### Bug Fixes
|
||||
* Fixed a secondary instance with `open_files_async = true` getting stuck after `TryCatchUpWithPrimary()` returned `PathNotFound` for a table file that the primary deleted before the background opener reached it.
|
||||
* Fixed blob direct write with more than one concurrent flush of a column family: a later flush sealed and published the blob files of the oldest pending memtable instead of its own, so flushes failed with `Corruption: VersionBuilder: Blob file #N already added` and the database stopped.
|
||||
|
||||
## 11.13.1 (10/23/2026)
|
||||
### Bug Fixes
|
||||
* Fixed data loss with `min_tombstones_for_range_conversion` and external file ingestion. An iterator whose view predated the ingestion could convert the deleted keys around the ingested keys into a range tombstone that deleted them. This happened when the file got no new sequence number (sequence number zero, `ingest_behind`, or `allow_db_generated_files`), and when an iterator was created during the ingestion.
|
||||
* Fixed a data loss bug where an iterator with `ReadOptions::read_tier = kMemtableTier` could, with `min_tombstones_for_range_conversion` enabled, insert a range tombstone that deleted live keys stored only in SST files. Read-path range tombstone conversion now runs only for iterators with `read_tier = kReadAllTier`.
|
||||
|
||||
## 11.13.0 (10/21/2026)
|
||||
### New Features
|
||||
* Added `FileSystem::IsPreallocationSupported()`. A `FileSystem` that never preallocates file space can return false so that DB open skips reopening and truncating the last WAL file.
|
||||
|
||||
### Public API Changes
|
||||
* Added the experimental `DBOptions::track_options_file_number_in_manifest`
|
||||
rollout option (with C and Java bindings). The option is persisted in each
|
||||
OPTIONS file. When enabled, RocksDB durably creates an OPTIONS file and then
|
||||
commits its file number in MANIFEST. Column-family create/drop commits the
|
||||
configuration snapshot and membership in one MANIFEST record. A newer tracked
|
||||
OPTIONS file without a commit is ignored after a crash, while a newer OPTIONS
|
||||
file written with tracking disabled retains the legacy highest-numbered-file
|
||||
behavior. Batched column-family changes publish one snapshot; after partial
|
||||
success it can be a safe superset that persisted-options APIs filter against
|
||||
live MANIFEST membership. Ordinary `DB::Open` continues to use caller-supplied
|
||||
options and does not depend on the operational snapshot.
|
||||
* Added `EncodeEntry()`, `PackSequenceAndType()`, and `UnPackSequenceAndType()` in `rocksdb/utilities/types_util.h` to construct internal keys and pack or unpack their sequence numbers and entry types. `kInternalKeyFooterSize` exposes the footer size in bytes.
|
||||
* Added `ldb update_manifest --rebind_options_file --options_file=OPTIONS-<number>` for offline repair of a committed MANIFEST OPTIONS file number. Operators must select a trusted snapshot that has MANIFEST tracking enabled and provide any external DB/CF allocation roots that are not serialized in OPTIONS.
|
||||
|
||||
### Behavior Changes
|
||||
* With `max_bgerror_resume_count <= 0` (auto resume disabled), a retryable or file-scoped IO error during compaction or flush is now a hard background error that stops writes until `DB::Resume()` is called. Previously, compaction errors were silently retried, and flush errors with a healthy WAL were soft errors that let writes continue while flushes were stopped.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed two more `SstFileManager` trash accounting leaks: retrying a failed trash deletion immediately (for example with rate limiting disabled) after the file had disappeared, and a trash file that was deleted but whose directory sync then failed. In both cases the file's size stayed in the total trash size and tracked size until restart.
|
||||
* Fixed `SstFileManager` trash accounting leaking the size of a queued trash file that disappeared before deletion (for example because its directory was renamed). The leaked bytes stayed in the total trash size until restart, which could push every later deletion past `max_trash_db_ratio` and make it immediate instead of rate limited.
|
||||
* Fixed `MultiGet` with `ReadOptions::async_io` returning `NotSupported` when io_uring initialization or an asynchronous read submission fails. It now checks async I/O support on the reading thread and falls back to synchronous reads when needed.
|
||||
* Fixed subset checkpoints so their newest OPTIONS file lists only the column
|
||||
families retained by the checkpoint. This applies with both legacy
|
||||
highest-numbered OPTIONS selection and MANIFEST-tracked OPTIONS selection, and
|
||||
prevents `LoadLatestOptions` from recreating column families that the subset
|
||||
checkpoint intentionally dropped from its copied MANIFEST.
|
||||
|
||||
### Performance Improvements
|
||||
* Perf context counters and timers no longer call the thread-local init function of `perf_context` on every update. With `kEnableCount`, a counter update costs about 60% less.
|
||||
|
||||
## 11.12.0 (10/07/2026)
|
||||
### New Features
|
||||
* Added `FullExternalTableFactory` and related full-mode external table APIs that preserve RocksDB internal keys, enabling external tables to support nonzero sequence numbers, point and single deletions, merge operands, blob indexes, wide-column entities, snapshots, live DB writes, and multi-level compactions.
|
||||
|
||||
### Public API Changes
|
||||
* Added experimental coroutine capability interfaces for basic and full `ExternalTableReader` implementations. Coroutine-capable external readers can initiate file reads through `FSRandomAccessFile::SubmitReadAsync()`.
|
||||
* `ExternalTableFactory::NewTableReader()` now receives an already-opened `FSRandomAccessFile` and its file size instead of requiring implementations to reopen `file_path`. The supplied file preserves RocksDB's direct-I/O alignment handling, rate limiting, read statistics and listener notifications, prefetching, and synchronous, asynchronous, and batched reads.
|
||||
* Added `PutRangeDeletionBlock()`, `GetRangeDeletionBlock()`, and `IsDeleteRangeSupported()` to the external table interfaces so formats can opt in to range deletions using RocksDB's raw range-deletion metadata block.
|
||||
* Opaque `PreparedFileInfo` metadata can now be serialized to a checksummed representation with `SerializePreparedFileInfo()` and restored with `DeserializePreparedFileInfo()` for reuse across processes.
|
||||
* Removed the deprecated `IngestExternalFileOptions::write_global_seqno` option and its C, Java, and `ldb` APIs, `TableProperties::external_sst_file_global_seqno_offset`, and the `file_offset` output from `ExternalTableReader::GetPropertiesBlock()`. Existing external SST files whose global sequence number was rewritten without updating the properties-block checksum remain readable.
|
||||
* `TablePropertiesNames` has no apparent use in the public API so has been deprecated there.
|
||||
* `TableProperties::creation_time` is renamed to `oldest_ancestor_time`, which better describes its meaning, along with `TablePropertiesNames::kCreationTime` to `kOldestAncestorTime`. The misspelled `SstFileMetaData::oldest_ancester_time` is renamed to `oldest_ancestor_time`. The old names remain available as deprecated aliases and will be removed in a future release.
|
||||
* Added `WriteBufferFlushPolicy` and `WriteBufferManager` policy selection for flushing the oldest or largest column family, including configurable batched flushing of the largest column families across DBs sharing a manager, with C and Java bindings.
|
||||
|
||||
### Behavior Changes
|
||||
* When `ExternalTableBuilderBase::PutPropertiesBlock()` returns `NotSupported`, RocksDB now overrides `num_entries`, `raw_key_size`, and `raw_value_size` from `ExternalTableBuilderBase::GetTableProperties()` with the logical totals tracked by the external-table adapter. Format-specific properties such as `data_size` remain unchanged.
|
||||
* The `FSWritableFile` passed to `ExternalTableFactory::NewTableBuilder()` now reports logical file size including RocksDB-buffered data and exposes the underlying file's direct-I/O mode and required buffer alignment.
|
||||
* `Iterator::Refresh()` now returns `NotSupported` on iterators from a secondary DB's `DB::NewIterators()` only, because those iterators are pinned to one cross-column-family read view. Iterators from `DB::NewIterator()` are unaffected; to advance a batch, call `DB::NewIterators()` again after `TryCatchUpWithPrimary()`.
|
||||
* `SstFileReader` now reports standard SST read statistics and file I/O listener notifications, and honors the configured read rate limiter.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed full recovery from retryable background errors to atomically advance WAL replay boundaries for all column families, preventing partial recovery of multi-column-family write batches after a failed recovery flush.
|
||||
* Fixed RocksDB builds on big-endian architectures such as s390x by disabling the optional persistent fault-injection log, whose binary format is little-endian-only.
|
||||
* Fixed `DB::ClipColumnFamily()` deleting a non-L0 SST file whose largest key equals `begin_key`, which lost keys at the start of the kept range.
|
||||
* Fixed swapped `oldest_key_time` and `newest_key_time` values in the `table_file_creation` event log entry.
|
||||
* Fixed `ExternalTableReader::VerifyChecksum()` results not being honored by the external table adapter.
|
||||
* Fixed a bug in manifest handling that could lead to a hang or memory corruption in the case of LSM sanity checks being fatally violated (exceptional; see `force_consistency_checks`).
|
||||
* Fixed `MultiGet()` with `ReadOptions::read_tier == kBlockCacheTier` reading data blocks from disk when `allow_mmap_reads` is false. Keys whose data block is not in the block cache now return `Status::Incomplete()`, the same as `Get()`.
|
||||
* Fixed trace replay (`DB::NewDefaultReplayer()`, db_bench `replay`) failing every read record (Get, MultiGet, iterator seek) on the default column family with `Corruption: Invalid Column Family ID` when the default column family handle was not passed, which is always the case in db_bench with `--num_column_families=1`.
|
||||
* Fixed multi-threaded `Replayer::Replay()` ignoring errors from executing trace records and returning OK. It now stops and returns the error, including `Status::NotSupported()` from executing a record, as single-threaded replay does.
|
||||
* Fixed secondary DB `NewIterators()` returning iterators from different database states or blocking for `TryCatchUpWithPrimary()` by serving the last completed catch-up view.
|
||||
|
||||
### Performance Improvements
|
||||
* Single Get coroutine reads using non-mmap, non-direct I/O on a `kFSBuffer`-capable `FileSystem` now use `FSRandomAccessFile::SubmitReadAsync()` instead of falling back to synchronous `Read()`. When rate-limited, these reads acquire their full token budget before submission to preserve a single filesystem-owned buffer, which can make physical I/O burstier than reads using caller-provided buffers.
|
||||
|
||||
|
||||
## 11.11.0 (09/21/2026)
|
||||
### New Features
|
||||
* External tables now support DB-assigned global sequence numbers when `IngestExternalFileOptions::allow_global_seqno` is enabled, allowing overlapping external tables to be ingested across multiple levels when compactions are disabled. This includes the deprecated `write_global_seqno` path when the reader supplies the properties block's file offset.
|
||||
|
||||
@@ -741,6 +741,16 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
|
||||
return opt->rep.write_dbid_to_manifest;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.track_options_file_number_in_manifest = v;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.track_options_file_number_in_manifest;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.write_identity_file = v;
|
||||
|
||||
@@ -204,6 +204,14 @@ rocksdb_options_set_background_close_inactive_wals(rocksdb_options_t* opt,
|
||||
extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_background_close_inactive_wals(rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
rocksdb_options_set_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt, unsigned char v);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_prefix_seek_opt_in_only(
|
||||
rocksdb_options_t* opt, unsigned char v);
|
||||
|
||||
|
||||
@@ -913,6 +913,12 @@ static void rocksdb_roundtrip_test_rocksdb_options(void) {
|
||||
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 1);
|
||||
rocksdb_options_set_track_and_verify_wals(obj, 0);
|
||||
CheckCondition(rocksdb_options_get_track_and_verify_wals(obj) == 0);
|
||||
rocksdb_options_set_track_options_file_number_in_manifest(obj, 1);
|
||||
CheckCondition(
|
||||
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 1);
|
||||
rocksdb_options_set_track_options_file_number_in_manifest(obj, 0);
|
||||
CheckCondition(
|
||||
rocksdb_options_get_track_options_file_number_in_manifest(obj) == 0);
|
||||
rocksdb_options_set_two_write_queues(obj, 1);
|
||||
CheckCondition(rocksdb_options_get_two_write_queues(obj) == 1);
|
||||
rocksdb_options_set_two_write_queues(obj, 0);
|
||||
|
||||
Vendored
+2
-1
@@ -276,7 +276,8 @@ struct KeyGen {
|
||||
// location, so we fix hash bits starting at the bottom of that word.
|
||||
auto hi_hash = uint64_t{0x9e3779b97f4a7c13U} ^
|
||||
(key_hash << 1 << (FLAGS_degenerate_hash_bits - 1));
|
||||
uint64_t un_hi, un_lo;
|
||||
uint64_t un_hi;
|
||||
uint64_t un_lo;
|
||||
BijectiveUnhash2x64(hi_hash, key_hash, &un_hi, &un_lo);
|
||||
un_lo ^= BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
EncodeFixed64(key_data, un_lo);
|
||||
|
||||
Vendored
+7
-2
@@ -32,9 +32,9 @@ class CacheReservationManager {
|
||||
// for an incremental amount of memory used (i.e, incremental_memory_used)
|
||||
class CacheReservationHandle {
|
||||
public:
|
||||
virtual ~CacheReservationHandle() {}
|
||||
virtual ~CacheReservationHandle() = default;
|
||||
};
|
||||
virtual ~CacheReservationManager() {}
|
||||
virtual ~CacheReservationManager() = default;
|
||||
virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
|
||||
// TODO(hx235): replace the usage of
|
||||
// `UpdateCacheReservation(memory_used_delta, increase)` with
|
||||
@@ -235,6 +235,11 @@ class ConcurrentCacheReservationManager
|
||||
cache_res_mgr_ = cache_res_mgr;
|
||||
cache_res_handle_ = std::move(cache_res_handle);
|
||||
}
|
||||
// No copying allowed
|
||||
CacheReservationHandle(const CacheReservationHandle&) = delete;
|
||||
CacheReservationHandle& operator=(const CacheReservationHandle&) = delete;
|
||||
CacheReservationHandle(CacheReservationHandle&&) = delete;
|
||||
CacheReservationHandle& operator=(CacheReservationHandle&&) = delete;
|
||||
|
||||
~CacheReservationHandle() override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_);
|
||||
|
||||
+2
-2
@@ -407,8 +407,8 @@ TEST(CacheReservationHandleTest, HandleTest) {
|
||||
std::size_t mem_used = 0;
|
||||
const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry;
|
||||
const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry;
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1,
|
||||
handle_2;
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1;
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_2;
|
||||
|
||||
// To test consecutive CacheReservationManager::MakeCacheReservation works
|
||||
// correctly in terms of returning the handle as well as updating cache
|
||||
|
||||
Vendored
+12
-3
@@ -975,7 +975,8 @@ void FixedHyperClockTable::Erase(const UniqueId64x2& hashed_key) {
|
||||
// Could be multiple entries in rare cases. Erase them all.
|
||||
// Optimistically increment acquire counter
|
||||
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
|
||||
SlotMeta old_meta, meta;
|
||||
SlotMeta old_meta;
|
||||
SlotMeta meta;
|
||||
h->meta.Apply(add_acquire, &old_meta, &meta);
|
||||
// Check if it's an entry visible to lookups
|
||||
if (meta.IsVisible()) {
|
||||
@@ -1738,7 +1739,8 @@ inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
|
||||
// Must be at least something to match
|
||||
assert(hashed_key || shift > 0);
|
||||
|
||||
SlotMeta old_meta, new_meta;
|
||||
SlotMeta old_meta;
|
||||
SlotMeta new_meta;
|
||||
// (Optimistically) increment acquire counter.
|
||||
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
|
||||
h.meta.Apply(add_acquire, &old_meta, &new_meta);
|
||||
@@ -1856,6 +1858,12 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
assert(saved_head_.IsLocked());
|
||||
}
|
||||
|
||||
// No copying allowed
|
||||
ChainRewriteLock(const ChainRewriteLock&) = delete;
|
||||
ChainRewriteLock& operator=(const ChainRewriteLock&) = delete;
|
||||
ChainRewriteLock(ChainRewriteLock&&) = delete;
|
||||
ChainRewriteLock& operator=(ChainRewriteLock&&) = delete;
|
||||
|
||||
~ChainRewriteLock() {
|
||||
if (!IsEnd()) {
|
||||
// Release lock
|
||||
@@ -3122,7 +3130,8 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
if (probably_equal) {
|
||||
// Increment acquire counter for definitive check
|
||||
auto add_acquire = AcquireCounter::PlusTransformPromiseNoOverflow(1);
|
||||
SlotMeta old_meta, new_meta;
|
||||
SlotMeta old_meta;
|
||||
SlotMeta new_meta;
|
||||
h->meta.Apply(add_acquire, &old_meta, &new_meta);
|
||||
// Check if it's a referencable (sharable) entry
|
||||
if (LIKELY(old_meta.IsShareable())) {
|
||||
|
||||
Vendored
+4
@@ -29,6 +29,10 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
const CompressedSecondaryCacheResultHandle&) = delete;
|
||||
CompressedSecondaryCacheResultHandle& operator=(
|
||||
const CompressedSecondaryCacheResultHandle&) = delete;
|
||||
CompressedSecondaryCacheResultHandle(CompressedSecondaryCacheResultHandle&&) =
|
||||
delete;
|
||||
CompressedSecondaryCacheResultHandle& operator=(
|
||||
CompressedSecondaryCacheResultHandle&&) = delete;
|
||||
|
||||
bool IsReady() override { return true; }
|
||||
|
||||
|
||||
+2
-1
@@ -508,7 +508,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length()));
|
||||
item1.release(); // Appease clang-analyze "potential memory leak"
|
||||
// Appease clang-analyze "potential memory leak"
|
||||
[[maybe_unused]] TestItem* released_item1 = item1.release();
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW);
|
||||
|
||||
Vendored
+2
-1
@@ -1366,7 +1366,8 @@ TEST_P(BasicSecondaryCacheTest, BasicFailTest) {
|
||||
// str1.length()).IsInvalidArgument());
|
||||
ASSERT_OK(
|
||||
cache->Insert(k1.AsSlice(), item1.get(), GetHelper(), str1.length()));
|
||||
item1.release(); // Appease clang-analyze "potential memory leak"
|
||||
// Appease clang-analyze "potential memory leak"
|
||||
[[maybe_unused]] TestItem* released_item1 = item1.release();
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup(k2.AsSlice(), nullptr, /*context*/ this,
|
||||
|
||||
Vendored
+8
-2
@@ -90,7 +90,7 @@ class CacheShardBase {
|
||||
class ShardedCacheBase : public Cache {
|
||||
public:
|
||||
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
|
||||
virtual ~ShardedCacheBase() = default;
|
||||
~ShardedCacheBase() override = default;
|
||||
|
||||
int GetNumShardBits() const;
|
||||
uint32_t GetNumShards() const;
|
||||
@@ -143,7 +143,13 @@ class ShardedCache : public ShardedCacheBase {
|
||||
sizeof(CacheShard) * GetNumShards()))),
|
||||
destroy_shards_in_dtor_(false) {}
|
||||
|
||||
virtual ~ShardedCache() {
|
||||
// No copying allowed
|
||||
ShardedCache(const ShardedCache&) = delete;
|
||||
ShardedCache& operator=(const ShardedCache&) = delete;
|
||||
ShardedCache(ShardedCache&&) = delete;
|
||||
ShardedCache& operator=(ShardedCache&&) = delete;
|
||||
|
||||
~ShardedCache() override {
|
||||
if (destroy_shards_in_dtor_) {
|
||||
ForEachShard([](CacheShard* cs) { cs->~CacheShard(); });
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -36,7 +36,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
#endif
|
||||
}
|
||||
|
||||
~TieredSecondaryCache() override {}
|
||||
~TieredSecondaryCache() override = default;
|
||||
|
||||
const char* Name() const override { return "TieredSecondaryCache"; }
|
||||
|
||||
@@ -75,7 +75,7 @@ class TieredSecondaryCache : public SecondaryCacheWrapper {
|
||||
|
||||
class ResultHandle : public SecondaryCacheResultHandle {
|
||||
public:
|
||||
~ResultHandle() override {}
|
||||
~ResultHandle() override = default;
|
||||
|
||||
bool IsReady() override {
|
||||
if (inner_handle_ && inner_handle_->IsReady()) {
|
||||
|
||||
Vendored
+1
-1
@@ -241,7 +241,7 @@ class FullTypedCacheHelperFns : public BasicTypedCacheHelperFns<TValue> {
|
||||
if (source != CacheTier::kVolatileTier) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
if constexpr (sizeof(TCreateContext) > 0) {
|
||||
if constexpr (!std::is_empty_v<TCreateContext>) {
|
||||
TCreateContext* tcontext = static_cast<TCreateContext*>(context);
|
||||
tcontext->Create(&value, out_charge, data, type, allocator);
|
||||
} else {
|
||||
|
||||
@@ -42,6 +42,13 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
std::unique_ptr<ColumnFamilyData, ColumnFamilyDataUnrefDeleter>;
|
||||
|
||||
public:
|
||||
ArenaWrappedDBIter() = default;
|
||||
// No copying allowed
|
||||
ArenaWrappedDBIter(const ArenaWrappedDBIter&) = delete;
|
||||
ArenaWrappedDBIter& operator=(const ArenaWrappedDBIter&) = delete;
|
||||
ArenaWrappedDBIter(ArenaWrappedDBIter&&) = delete;
|
||||
ArenaWrappedDBIter& operator=(ArenaWrappedDBIter&&) = delete;
|
||||
|
||||
~ArenaWrappedDBIter() override {
|
||||
if (db_iter_ != nullptr) {
|
||||
DestroyDBIter();
|
||||
|
||||
@@ -24,6 +24,8 @@ class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
AttributeGroupIteratorImpl(const AttributeGroupIteratorImpl&) = delete;
|
||||
AttributeGroupIteratorImpl& operator=(const AttributeGroupIteratorImpl&) =
|
||||
delete;
|
||||
AttributeGroupIteratorImpl(AttributeGroupIteratorImpl&&) = delete;
|
||||
AttributeGroupIteratorImpl& operator=(AttributeGroupIteratorImpl&&) = delete;
|
||||
|
||||
bool Valid() const override { return impl_.Valid(); }
|
||||
void SeekToFirst() override { impl_.SeekToFirst(); }
|
||||
|
||||
@@ -68,6 +68,8 @@ class BlobFileBuilder {
|
||||
|
||||
BlobFileBuilder(const BlobFileBuilder&) = delete;
|
||||
BlobFileBuilder& operator=(const BlobFileBuilder&) = delete;
|
||||
BlobFileBuilder(BlobFileBuilder&&) = delete;
|
||||
BlobFileBuilder& operator=(BlobFileBuilder&&) = delete;
|
||||
|
||||
~BlobFileBuilder();
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
}
|
||||
}
|
||||
|
||||
reader.release();
|
||||
[[maybe_unused]] BlobFileReader* released_reader = reader.release();
|
||||
|
||||
*blob_file_reader = cache_.Guard(handle);
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ class BlobFileCache {
|
||||
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
BlobFileCache(BlobFileCache&&) = delete;
|
||||
BlobFileCache& operator=(BlobFileCache&&) = delete;
|
||||
|
||||
~BlobFileCache() = default;
|
||||
|
||||
// Returns a cached reader for `blob_file`, opening and caching it on miss.
|
||||
// Checksum metadata is forwarded to the FileSystem through FileOptions when
|
||||
|
||||
@@ -52,6 +52,8 @@ class SharedBlobFileMetaData {
|
||||
SharedBlobFileMetaData(SharedBlobFileMetaData&&) = delete;
|
||||
SharedBlobFileMetaData& operator=(SharedBlobFileMetaData&&) = delete;
|
||||
|
||||
~SharedBlobFileMetaData() = default;
|
||||
|
||||
uint64_t GetBlobFileSize() const;
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
@@ -110,6 +112,8 @@ class BlobFileMetaData {
|
||||
BlobFileMetaData(BlobFileMetaData&&) = delete;
|
||||
BlobFileMetaData& operator=(BlobFileMetaData&&) = delete;
|
||||
|
||||
~BlobFileMetaData() = default;
|
||||
|
||||
const std::shared_ptr<SharedBlobFileMetaData>& GetSharedMeta() const {
|
||||
return shared_meta_;
|
||||
}
|
||||
|
||||
@@ -543,10 +543,11 @@ Status BlobFilePartitionManager::WriteBlob(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFilePartitionManager::RotateCurrentGeneration() {
|
||||
void BlobFilePartitionManager::RotateCurrentGeneration(uint64_t memtable_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
GenerationBatch batch;
|
||||
batch.memtable_id = memtable_id;
|
||||
batch.sealed_files = std::move(current_generation_sealed_files_);
|
||||
current_generation_sealed_files_.clear();
|
||||
|
||||
@@ -569,8 +570,19 @@ void BlobFilePartitionManager::RotateCurrentGeneration() {
|
||||
pending_generations_.emplace_back(std::move(batch));
|
||||
}
|
||||
|
||||
BlobFilePartitionManager::GenerationBatch*
|
||||
BlobFilePartitionManager::FindPendingGeneration(uint64_t memtable_id) {
|
||||
for (auto& batch : pending_generations_) {
|
||||
if (batch.memtable_id == memtable_id) {
|
||||
return &batch;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Status BlobFilePartitionManager::PrepareFlushAdditions(
|
||||
const WriteOptions& write_options, size_t num_generations,
|
||||
const WriteOptions& write_options,
|
||||
const std::vector<uint64_t>& memtable_ids,
|
||||
std::vector<BlobFileAddition>* additions,
|
||||
std::vector<BlobFileGarbage>* garbages,
|
||||
std::vector<std::vector<uint64_t>>* generation_blob_file_numbers) {
|
||||
@@ -583,13 +595,13 @@ Status BlobFilePartitionManager::PrepareFlushAdditions(
|
||||
}
|
||||
|
||||
MutexLock lock(&mutex_);
|
||||
if (num_generations > pending_generations_.size()) {
|
||||
return Status::Corruption(
|
||||
"Missing blob direct write generation metadata for flush");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_generations; ++i) {
|
||||
GenerationBatch& batch = pending_generations_[i];
|
||||
for (const uint64_t memtable_id : memtable_ids) {
|
||||
GenerationBatch* const batch_ptr = FindPendingGeneration(memtable_id);
|
||||
if (batch_ptr == nullptr) {
|
||||
return Status::Corruption(
|
||||
"Missing blob direct write generation metadata for flush");
|
||||
}
|
||||
GenerationBatch& batch = *batch_ptr;
|
||||
while (!batch.deferred_files.empty()) {
|
||||
DeferredFile deferred = std::move(batch.deferred_files.front());
|
||||
batch.deferred_files.pop_front();
|
||||
@@ -668,12 +680,22 @@ Status BlobFilePartitionManager::MarkBlobWriteAsGarbage(uint64_t file_number,
|
||||
return Status::Corruption(message);
|
||||
}
|
||||
|
||||
void BlobFilePartitionManager::CommitPreparedGenerations(
|
||||
size_t num_generations) {
|
||||
MutexLock lock(&mutex_);
|
||||
while (num_generations-- > 0 && !pending_generations_.empty()) {
|
||||
pending_generations_.pop_front();
|
||||
void BlobFilePartitionManager::CommitFlushedGenerations(
|
||||
uint64_t earliest_unflushed_memtable_id) {
|
||||
std::vector<uint64_t> sealed_file_numbers;
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
while (!pending_generations_.empty() &&
|
||||
pending_generations_.front().memtable_id <
|
||||
earliest_unflushed_memtable_id) {
|
||||
for (const auto& sealed_file :
|
||||
pending_generations_.front().sealed_files) {
|
||||
sealed_file_numbers.push_back(sealed_file.addition.GetBlobFileNumber());
|
||||
}
|
||||
pending_generations_.pop_front();
|
||||
}
|
||||
}
|
||||
RemoveFilePartitionMappings(sealed_file_numbers);
|
||||
}
|
||||
|
||||
Status BlobFilePartitionManager::SyncAllOpenFiles(
|
||||
|
||||
@@ -88,27 +88,31 @@ class BlobFilePartitionManager {
|
||||
const Slice& key,
|
||||
const WideColumns& columns) const;
|
||||
|
||||
// Move the current active partition files into the next immutable
|
||||
// memtable-generation batch. Called from SwitchMemtable() while DB mutex is
|
||||
// held. No I/O is performed here.
|
||||
void RotateCurrentGeneration();
|
||||
// Move the current active partition files into the generation of the
|
||||
// memtable `memtable_id`, which just became immutable. Called from
|
||||
// SwitchMemtable() while DB mutex is held. No I/O is performed here.
|
||||
void RotateCurrentGeneration(uint64_t memtable_id);
|
||||
|
||||
// Seal the first `num_generations` queued immutable generations and return
|
||||
// all blob additions and initial-garbage updates that must be registered
|
||||
// with the matching flush. Generations stay queued until
|
||||
// CommitPreparedGenerations() is called. If `generation_blob_file_numbers`
|
||||
// is non-null, it receives the sealed blob file numbers for each prepared
|
||||
// generation in the same FIFO order.
|
||||
// Seal the queued generations of the memtables `memtable_ids` and return all
|
||||
// blob additions and initial-garbage updates that must be registered with
|
||||
// the flush of those memtables. Concurrent flushes pick different
|
||||
// memtables, so generations are matched by memtable ID, not by queue
|
||||
// position. Generations stay queued until CommitFlushedGenerations() removes
|
||||
// them. If `generation_blob_file_numbers` is non-null, it receives the
|
||||
// sealed blob file numbers for each memtable, in `memtable_ids` order.
|
||||
Status PrepareFlushAdditions(const WriteOptions& write_options,
|
||||
size_t num_generations,
|
||||
const std::vector<uint64_t>& memtable_ids,
|
||||
std::vector<BlobFileAddition>* additions,
|
||||
std::vector<BlobFileGarbage>* garbages,
|
||||
std::vector<std::vector<uint64_t>>*
|
||||
generation_blob_file_numbers = nullptr);
|
||||
|
||||
// Remove the first `num_generations` prepared immutable generations after
|
||||
// their blob-file additions and garbage edits were committed to MANIFEST.
|
||||
void CommitPreparedGenerations(size_t num_generations);
|
||||
// Remove the generations of the memtables older than
|
||||
// `earliest_unflushed_memtable_id`, whose flush results are installed, and
|
||||
// stop tracking their sealed files as manager-owned. A flush that finishes
|
||||
// before an older one is installed with it, or rolled back if it fails, so
|
||||
// the flush that prepared a generation cannot remove it on its own.
|
||||
void CommitFlushedGenerations(uint64_t earliest_unflushed_memtable_id);
|
||||
|
||||
// In v1 flush-on-write mode each AddRecord flushes to the OS immediately, so
|
||||
// there is nothing extra to do for the non-sync write path.
|
||||
@@ -200,6 +204,8 @@ class BlobFilePartitionManager {
|
||||
};
|
||||
|
||||
struct GenerationBatch {
|
||||
// The memtable that became immutable when this generation was rotated.
|
||||
uint64_t memtable_id = 0;
|
||||
// Files that were still open when the memtable became immutable.
|
||||
std::deque<DeferredFile> deferred_files;
|
||||
// Files already sealed for this memtable generation.
|
||||
@@ -208,6 +214,8 @@ class BlobFilePartitionManager {
|
||||
|
||||
// Clears all active-file bookkeeping from a partition after sealing.
|
||||
void ResetPartitionState(Partition* partition);
|
||||
// Returns the queued generation of `memtable_id`, or null.
|
||||
GenerationBatch* FindPendingGeneration(uint64_t memtable_id);
|
||||
// Marks a blob file as manager-owned until it is committed or abandoned.
|
||||
void AddFilePartitionMapping(uint64_t file_number, uint32_t partition_idx);
|
||||
// Stops tracking a blob file after open/seal failure.
|
||||
|
||||
@@ -53,6 +53,8 @@ class BlobFileReader {
|
||||
|
||||
BlobFileReader(const BlobFileReader&) = delete;
|
||||
BlobFileReader& operator=(const BlobFileReader&) = delete;
|
||||
BlobFileReader(BlobFileReader&&) = delete;
|
||||
BlobFileReader& operator=(BlobFileReader&&) = delete;
|
||||
|
||||
~BlobFileReader();
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ class BlobIndex {
|
||||
|
||||
BlobIndex(const BlobIndex&) = default;
|
||||
BlobIndex& operator=(const BlobIndex&) = default;
|
||||
BlobIndex(BlobIndex&&) = default;
|
||||
BlobIndex& operator=(BlobIndex&&) = default;
|
||||
|
||||
~BlobIndex() = default;
|
||||
|
||||
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ class BlobLogSequentialReader {
|
||||
// No copying allowed
|
||||
BlobLogSequentialReader(const BlobLogSequentialReader&) = delete;
|
||||
BlobLogSequentialReader& operator=(const BlobLogSequentialReader&) = delete;
|
||||
BlobLogSequentialReader(BlobLogSequentialReader&&) = delete;
|
||||
BlobLogSequentialReader& operator=(BlobLogSequentialReader&&) = delete;
|
||||
|
||||
~BlobLogSequentialReader();
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ class BlobLogWriter {
|
||||
// No copying allowed
|
||||
BlobLogWriter(const BlobLogWriter&) = delete;
|
||||
BlobLogWriter& operator=(const BlobLogWriter&) = delete;
|
||||
BlobLogWriter(BlobLogWriter&&) = delete;
|
||||
BlobLogWriter& operator=(BlobLogWriter&&) = delete;
|
||||
|
||||
~BlobLogWriter();
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ struct BlobReadRequest {
|
||||
BlobReadRequest() = default;
|
||||
BlobReadRequest(const BlobReadRequest& other) = default;
|
||||
BlobReadRequest& operator=(const BlobReadRequest& other) = default;
|
||||
BlobReadRequest(BlobReadRequest&& other) = default;
|
||||
BlobReadRequest& operator=(BlobReadRequest&& other) = default;
|
||||
~BlobReadRequest() = default;
|
||||
};
|
||||
|
||||
using BlobFileReadRequests =
|
||||
|
||||
@@ -202,7 +202,7 @@ Status BlobSource::PutBlobIntoCache(
|
||||
const Status s = InsertEntryIntoCache(cache_key, blob->get(), &cache_handle,
|
||||
Cache::Priority::BOTTOM);
|
||||
if (s.ok()) {
|
||||
blob->release();
|
||||
[[maybe_unused]] BlobContents* released_blob = blob->release();
|
||||
|
||||
assert(cache_handle != nullptr);
|
||||
*cached_blob =
|
||||
|
||||
@@ -67,6 +67,8 @@ class BlobSource {
|
||||
|
||||
BlobSource(const BlobSource&) = delete;
|
||||
BlobSource& operator=(const BlobSource&) = delete;
|
||||
BlobSource(BlobSource&&) = delete;
|
||||
BlobSource& operator=(BlobSource&&) = delete;
|
||||
|
||||
~BlobSource();
|
||||
|
||||
|
||||
@@ -1729,11 +1729,13 @@ TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
~DBBlobBasicIOErrorTest() override { Close(); }
|
||||
|
||||
protected:
|
||||
DBBlobBasicIOErrorTest() : sync_point_(GetParam()) {
|
||||
fault_injection_env_.reset(new FaultInjectionTestEnv(env_));
|
||||
}
|
||||
~DBBlobBasicIOErrorTest() { Close(); }
|
||||
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
@@ -1460,7 +1462,9 @@ TEST_F(DBBlobDirectWriteTest,
|
||||
std::vector<BlobFileGarbage> garbages1;
|
||||
// Simulate a flush retry: prepare once, skip commit, then prepare the same
|
||||
// generation again and verify it reuses the already sealed blob file.
|
||||
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
|
||||
const std::vector<uint64_t> memtable_ids = {
|
||||
cfh->cfd()->imm()->GetEarliestMemTableID()};
|
||||
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
|
||||
&additions1, &garbages1));
|
||||
ASSERT_EQ(additions1.size(), 1U);
|
||||
ASSERT_TRUE(garbages1.empty());
|
||||
@@ -1469,7 +1473,7 @@ TEST_F(DBBlobDirectWriteTest,
|
||||
|
||||
std::vector<BlobFileAddition> additions2;
|
||||
std::vector<BlobFileGarbage> garbages2;
|
||||
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), /*num_generations=*/1,
|
||||
ASSERT_OK(mgr->PrepareFlushAdditions(WriteOptions(), memtable_ids,
|
||||
&additions2, &garbages2));
|
||||
ASSERT_EQ(additions2.size(), 1U);
|
||||
ASSERT_TRUE(garbages2.empty());
|
||||
@@ -1875,6 +1879,131 @@ TEST_F(DBBlobDirectWriteTest,
|
||||
AssertOrderedTraceStoresLogicalPut(options, /*expect_blob_files=*/false);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobDirectWriteTest, OverlappingFlushesPublishTheirOwnBlobFiles) {
|
||||
for (bool atomic_flush : {false, true}) {
|
||||
SCOPED_TRACE("atomic_flush=" + std::to_string(atomic_flush));
|
||||
Options options = GetDirectWriteOptions();
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.max_background_jobs = 8;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
const std::string value(1024, 'v');
|
||||
|
||||
// The flush of the memtable holding "key1" waits while it writes its
|
||||
// table file, until a second flush, of the memtable holding "key2", has
|
||||
// finished its own table file.
|
||||
std::atomic<int> table_writes{0};
|
||||
std::atomic<bool> released{false};
|
||||
std::promise<void> first_flush_writing;
|
||||
std::promise<void> release_first_flush;
|
||||
std::shared_future<void> release_first_flush_future =
|
||||
release_first_flush.get_future().share();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
|
||||
if (table_writes.fetch_add(1) == 0) {
|
||||
first_flush_writing.set_value();
|
||||
release_first_flush_future.wait();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
atomic_flush ? "DBImpl::AtomicFlushMemTablesToOutputFiles:WaitCV"
|
||||
: "DBImpl::FlushMemTableToOutputFile:Finish",
|
||||
[&](void*) {
|
||||
if (!released.exchange(true)) {
|
||||
release_first_flush.set_value();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
FlushOptions no_wait;
|
||||
no_wait.wait = false;
|
||||
no_wait.allow_write_stall = true;
|
||||
ASSERT_OK(Put("key1", value));
|
||||
ASSERT_OK(db_->Flush(no_wait));
|
||||
first_flush_writing.get_future().wait();
|
||||
ASSERT_OK(Put("key2", value));
|
||||
ASSERT_OK(db_->Flush(no_wait));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_OK(dbfull()->TEST_GetBGError());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ(value, Get("key1"));
|
||||
ASSERT_EQ(value, Get("key2"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobDirectWriteTest, RetryAfterOlderOverlappingFlushFails) {
|
||||
Options options = GetDirectWriteOptions();
|
||||
options.max_background_jobs = 8;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_bgerror_resume_count = 0;
|
||||
DestroyAndReopen(options);
|
||||
const std::string value(1024, 'v');
|
||||
|
||||
// The flush of the memtable holding "key2" finishes while the older flush,
|
||||
// of the memtable holding "key1", is still writing its table file. The
|
||||
// newer flush's results wait to be installed after the older one's, but the
|
||||
// older flush fails, which rolls the newer memtable back too.
|
||||
std::atomic<int> table_writes{0};
|
||||
std::atomic<bool> released{false};
|
||||
std::atomic<bool> failed{false};
|
||||
std::promise<void> first_flush_writing;
|
||||
std::promise<void> release_first_flush;
|
||||
std::shared_future<void> release_first_flush_future =
|
||||
release_first_flush.get_future().share();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:num_memtables", [&](void*) {
|
||||
if (table_writes.fetch_add(1) == 0) {
|
||||
first_flush_writing.set_value();
|
||||
release_first_flush_future.wait();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushMemTableToOutputFile:Finish", [&](void*) {
|
||||
if (!released.exchange(true)) {
|
||||
release_first_flush.set_value();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:s", [&](void* arg) {
|
||||
if (released.load() && !failed.exchange(true)) {
|
||||
IOStatus injected = IOStatus::IOError("injected");
|
||||
injected.SetRetryable(true);
|
||||
*static_cast<Status*>(arg) = injected;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
FlushOptions no_wait;
|
||||
no_wait.wait = false;
|
||||
no_wait.allow_write_stall = true;
|
||||
ASSERT_OK(Put("key1", value));
|
||||
ASSERT_OK(db_->Flush(no_wait));
|
||||
first_flush_writing.get_future().wait();
|
||||
ASSERT_OK(Put("key2", value));
|
||||
ASSERT_OK(db_->Flush(no_wait));
|
||||
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_TRUE(failed.load());
|
||||
|
||||
// Retrying the flush must still find the blob files of both memtables.
|
||||
ASSERT_OK(dbfull()->Resume());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_GetBGError());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ(value, Get("key1"));
|
||||
ASSERT_EQ(value, Get("key2"));
|
||||
}
|
||||
|
||||
class DBBlobDirectWriteWithTimestampTest : public DBBasicTestWithTimestampBase {
|
||||
protected:
|
||||
DBBlobDirectWriteWithTimestampTest()
|
||||
|
||||
@@ -419,6 +419,13 @@ struct rocksdb_readoptions_t {
|
||||
std::function<bool(const TableProperties&)> table_filter_func;
|
||||
std::shared_ptr<UserDefinedIndexFactory> table_index_factory;
|
||||
|
||||
rocksdb_readoptions_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_readoptions_t(const rocksdb_readoptions_t&) = delete;
|
||||
rocksdb_readoptions_t& operator=(const rocksdb_readoptions_t&) = delete;
|
||||
rocksdb_readoptions_t(rocksdb_readoptions_t&&) = delete;
|
||||
rocksdb_readoptions_t& operator=(rocksdb_readoptions_t&&) = delete;
|
||||
|
||||
~rocksdb_readoptions_t() { ClearTableFilter(); }
|
||||
|
||||
void ClearTableFilter() {
|
||||
@@ -805,14 +812,22 @@ struct rocksdb_open_and_compact_options_t {
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
void* state_ = nullptr;
|
||||
void (*destructor_)(void*) = nullptr;
|
||||
unsigned char (*filter_)(void*, int level, const char* key, size_t key_length,
|
||||
const char* existing_value, size_t value_length,
|
||||
char** new_value, size_t* new_value_length,
|
||||
unsigned char* value_changed);
|
||||
const char* (*name_)(void*);
|
||||
unsigned char ignore_snapshots_;
|
||||
unsigned char* value_changed) = nullptr;
|
||||
const char* (*name_)(void*) = nullptr;
|
||||
unsigned char ignore_snapshots_ = true;
|
||||
|
||||
rocksdb_compactionfilter_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_compactionfilter_t(const rocksdb_compactionfilter_t&) = delete;
|
||||
rocksdb_compactionfilter_t& operator=(const rocksdb_compactionfilter_t&) =
|
||||
delete;
|
||||
rocksdb_compactionfilter_t(rocksdb_compactionfilter_t&&) = delete;
|
||||
rocksdb_compactionfilter_t& operator=(rocksdb_compactionfilter_t&&) = delete;
|
||||
|
||||
~rocksdb_compactionfilter_t() override { (*destructor_)(state_); }
|
||||
|
||||
@@ -838,11 +853,22 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
void* state_ = nullptr;
|
||||
void (*destructor_)(void*) = nullptr;
|
||||
rocksdb_compactionfilter_t* (*create_compaction_filter_)(
|
||||
void*, rocksdb_compactionfiltercontext_t* context);
|
||||
const char* (*name_)(void*);
|
||||
void*, rocksdb_compactionfiltercontext_t* context) = nullptr;
|
||||
const char* (*name_)(void*) = nullptr;
|
||||
|
||||
rocksdb_compactionfilterfactory_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_compactionfilterfactory_t(const rocksdb_compactionfilterfactory_t&) =
|
||||
delete;
|
||||
rocksdb_compactionfilterfactory_t& operator=(
|
||||
const rocksdb_compactionfilterfactory_t&) = delete;
|
||||
rocksdb_compactionfilterfactory_t(rocksdb_compactionfilterfactory_t&&) =
|
||||
delete;
|
||||
rocksdb_compactionfilterfactory_t& operator=(
|
||||
rocksdb_compactionfilterfactory_t&&) = delete;
|
||||
|
||||
~rocksdb_compactionfilterfactory_t() override { (*destructor_)(state_); }
|
||||
|
||||
@@ -873,6 +899,12 @@ struct rocksdb_comparator_t : public Comparator {
|
||||
|
||||
rocksdb_comparator_t(size_t ts_size) : Comparator(ts_size) {}
|
||||
|
||||
// No copying allowed
|
||||
rocksdb_comparator_t(const rocksdb_comparator_t&) = delete;
|
||||
rocksdb_comparator_t& operator=(const rocksdb_comparator_t&) = delete;
|
||||
rocksdb_comparator_t(rocksdb_comparator_t&&) = delete;
|
||||
rocksdb_comparator_t& operator=(rocksdb_comparator_t&&) = delete;
|
||||
|
||||
~rocksdb_comparator_t() override { (*destructor_)(state_); }
|
||||
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
@@ -904,9 +936,16 @@ struct rocksdb_comparator_t : public Comparator {
|
||||
};
|
||||
|
||||
struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
void* state_ = nullptr;
|
||||
void (*destructor_)(void*) = nullptr;
|
||||
const char* (*name_)(void*) = nullptr;
|
||||
|
||||
rocksdb_filterpolicy_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_filterpolicy_t(const rocksdb_filterpolicy_t&) = delete;
|
||||
rocksdb_filterpolicy_t& operator=(const rocksdb_filterpolicy_t&) = delete;
|
||||
rocksdb_filterpolicy_t(rocksdb_filterpolicy_t&&) = delete;
|
||||
rocksdb_filterpolicy_t& operator=(rocksdb_filterpolicy_t&&) = delete;
|
||||
|
||||
~rocksdb_filterpolicy_t() override { (*destructor_)(state_); }
|
||||
|
||||
@@ -914,19 +953,29 @@ struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
};
|
||||
|
||||
struct rocksdb_mergeoperator_t : public MergeOperator {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
void* state_ = nullptr;
|
||||
void (*destructor_)(void*) = nullptr;
|
||||
const char* (*name_)(void*) = nullptr;
|
||||
char* (*full_merge_)(void*, const char* key, size_t key_length,
|
||||
const char* existing_value, size_t existing_value_length,
|
||||
const char* const* operands_list,
|
||||
const size_t* operands_list_length, int num_operands,
|
||||
unsigned char* success, size_t* new_value_length);
|
||||
unsigned char* success,
|
||||
size_t* new_value_length) = nullptr;
|
||||
char* (*partial_merge_)(void*, const char* key, size_t key_length,
|
||||
const char* const* operands_list,
|
||||
const size_t* operands_list_length, int num_operands,
|
||||
unsigned char* success, size_t* new_value_length);
|
||||
void (*delete_value_)(void*, const char* value, size_t value_length);
|
||||
unsigned char* success,
|
||||
size_t* new_value_length) = nullptr;
|
||||
void (*delete_value_)(void*, const char* value,
|
||||
size_t value_length) = nullptr;
|
||||
|
||||
rocksdb_mergeoperator_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_mergeoperator_t(const rocksdb_mergeoperator_t&) = delete;
|
||||
rocksdb_mergeoperator_t& operator=(const rocksdb_mergeoperator_t&) = delete;
|
||||
rocksdb_mergeoperator_t(rocksdb_mergeoperator_t&&) = delete;
|
||||
rocksdb_mergeoperator_t& operator=(rocksdb_mergeoperator_t&&) = delete;
|
||||
|
||||
~rocksdb_mergeoperator_t() override { (*destructor_)(state_); }
|
||||
|
||||
@@ -1008,12 +1057,19 @@ struct rocksdb_env_t {
|
||||
};
|
||||
|
||||
struct rocksdb_slicetransform_t : public SliceTransform {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
void* state_ = nullptr;
|
||||
void (*destructor_)(void*) = nullptr;
|
||||
const char* (*name_)(void*) = nullptr;
|
||||
char* (*transform_)(void*, const char* key, size_t length,
|
||||
size_t* dst_length);
|
||||
unsigned char (*in_domain_)(void*, const char* key, size_t length);
|
||||
size_t* dst_length) = nullptr;
|
||||
unsigned char (*in_domain_)(void*, const char* key, size_t length) = nullptr;
|
||||
|
||||
rocksdb_slicetransform_t() = default;
|
||||
// No copying allowed
|
||||
rocksdb_slicetransform_t(const rocksdb_slicetransform_t&) = delete;
|
||||
rocksdb_slicetransform_t& operator=(const rocksdb_slicetransform_t&) = delete;
|
||||
rocksdb_slicetransform_t(rocksdb_slicetransform_t&&) = delete;
|
||||
rocksdb_slicetransform_t& operator=(rocksdb_slicetransform_t&&) = delete;
|
||||
|
||||
~rocksdb_slicetransform_t() override { (*destructor_)(state_); }
|
||||
|
||||
@@ -1337,6 +1393,14 @@ struct rocksdb_compactionservice_t : public CompactionService {
|
||||
cancel_awaiting_jobs_(cancel_awaiting_jobs),
|
||||
on_installation_(on_installation) {}
|
||||
|
||||
// No copying allowed
|
||||
rocksdb_compactionservice_t(const rocksdb_compactionservice_t&) = delete;
|
||||
rocksdb_compactionservice_t& operator=(const rocksdb_compactionservice_t&) =
|
||||
delete;
|
||||
rocksdb_compactionservice_t(rocksdb_compactionservice_t&&) = delete;
|
||||
rocksdb_compactionservice_t& operator=(rocksdb_compactionservice_t&&) =
|
||||
delete;
|
||||
|
||||
~rocksdb_compactionservice_t() override {
|
||||
if (destructor_) {
|
||||
(*destructor_)(state_);
|
||||
@@ -6963,7 +7027,12 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(
|
||||
// they delegate to a NewBloomFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
struct Wrapper : public rocksdb_filterpolicy_t {
|
||||
const FilterPolicy* rep_;
|
||||
const FilterPolicy* rep_ = nullptr;
|
||||
Wrapper() = default;
|
||||
Wrapper(const Wrapper&) = delete;
|
||||
Wrapper& operator=(const Wrapper&) = delete;
|
||||
Wrapper(Wrapper&&) = delete;
|
||||
Wrapper& operator=(Wrapper&&) = delete;
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
const char* CompatibilityName() const override {
|
||||
@@ -7003,7 +7072,12 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_format(
|
||||
// they delegate to a NewRibbonFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
struct Wrapper : public rocksdb_filterpolicy_t {
|
||||
const FilterPolicy* rep_;
|
||||
const FilterPolicy* rep_ = nullptr;
|
||||
Wrapper() = default;
|
||||
Wrapper(const Wrapper&) = delete;
|
||||
Wrapper& operator=(const Wrapper&) = delete;
|
||||
Wrapper(Wrapper&&) = delete;
|
||||
Wrapper& operator=(Wrapper&&) = delete;
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
const char* CompatibilityName() const override {
|
||||
@@ -10017,6 +10091,16 @@ unsigned char rocksdb_options_get_write_dbid_to_manifest(
|
||||
return opt->rep.write_dbid_to_manifest;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.track_options_file_number_in_manifest = v;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_track_options_file_number_in_manifest(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.track_options_file_number_in_manifest;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_write_identity_file(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.write_identity_file = v;
|
||||
|
||||
@@ -26,6 +26,8 @@ class CoalescingIterator : public Iterator {
|
||||
// No copy allowed
|
||||
CoalescingIterator(const CoalescingIterator&) = delete;
|
||||
CoalescingIterator& operator=(const CoalescingIterator&) = delete;
|
||||
CoalescingIterator(CoalescingIterator&&) = delete;
|
||||
CoalescingIterator& operator=(CoalescingIterator&&) = delete;
|
||||
|
||||
bool Valid() const override { return impl_.Valid(); }
|
||||
void SeekToFirst() override { impl_.SeekToFirst(); }
|
||||
|
||||
+10
-5
@@ -613,7 +613,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
const FileOptions* file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id, bool read_only, bool fast_sst_open)
|
||||
const std::string& db_session_id, bool read_only,
|
||||
bool options_are_sanitized, bool fast_sst_open)
|
||||
: id_(id),
|
||||
name_(name),
|
||||
dummy_versions_(_dummy_versions),
|
||||
@@ -623,7 +624,10 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
dropped_(false),
|
||||
flush_skip_reschedule_(false),
|
||||
internal_comparator_(cf_options.comparator),
|
||||
initial_cf_options_(SanitizeCfOptions(db_options, read_only, cf_options)),
|
||||
initial_cf_options_(
|
||||
options_are_sanitized
|
||||
? cf_options
|
||||
: SanitizeCfOptions(db_options, read_only, cf_options)),
|
||||
ioptions_(db_options, initial_cf_options_),
|
||||
mutable_cf_options_(initial_cf_options_),
|
||||
is_delete_range_supported_(
|
||||
@@ -1838,7 +1842,7 @@ ColumnFamilySet::ColumnFamilySet(
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
|
||||
block_cache_tracer, io_tracer, db_id, db_session_id,
|
||||
/*read_only*/ true)),
|
||||
/*read_only=*/true, /*options_are_sanitized=*/false)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
@@ -2007,12 +2011,13 @@ void ColumnFamilySet::EndBulkFlushableMemAccountingUpdate() {
|
||||
// under a DB mutex AND write thread
|
||||
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
const ColumnFamilyOptions& options, bool read_only) {
|
||||
const ColumnFamilyOptions& options, bool read_only,
|
||||
bool options_are_sanitized) {
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
|
||||
db_id_, db_session_id_, read_only, fast_sst_open_);
|
||||
db_id_, db_session_id_, read_only, options_are_sanitized, fast_sst_open_);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
auto ucmp = new_cfd->user_comparator();
|
||||
|
||||
+17
-13
@@ -171,7 +171,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
ColumnFamilyHandleImpl(ColumnFamilyData* cfd, DBImpl* db,
|
||||
InstrumentedMutex* mutex);
|
||||
// destroy without mutex
|
||||
virtual ~ColumnFamilyHandleImpl();
|
||||
~ColumnFamilyHandleImpl() override;
|
||||
virtual ColumnFamilyData* cfd() const { return cfd_; }
|
||||
virtual DBImpl* db() const { return db_; }
|
||||
|
||||
@@ -654,17 +654,15 @@ class ColumnFamilyData {
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const FileOptions* file_options,
|
||||
ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
bool read_only, bool fast_sst_open = false);
|
||||
ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* dummy_versions,
|
||||
Cache* table_cache, WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options, const ImmutableDBOptions& db_options,
|
||||
const FileOptions* file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id,
|
||||
const std::string& db_session_id, bool read_only,
|
||||
bool options_are_sanitized, bool fast_sst_open = false);
|
||||
|
||||
std::vector<std::string> GetDbPaths() const;
|
||||
|
||||
@@ -850,7 +848,8 @@ class ColumnFamilySet {
|
||||
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
|
||||
Version* dummy_version,
|
||||
const ColumnFamilyOptions& options,
|
||||
bool read_only);
|
||||
bool read_only,
|
||||
bool options_are_sanitized);
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
|
||||
const {
|
||||
@@ -951,6 +950,11 @@ class RefedColumnFamilySet {
|
||||
explicit iterator(ColumnFamilySet::iterator wrapped) : wrapped_(wrapped) {
|
||||
MaybeRef(*wrapped_);
|
||||
}
|
||||
// No copying allowed
|
||||
iterator(const iterator&) = delete;
|
||||
iterator& operator=(const iterator&) = delete;
|
||||
iterator(iterator&&) = delete;
|
||||
iterator& operator=(iterator&&) = delete;
|
||||
~iterator() { MaybeUnref(*wrapped_); }
|
||||
inline void MaybeRef(ColumnFamilyData* cfd) {
|
||||
if (cfd->GetID() != ColumnFamilyData::kDummyColumnFamilyDataId) {
|
||||
|
||||
+499
-38
@@ -25,6 +25,7 @@
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "util/defer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -113,7 +115,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
void Build(int base, int n, int flush_every = 0) {
|
||||
std::string key_space, value_space;
|
||||
std::string key_space;
|
||||
std::string value_space;
|
||||
WriteBatch batch;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -179,6 +182,35 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
db_.reset();
|
||||
}
|
||||
|
||||
void AssertColumnFamilyNames(const std::vector<std::string>& expected) {
|
||||
std::vector<std::string> column_families;
|
||||
ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &column_families));
|
||||
ASSERT_EQ(expected, column_families);
|
||||
}
|
||||
|
||||
void LoadLatestColumnFamilyDescriptors(
|
||||
std::vector<ColumnFamilyDescriptor>* descriptors) {
|
||||
ConfigOptions config_options;
|
||||
config_options.env = db_options_.env;
|
||||
DBOptions loaded_db_options;
|
||||
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
|
||||
descriptors));
|
||||
}
|
||||
|
||||
size_t CountOptionsFiles() {
|
||||
std::vector<std::string> children;
|
||||
EXPECT_OK(db_options_.env->GetChildren(dbname_, &children));
|
||||
size_t count = 0;
|
||||
for (const auto& child : children) {
|
||||
uint64_t number = 0;
|
||||
FileType type;
|
||||
if (ParseFileName(child, &number, &type) && type == kOptionsFile) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Status TryOpen(std::vector<std::string> cf,
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -514,8 +546,9 @@ class ColumnFamilyTest
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
|
||||
testing::Values(kLatestBbtFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, ColumnFamilyTest,
|
||||
testing::Values(BlockBasedTableOptions::kLatestSupportedFormatVersion));
|
||||
|
||||
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
@@ -705,8 +738,11 @@ INSTANTIATE_TEST_CASE_P(
|
||||
std::make_tuple(test::kDefaultFormatVersion, false)));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, FlushEmptyCFTestWithParam,
|
||||
testing::Values(std::make_tuple(kLatestBbtFormatVersion, true),
|
||||
std::make_tuple(kLatestBbtFormatVersion, false)));
|
||||
testing::Values(
|
||||
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
|
||||
true),
|
||||
std::make_tuple(BlockBasedTableOptions::kLatestSupportedFormatVersion,
|
||||
false)));
|
||||
|
||||
TEST_P(ColumnFamilyTest, AddDrop) {
|
||||
Open();
|
||||
@@ -917,7 +953,9 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
CreateColumnFamilies({"cf1", "cf2"});
|
||||
|
||||
// fill up the DB
|
||||
std::string one, two, three;
|
||||
std::string one;
|
||||
std::string two;
|
||||
std::string three;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
PutFixed64(&three, 3);
|
||||
@@ -1125,7 +1163,10 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three"});
|
||||
ColumnFamilyOptions default_cf, one, two, three;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
ColumnFamilyOptions three;
|
||||
// setup options. all column families have max_write_buffer_number setup to 10
|
||||
// "default" -> 100KB memtable, start flushing immediately
|
||||
// "one" -> 200KB memtable, start flushing with two immutable memtables
|
||||
@@ -1277,7 +1318,9 @@ TEST_P(ColumnFamilyTest, GetComparator) {
|
||||
|
||||
// Add three column families - one with no comparator and two
|
||||
// with comparators specified
|
||||
ColumnFamilyOptions second, third, fourth;
|
||||
ColumnFamilyOptions second;
|
||||
ColumnFamilyOptions third;
|
||||
ColumnFamilyOptions fourth;
|
||||
second.comparator = &third_comparator;
|
||||
third.comparator = &fourth_comparator;
|
||||
CreateColumnFamilies({"second", "third", "fourth"}, {second, third, fourth});
|
||||
@@ -1290,12 +1333,16 @@ TEST_P(ColumnFamilyTest, GetComparator) {
|
||||
TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
Open();
|
||||
CreateColumnFamilies({"first", "second"});
|
||||
ColumnFamilyOptions default_cf, first, second;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions first;
|
||||
ColumnFamilyOptions second;
|
||||
first.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
second.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
Reopen({default_cf, first, second});
|
||||
|
||||
std::string one, two, three;
|
||||
std::string one;
|
||||
std::string two;
|
||||
std::string three;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
PutFixed64(&three, 3);
|
||||
@@ -1320,7 +1367,9 @@ TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
@@ -1392,7 +1441,9 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1492,7 +1543,9 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1589,7 +1642,9 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1688,7 +1743,8 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1788,7 +1844,8 @@ TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1879,7 +1936,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -1976,7 +2034,8 @@ TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
@@ -2189,7 +2248,9 @@ TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
ColumnFamilyOptions default_cf;
|
||||
ColumnFamilyOptions one;
|
||||
ColumnFamilyOptions two;
|
||||
default_cf.write_buffer_size = 100000; // small write buffer size
|
||||
default_cf.arena_block_size = 4096;
|
||||
default_cf.disable_auto_compactions = true;
|
||||
@@ -2250,8 +2311,378 @@ struct CountOptionsFilesFs : public FileSystemWrapper {
|
||||
|
||||
std::atomic<int> options_files_created{};
|
||||
};
|
||||
|
||||
struct FailOptionsFilesFs : public FileSystemWrapper {
|
||||
explicit FailOptionsFilesFs(const std::shared_ptr<FileSystem>& t)
|
||||
: FileSystemWrapper(t) {}
|
||||
const char* Name() const override { return "FailOptionsFilesFs"; }
|
||||
|
||||
IOStatus NewWritableFile(const std::string& f, const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* r,
|
||||
IODebugContext* dbg) override {
|
||||
if (fail_options_writes.load(std::memory_order_relaxed) &&
|
||||
f.find("OPTIONS-") != std::string::npos) {
|
||||
return IOStatus::IOError("injected OPTIONS-file creation failure");
|
||||
}
|
||||
return FileSystemWrapper::NewWritableFile(f, file_opts, r, dbg);
|
||||
}
|
||||
|
||||
std::atomic<bool> fail_options_writes{false};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedOptionsWriteFailureDoesNotChangeManifest) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
db_options_.max_manifest_file_size = 1;
|
||||
db_options_.max_manifest_space_amp_pct = 0;
|
||||
auto fail_fs =
|
||||
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
|
||||
auto fail_env =
|
||||
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
|
||||
|
||||
Open();
|
||||
OptionsFileManifestState before_failure;
|
||||
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
|
||||
dbname_, db_options_.env->GetFileSystem().get(), &before_failure));
|
||||
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
|
||||
|
||||
OptionsFileManifestState after_failure;
|
||||
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
|
||||
dbname_, db_options_.env->GetFileSystem().get(), &after_failure));
|
||||
ASSERT_EQ(before_failure.has_committed_options_file_number,
|
||||
after_failure.has_committed_options_file_number);
|
||||
ASSERT_EQ(before_failure.committed_options_file_number,
|
||||
after_failure.committed_options_file_number);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(1U, loaded_cfs.size());
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
|
||||
|
||||
const uint64_t old_manifest_number =
|
||||
dbfull()->GetVersionSet()->manifest_file_number();
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key", "value"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_NE(old_manifest_number,
|
||||
dbfull()->GetVersionSet()->manifest_file_number());
|
||||
|
||||
OptionsFileManifestState after_rotation;
|
||||
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
|
||||
dbname_, db_options_.env->GetFileSystem().get(), &after_rotation));
|
||||
ASSERT_EQ(before_failure.has_committed_options_file_number,
|
||||
after_rotation.has_committed_options_file_number);
|
||||
ASSERT_EQ(before_failure.committed_options_file_number,
|
||||
after_rotation.committed_options_file_number);
|
||||
Close();
|
||||
|
||||
AssertColumnFamilyNames({kDefaultColumnFamilyName});
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedDropFailsBeforeManifestOnOptionsError) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
auto fail_fs =
|
||||
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
|
||||
auto fail_env =
|
||||
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
|
||||
|
||||
Open();
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
|
||||
ASSERT_NE(nullptr, handle);
|
||||
|
||||
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
|
||||
ASSERT_NOK(db_->DropColumnFamily(handle));
|
||||
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
Close();
|
||||
|
||||
AssertColumnFamilyNames({kDefaultColumnFamilyName, "one"});
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedManifestFailureKeepsPreviousOptions) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
auto fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
|
||||
|
||||
Open();
|
||||
std::string selected_options_before;
|
||||
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
|
||||
&selected_options_before));
|
||||
|
||||
std::atomic<bool> inject_error{true};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (inject_error.exchange(false)) {
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
|
||||
/*retryable=*/false, /*has_data_loss=*/false);
|
||||
fault_fs->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_NOK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
|
||||
std::string selected_options_after;
|
||||
ASSERT_OK(GetLatestOptionsFileName(dbname_, db_options_.env,
|
||||
&selected_options_after));
|
||||
ASSERT_EQ(selected_options_before, selected_options_after);
|
||||
Close();
|
||||
|
||||
AssertColumnFamilyNames({kDefaultColumnFamilyName});
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, FirstTrackedCreateFailureIgnoresUncommittedOptions) {
|
||||
// Create a legacy DB without a committed number, then enable tracking while
|
||||
// Open also
|
||||
// needs to create a missing CF. The current topology must be bound before
|
||||
// the proposed topology OPTIONS file can become a durable orphan.
|
||||
Open();
|
||||
Close();
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
db_options_.create_missing_column_families = true;
|
||||
|
||||
auto fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
|
||||
|
||||
bool injected = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::CreateColumnFamilyImpl:AfterPersistOptions", [&](void*) {
|
||||
if (!injected) {
|
||||
injected = true;
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
|
||||
/*retryable=*/false, /*has_data_loss=*/false);
|
||||
fault_fs->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_NOK(TryOpen({kDefaultColumnFamilyName, "one"}));
|
||||
ASSERT_TRUE(injected);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(1U, loaded_cfs.size());
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
|
||||
|
||||
OptionsFileManifestState protocol_state;
|
||||
ASSERT_OK(VersionSet::GetOptionsFileManifestState(
|
||||
dbname_, db_options_.env->GetFileSystem().get(), &protocol_state));
|
||||
ASSERT_TRUE(protocol_state.has_committed_options_file_number);
|
||||
std::string selected_options;
|
||||
ASSERT_OK(
|
||||
GetLatestOptionsFileName(dbname_, db_options_.env, &selected_options));
|
||||
ASSERT_EQ(OptionsFileName(protocol_state.committed_options_file_number),
|
||||
selected_options);
|
||||
|
||||
AssertColumnFamilyNames({kDefaultColumnFamilyName});
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedCreatePersistsCommittedOptions) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
Open();
|
||||
|
||||
ColumnFamilyOptions requested;
|
||||
requested.arena_block_size = 0;
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamily(requested, "one", &handle));
|
||||
const ColumnFamilyOptions effective = db_->GetOptions(handle);
|
||||
ASSERT_GT(effective.arena_block_size, 0U);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(2U, loaded_cfs.size());
|
||||
ASSERT_EQ("one", loaded_cfs[1].name);
|
||||
ASSERT_EQ(effective.arena_block_size, loaded_cfs[1].options.arena_block_size);
|
||||
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedBatchTopologyWritesOneOptionsSnapshot) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
auto count_fs =
|
||||
std::make_shared<CountOptionsFilesFs>(db_options_.env->GetFileSystem());
|
||||
auto count_env =
|
||||
std::make_unique<CompositeEnvWrapper>(db_options_.env, count_fs);
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, count_env.get());
|
||||
|
||||
Open();
|
||||
count_fs->options_files_created.store(0, std::memory_order_relaxed);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(),
|
||||
{"one", "two", "three"}, &handles));
|
||||
ASSERT_EQ(3U, handles.size());
|
||||
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
|
||||
|
||||
count_fs->options_files_created.store(0, std::memory_order_relaxed);
|
||||
ASSERT_OK(db_->DropColumnFamilies(handles));
|
||||
ASSERT_EQ(1, count_fs->options_files_created.load(std::memory_order_relaxed));
|
||||
for (auto* handle : handles) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(1U, loaded_cfs.size());
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs.front().name);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedPartialBatchFiltersOptionsSuperset) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
auto fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
|
||||
|
||||
Open();
|
||||
std::atomic<int> manifest_writes{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
|
||||
/*retryable=*/false, /*has_data_loss=*/false);
|
||||
fault_fs->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_NOK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
|
||||
&handles));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
ASSERT_EQ(1U, handles.size());
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(2U, loaded_cfs.size());
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
|
||||
ASSERT_EQ("one", loaded_cfs[1].name);
|
||||
|
||||
ConfigOptions config_options;
|
||||
config_options.env = db_options_.env;
|
||||
DBOptions loaded_db_options;
|
||||
ASSERT_OK(LoadLatestOptions(config_options, dbname_, &loaded_db_options,
|
||||
&loaded_cfs));
|
||||
ASSERT_OK(CheckOptionsCompatibility(config_options, dbname_,
|
||||
loaded_db_options, loaded_cfs));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, TrackedPartialBatchDropFiltersOptionsSuperset) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
auto fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(db_options_.env->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_env(NewCompositeEnv(fault_fs));
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fault_env.get());
|
||||
|
||||
Open();
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(db_->CreateColumnFamilies(ColumnFamilyOptions(), {"one", "two"},
|
||||
&handles));
|
||||
ASSERT_EQ(2U, handles.size());
|
||||
|
||||
std::atomic<int> manifest_writes{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (manifest_writes.fetch_add(1, std::memory_order_relaxed) == 1) {
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, /*seed=*/0, /*one_in=*/1,
|
||||
/*retryable=*/false, /*has_data_loss=*/false);
|
||||
fault_fs->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_NOK(db_->DropColumnFamilies(handles));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> loaded_cfs;
|
||||
LoadLatestColumnFamilyDescriptors(&loaded_cfs);
|
||||
ASSERT_EQ(2U, loaded_cfs.size());
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, loaded_cfs[0].name);
|
||||
ASSERT_EQ("two", loaded_cfs[1].name);
|
||||
for (auto* handle : handles) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, InvalidTrackedTopologyChangeCreatesNoOptionsOrphan) {
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
Open();
|
||||
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
|
||||
const size_t before_duplicate_create = CountOptionsFiles();
|
||||
ColumnFamilyHandle* duplicate = nullptr;
|
||||
ASSERT_TRUE(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &duplicate)
|
||||
.IsInvalidArgument());
|
||||
ASSERT_EQ(nullptr, duplicate);
|
||||
ASSERT_EQ(before_duplicate_create, CountOptionsFiles());
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(handle));
|
||||
const size_t before_duplicate_drop = CountOptionsFiles();
|
||||
ASSERT_TRUE(db_->DropColumnFamily(handle).IsInvalidArgument());
|
||||
ASSERT_EQ(before_duplicate_drop, CountOptionsFiles());
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest,
|
||||
PartialMultiDropConsumesLegacyOptionsPersistenceFailure) {
|
||||
auto fail_fs =
|
||||
std::make_shared<FailOptionsFilesFs>(db_options_.env->GetFileSystem());
|
||||
auto fail_env =
|
||||
std::make_unique<CompositeEnvWrapper>(db_options_.env, fail_fs);
|
||||
SaveAndRestore<Env*> save_restore_env(&db_options_.env, fail_env.get());
|
||||
|
||||
Open();
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "one", &handle));
|
||||
|
||||
fail_fs->fail_options_writes.store(true, std::memory_order_relaxed);
|
||||
Status s = db_->DropColumnFamilies({handle, db_->DefaultColumnFamily()});
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
fail_fs->fail_options_writes.store(false, std::memory_order_relaxed);
|
||||
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handle));
|
||||
Close();
|
||||
AssertColumnFamilyNames({kDefaultColumnFamilyName});
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
// Can't accidentally add CFs to an existing DB
|
||||
Open();
|
||||
@@ -2284,6 +2715,16 @@ TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
|
||||
Close();
|
||||
ASSERT_EQ(my_fs->options_files_created.load(), 2);
|
||||
|
||||
Destroy();
|
||||
db_options_.track_options_file_number_in_manifest = true;
|
||||
my_fs->options_files_created.store(0, std::memory_order_relaxed);
|
||||
ASSERT_OK(TryOpen({"default", "one", "two", "three", "four"}));
|
||||
Close();
|
||||
// A new tracked DB writes its initial snapshot, one proposed batch snapshot,
|
||||
// and the normal final DB::Open snapshot, independent of the number of
|
||||
// missing CFs.
|
||||
ASSERT_EQ(my_fs->options_files_created.load(), 3);
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, SanitizeCfOptions) {
|
||||
@@ -3271,6 +3712,12 @@ TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
// The DB keeps its Env pointer until Close(). Close it before the local Env
|
||||
// is destroyed, including when an ASSERT below exits the test early.
|
||||
Defer close_db_on_exit([this] {
|
||||
Close();
|
||||
db_options_.env = env_;
|
||||
});
|
||||
db_options_.max_background_flushes = 1;
|
||||
// When this option is removed, the test will need re-engineering
|
||||
db_options_.background_close_inactive_wals = true;
|
||||
@@ -3280,25 +3727,38 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
// Create an iterator holding the current super version.
|
||||
Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]);
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BGWorkFlush:done",
|
||||
"ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone"},
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer disable_sync_points(
|
||||
[] { ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); });
|
||||
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
ASSERT_OK(Flush(1));
|
||||
// Flush() can return before the background function releases all temporary
|
||||
// references to the old super version.
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:SetupFlushDone");
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush",
|
||||
"DBImpl::BackgroundCallFlush:start"},
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
|
||||
// Flush jobs will close previous WAL files after finishing. By
|
||||
// block flush jobs from running, we trigger a condition where
|
||||
// the iterator destructor should close the WAL files.
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
||||
Env::Priority::HIGH);
|
||||
// Make sure the task is sleeping. Otherwise, it might start to execute
|
||||
// after sleeping_task.WaitUntilDone() and cause TSAN warning.
|
||||
sleeping_task.WaitUntilSleeping();
|
||||
|
||||
// Flush jobs close previous WAL files after finishing. Keep the next flush
|
||||
// from starting so the iterator destructor is responsible for closing the
|
||||
// WAL. Occupying one Env worker is insufficient because the shared Env can
|
||||
// have multiple HIGH-priority workers.
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
|
||||
@@ -3306,18 +3766,15 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
// Deleting the iterator will clear its super version, triggering
|
||||
// closing all files
|
||||
delete it;
|
||||
it.reset();
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
|
||||
sleeping_task.WakeUp();
|
||||
sleeping_task.WaitUntilDone();
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile1:ReleaseFlush");
|
||||
WaitForFlush(1);
|
||||
|
||||
Reopen();
|
||||
ASSERT_EQ("mirko", Get(0, "fodor"));
|
||||
ASSERT_EQ("mirko", Get(1, "fodor"));
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
@@ -3574,7 +4031,8 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
// Leave cf_paths for one column families to be empty.
|
||||
// Files should be generated according to db_paths for that
|
||||
// column family.
|
||||
ColumnFamilyOptions cf_opt1, cf_opt2;
|
||||
ColumnFamilyOptions cf_opt1;
|
||||
ColumnFamilyOptions cf_opt2;
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
CreateColumnFamilies({"one", "two"}, {cf_opt1, cf_opt2});
|
||||
@@ -3599,7 +4057,8 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
Open();
|
||||
// Configure Column family specific paths.
|
||||
ColumnFamilyOptions cf_opt1, cf_opt2;
|
||||
ColumnFamilyOptions cf_opt1;
|
||||
ColumnFamilyOptions cf_opt2;
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "_one_1",
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
cf_opt2.cf_paths.emplace_back(dbname_ + "_two_1",
|
||||
@@ -3715,7 +4174,9 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
|
||||
// the behavior of manual flush is that it skips retaining UDTs.
|
||||
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
|
||||
public:
|
||||
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestBbtFormatVersion) {}
|
||||
ColumnFamilyRetainUDTTest()
|
||||
: ColumnFamilyTestBase(
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion) {}
|
||||
|
||||
void SetUp() override {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
|
||||
@@ -214,7 +214,8 @@ bool Compaction::IsBottommostLevel(
|
||||
} else {
|
||||
output_l0_idx = -1;
|
||||
}
|
||||
Slice smallest_key, largest_key;
|
||||
Slice smallest_key;
|
||||
Slice largest_key;
|
||||
GetBoundaryKeys(vstorage, inputs, &smallest_key, &largest_key);
|
||||
return !vstorage->RangeMightExistAfterSortedRun(smallest_key, largest_key,
|
||||
output_level, output_l0_idx);
|
||||
|
||||
@@ -114,6 +114,8 @@ class Compaction {
|
||||
// No copying allowed
|
||||
Compaction(const Compaction&) = delete;
|
||||
void operator=(const Compaction&) = delete;
|
||||
Compaction(Compaction&&) = delete;
|
||||
void operator=(Compaction&&) = delete;
|
||||
|
||||
~Compaction();
|
||||
|
||||
|
||||
@@ -1593,8 +1593,8 @@ TEST_P(CompactionIteratorTsGcTest, DropTombstones) {
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_sequence=*/kMaxSequenceNumber,
|
||||
/*merge_op=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
|
||||
@@ -2565,7 +2565,8 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
get_time_status.ToString().c_str());
|
||||
}
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
InternalKey tmp_start;
|
||||
InternalKey tmp_end;
|
||||
if (sub_compact->start.has_value()) {
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@ class CompactionJob {
|
||||
CompactionJob(CompactionJob&& job) = delete;
|
||||
CompactionJob(const CompactionJob& job) = delete;
|
||||
CompactionJob& operator=(const CompactionJob& job) = delete;
|
||||
CompactionJob& operator=(CompactionJob&& job) = delete;
|
||||
|
||||
// REQUIRED: mutex held
|
||||
// Prepare for the compaction by setting up boundaries for each subcompaction
|
||||
|
||||
@@ -322,8 +322,10 @@ class CompactionJobTestBase : public testing::Test {
|
||||
assert(contents.size() > 0);
|
||||
|
||||
bool first_key = true;
|
||||
std::string smallest, largest;
|
||||
InternalKey smallest_key, largest_key;
|
||||
std::string smallest;
|
||||
std::string largest;
|
||||
InternalKey smallest_key;
|
||||
InternalKey largest_key;
|
||||
SequenceNumber smallest_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber largest_seqno = 0;
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
|
||||
#include "db/compaction/compaction_outputs.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "logging/logging.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -373,6 +376,24 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void CompactionOutputs::PrepareNewOutputForFirstKey(
|
||||
const CompactionIterator& c_iter) {
|
||||
// A new output file starts a fresh partition-tracking window. Establish the
|
||||
// partitioner fast path from this file's first key: if the partitioner can
|
||||
// express its next boundary as "first key not sharing this prefix", cache
|
||||
// that prefix; otherwise fall back to per-key ShouldPartition(). This is the
|
||||
// only place the prefix needs to be (re)derived, since a partition boundary
|
||||
// always forces a file cut, and re-deriving on non-partition cuts just
|
||||
// recomputes the same still-valid prefix.
|
||||
if (partitioner_) {
|
||||
OptSlice prefix = partitioner_->ShouldPartitionByPrefix(c_iter.user_key());
|
||||
partitioner_prefix_valid_ = prefix.has_value();
|
||||
if (partitioner_prefix_valid_) {
|
||||
partitioner_prefix_.assign(prefix->data(), prefix->size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status CompactionOutputs::AddToOutput(
|
||||
const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
@@ -412,42 +433,117 @@ Status CompactionOutputs::AddToOutput(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// A new output file starts a fresh partition-tracking window. Establish the
|
||||
// partitioner fast path from this file's first key: if the partitioner can
|
||||
// express its next boundary as "first key not sharing this prefix", cache
|
||||
// that prefix; otherwise fall back to per-key ShouldPartition(). This is
|
||||
// the only place the prefix needs to be (re)derived, since a partition
|
||||
// boundary always forces a file cut, and re-deriving on non-partition cuts
|
||||
// just recomputes the same still-valid prefix.
|
||||
if (partitioner_) {
|
||||
OptSlice prefix =
|
||||
partitioner_->ShouldPartitionByPrefix(c_iter.user_key());
|
||||
partitioner_prefix_valid_ = prefix.has_value();
|
||||
if (partitioner_prefix_valid_) {
|
||||
partitioner_prefix_.assign(prefix->data(), prefix->size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// c_iter may emit range deletion keys, so update `last_key_for_partitioner_`
|
||||
// here before returning below when `is_range_del` is true. Only needed in the
|
||||
// fallback mode; the prefix fast path does not use it.
|
||||
if (partitioner_ && !partitioner_prefix_valid_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
PrepareNewOutputForFirstKey(c_iter);
|
||||
}
|
||||
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
// c_iter may emit range deletion keys, so update
|
||||
// `last_key_for_partitioner_` before returning. Only needed in the fallback
|
||||
// mode; the prefix fast path does not use it.
|
||||
if (partitioner_ && !partitioner_prefix_valid_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(builder_ != nullptr);
|
||||
const Slice& value = c_iter.value();
|
||||
s = current_output().validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
bool retried_after_new_table = false;
|
||||
while (true) {
|
||||
s = current_output().validator.CanAdd(key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
TableBuilderAddContext add_context;
|
||||
TableBuilderAddResult add_result =
|
||||
builder_->TryAdd(key, value, &add_context);
|
||||
|
||||
if (add_result == TableBuilderAddResult::kError) {
|
||||
s = builder_->status();
|
||||
if (s.ok()) {
|
||||
return Status::Corruption(
|
||||
"TableBuilder returned kError without a non-OK status");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
if (add_result == TableBuilderAddResult::kAdded) {
|
||||
current_output().validator.AddValidated(key, value);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(add_result == TableBuilderAddResult::kRequiresNewTable);
|
||||
s = builder_->status();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
const Compaction* compaction = compaction_;
|
||||
if (compaction == nullptr) {
|
||||
return Status::Corruption("Compaction output is missing compaction");
|
||||
}
|
||||
ColumnFamilyData* cfd = compaction->column_family_data();
|
||||
if (cfd == nullptr) {
|
||||
return Status::Corruption(
|
||||
"Compaction output is missing column family data");
|
||||
}
|
||||
const bool allow_data_in_errors =
|
||||
compaction->immutable_options().allow_data_in_errors;
|
||||
const std::string key_for_diagnostics =
|
||||
allow_data_in_errors ? key.ToString(/* hex */ true) : "hidden";
|
||||
if (compaction->output_level() == 0) {
|
||||
const std::string detail =
|
||||
"cf: " + cfd->GetName() + ", file_number: " +
|
||||
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
|
||||
", key: " + key_for_diagnostics;
|
||||
return Status::Corruption(
|
||||
"TableBuilder requested a new table while producing level 0 output",
|
||||
detail);
|
||||
}
|
||||
if (retried_after_new_table || builder_->IsEmpty()) {
|
||||
const std::string detail =
|
||||
"cf: " + cfd->GetName() + ", file_number: " +
|
||||
std::to_string(current_output().meta.fd.GetNumber()) + ", reason: " +
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason) +
|
||||
", key: " + key_for_diagnostics;
|
||||
return Status::Corruption(
|
||||
"TableBuilder requested a new table without accepting any rows",
|
||||
detail);
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
compaction->immutable_options().info_log,
|
||||
"[%s] Compaction output table builder requested a new table before "
|
||||
"key %s for file #%" PRIu64 ". reason: %s",
|
||||
cfd->GetName().c_str(), key_for_diagnostics.c_str(),
|
||||
current_output().meta.fd.GetNumber(),
|
||||
TableBuilderNewTableReasonToString(add_context.new_table_reason));
|
||||
|
||||
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
|
||||
key, &c_iter, *this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
grandparent_boundary_switched_num_ = 0;
|
||||
grandparent_overlapped_bytes_ =
|
||||
GetCurrentKeyGrandparentOverlappedBytes(key);
|
||||
range_tombstone_lower_bound_.Clear();
|
||||
|
||||
s = open_file_func(*this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
PrepareNewOutputForFirstKey(c_iter);
|
||||
retried_after_new_table = true;
|
||||
}
|
||||
|
||||
if (partitioner_ && !partitioner_prefix_valid_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
builder_->Add(key, value);
|
||||
|
||||
stats_.num_output_records++;
|
||||
current_output_file_size_ = builder_->EstimatedFileSize();
|
||||
@@ -512,10 +608,13 @@ Status CompactionOutputs::AddRangeDels(
|
||||
assert(!range_del_agg.IsEmpty());
|
||||
FileMetaData& meta = current_output().meta;
|
||||
const Comparator* ucmp = icmp.user_comparator();
|
||||
InternalKey lower_bound_buf, upper_bound_buf;
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
InternalKey lower_bound_buf;
|
||||
InternalKey upper_bound_buf;
|
||||
Slice lower_bound_guard;
|
||||
Slice upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
const Slice* lower_bound;
|
||||
const Slice* upper_bound;
|
||||
|
||||
// We first determine the internal key lower_bound and upper_bound for
|
||||
// this output file. All and only range tombstones that overlap with
|
||||
|
||||
@@ -245,6 +245,11 @@ class CompactionOutputs {
|
||||
// before processing the current key in compaction iterator.
|
||||
bool ShouldStopBefore(const CompactionIterator& c_iter);
|
||||
|
||||
// Re-establishes per-file state that depends on the first key accepted by a
|
||||
// freshly opened builder. This keeps caller-requested file cuts equivalent to
|
||||
// RocksDB's built-in file-boundary decisions for partition tracking.
|
||||
void PrepareNewOutputForFirstKey(const CompactionIterator& c_iter);
|
||||
|
||||
void Cleanup() {
|
||||
if (builder_ != nullptr) {
|
||||
// May happen if we get a shutdown call in the middle of compaction
|
||||
|
||||
@@ -236,7 +236,10 @@ void CompactionPicker::GetRange(const CompactionInputFiles& inputs1,
|
||||
} else if (inputs2.empty()) {
|
||||
GetRange(inputs1, smallest, largest);
|
||||
} else {
|
||||
InternalKey smallest1, smallest2, largest1, largest2;
|
||||
InternalKey smallest1;
|
||||
InternalKey smallest2;
|
||||
InternalKey largest1;
|
||||
InternalKey largest2;
|
||||
GetRange(inputs1, &smallest1, &largest1);
|
||||
GetRange(inputs2, &smallest2, &largest2);
|
||||
*smallest =
|
||||
@@ -286,7 +289,8 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
|
||||
return true;
|
||||
}
|
||||
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
|
||||
// Keep expanding inputs until we are sure that there is a "clean cut"
|
||||
// boundary between the files in input and the surrounding files.
|
||||
@@ -360,7 +364,8 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
|
||||
|
||||
// TODO: Intra L0 compactions can have the ranges overlapped, but the input
|
||||
// files cannot be overlapped in the order of L0 files.
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
GetRange(inputs, &smallest, &largest, Compaction::kInvalidLevel);
|
||||
if (proximal_level != Compaction::kInvalidLevel) {
|
||||
if (ioptions_.compaction_style == kCompactionStyleUniversal) {
|
||||
@@ -370,7 +375,8 @@ bool CompactionPicker::FilesRangeOverlapWithCompaction(
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
InternalKey proximal_smallest, proximal_largest;
|
||||
InternalKey proximal_smallest;
|
||||
InternalKey proximal_largest;
|
||||
GetRange(inputs, &proximal_smallest, &proximal_largest, level);
|
||||
if (RangeOverlapWithCompaction(proximal_smallest.user_key(),
|
||||
proximal_largest.user_key(),
|
||||
@@ -546,7 +552,8 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
assert(vstorage->NumLevelFiles(l) == 0);
|
||||
}
|
||||
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
|
||||
// Get the range one last time.
|
||||
GetRange(*inputs, &smallest, &largest);
|
||||
@@ -579,7 +586,8 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
CompactionInputFiles expanded_inputs;
|
||||
expanded_inputs.level = input_level;
|
||||
// Get closed interval of output level
|
||||
InternalKey all_start, all_limit;
|
||||
InternalKey all_start;
|
||||
InternalKey all_limit;
|
||||
GetRange(*inputs, *output_level_inputs, &all_start, &all_limit);
|
||||
bool try_overlapping_inputs = true;
|
||||
if (only_expand_towards_right) {
|
||||
@@ -605,7 +613,8 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
if (try_overlapping_inputs && expanded_inputs.size() > inputs->size() &&
|
||||
!AreFilesInCompaction(expanded_inputs.files) &&
|
||||
output_level_inputs_size + expanded_inputs_size < limit) {
|
||||
InternalKey new_start, new_limit;
|
||||
InternalKey new_start;
|
||||
InternalKey new_limit;
|
||||
GetRange(expanded_inputs, &new_start, &new_limit);
|
||||
CompactionInputFiles expanded_output_level_inputs;
|
||||
expanded_output_level_inputs.level = output_level;
|
||||
@@ -655,7 +664,8 @@ void CompactionPicker::GetGrandparents(
|
||||
VersionStorageInfo* vstorage, const CompactionInputFiles& inputs,
|
||||
const CompactionInputFiles& output_level_inputs,
|
||||
std::vector<FileMetaData*>* grandparents) {
|
||||
InternalKey start, limit;
|
||||
InternalKey start;
|
||||
InternalKey limit;
|
||||
GetRange(inputs, output_level_inputs, &start, &limit);
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2 or the first
|
||||
@@ -1303,7 +1313,8 @@ bool CompactionPicker::GetOverlappingL0Files(
|
||||
// Two level 0 compaction won't run at the same time, so don't need to worry
|
||||
// about files on level 0 being compacted.
|
||||
assert(level0_compactions_in_progress()->empty());
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
GetRange(*start_level_inputs, &smallest, &largest);
|
||||
// Note that the next call will discard the file we placed in
|
||||
// c->inputs_[0] earlier and replace it with an overlapping set
|
||||
|
||||
@@ -276,7 +276,7 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
NullCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual ~NullCompactionPicker() {}
|
||||
~NullCompactionPicker() override = default;
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* PickCompaction(
|
||||
|
||||
@@ -226,7 +226,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
vstorage->LevelFiles(last_level);
|
||||
|
||||
// Compute effective size and limit for comparison.
|
||||
uint64_t effective_size, effective_max;
|
||||
uint64_t effective_size;
|
||||
uint64_t effective_max;
|
||||
GetEffectiveSizeAndLimit(fifo_opts, total_size,
|
||||
vstorage->GetBlobStats().total_file_size,
|
||||
&effective_size, &effective_max);
|
||||
|
||||
@@ -393,7 +393,8 @@ void LevelCompactionBuilder::SetupOtherFilesWithRoundRobinExpansion() {
|
||||
}
|
||||
|
||||
size_t start_index = vstorage_->FilesByCompactionPri(start_level_)[0];
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
// Constraint 4 (No need to check again later)
|
||||
compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest);
|
||||
CompactionInputFiles output_level_inputs;
|
||||
@@ -675,7 +676,8 @@ bool LevelCompactionBuilder::TryPickL0TrivialMove() {
|
||||
const std::vector<FileMetaData*>& level_files =
|
||||
vstorage_->LevelFiles(start_level_);
|
||||
|
||||
InternalKey my_smallest, my_largest;
|
||||
InternalKey my_smallest;
|
||||
InternalKey my_largest;
|
||||
for (auto it = level_files.rbegin(); it != level_files.rend(); ++it) {
|
||||
CompactionInputFiles output_level_inputs;
|
||||
output_level_inputs.level = output_level_;
|
||||
@@ -886,7 +888,8 @@ bool LevelCompactionBuilder::PickFileToCompact() {
|
||||
// Note we rely on ExpandInputsToCleanCut() to tell us whether any output-
|
||||
// level files are locked, not just the extra ones pulled in for user-key
|
||||
// overlap.
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
compaction_picker_->GetRange(start_level_inputs_, &smallest, &largest);
|
||||
CompactionInputFiles output_level_inputs;
|
||||
output_level_inputs.level = output_level_;
|
||||
|
||||
@@ -5825,8 +5825,8 @@ TEST_F(CompactionPickerTest, FIFOBlobAwareSizeDropping) {
|
||||
// Sub-case 1: Single-level. SST = 200KB, blob = 500MB, limit = 200MB.
|
||||
// effective_size ~ 500MB >> 200MB -> drops from L0.
|
||||
{
|
||||
SetupFIFORatioBased(/*max_table=*/200ULL * 1024 * 1024,
|
||||
/*max_data=*/200ULL * 1024 * 1024,
|
||||
SetupFIFORatioBased(/*max_table_files_size=*/200ULL * 1024 * 1024,
|
||||
/*max_data_files_size=*/200ULL * 1024 * 1024,
|
||||
/*trigger=*/4,
|
||||
/*allow_compaction=*/true,
|
||||
/*use_kv_ratio=*/false);
|
||||
@@ -5880,8 +5880,8 @@ TEST_F(CompactionPickerTest, FIFOBlobAwareSizeDropping) {
|
||||
// Sub-case 3: Under limit. SST = 256KB, blob = 200MB, limit = 1GB.
|
||||
// effective_size ~ 200MB < 1GB -> no dropping.
|
||||
{
|
||||
SetupFIFORatioBased(/*max_table=*/1ULL * 1024 * 1024 * 1024,
|
||||
/*max_data=*/1ULL * 1024 * 1024 * 1024,
|
||||
SetupFIFORatioBased(/*max_table_files_size=*/1ULL * 1024 * 1024 * 1024,
|
||||
/*max_data_files_size=*/1ULL * 1024 * 1024 * 1024,
|
||||
/*trigger=*/4,
|
||||
/*allow_compaction=*/true,
|
||||
/*use_kv_ratio=*/true);
|
||||
@@ -6034,7 +6034,9 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
|
||||
/*existing_snapshots=*/{}, /*snapshot_checker=*/nullptr,
|
||||
vstorage_.get(), &log_buffer_, /*full_history_ts_low=*/""));
|
||||
|
||||
if (!compaction) return {};
|
||||
if (!compaction) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (out_reason) {
|
||||
*out_reason = compaction->compaction_reason();
|
||||
@@ -6080,7 +6082,9 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
|
||||
|
||||
size_t insert_pos = oldest_input_pos;
|
||||
for (size_t idx : sorted_indices) {
|
||||
if (idx < oldest_input_pos) insert_pos--;
|
||||
if (idx < oldest_input_pos) {
|
||||
insert_pos--;
|
||||
}
|
||||
}
|
||||
insert_pos = std::min(insert_pos, files.size());
|
||||
files.insert(files.begin() + insert_pos,
|
||||
@@ -6104,10 +6108,13 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
|
||||
sizes.push_back(f.size);
|
||||
}
|
||||
}
|
||||
if (sizes.empty()) return {0, 0, 0, 0.0, 0.0};
|
||||
if (sizes.empty()) {
|
||||
return {0, 0, 0, 0.0, 0.0};
|
||||
}
|
||||
|
||||
uint64_t sum = 0;
|
||||
uint64_t min_s = UINT64_MAX, max_s = 0;
|
||||
uint64_t min_s = UINT64_MAX;
|
||||
uint64_t max_s = 0;
|
||||
for (uint64_t s : sizes) {
|
||||
sum += s;
|
||||
min_s = std::min(min_s, s);
|
||||
@@ -6244,7 +6251,8 @@ class FIFORatioBasedCompactionPickingTest : public CompactionPickerTest {
|
||||
if (!inputs.empty() && reason == CompactionReason::kFIFOReduceNumFiles) {
|
||||
// Compute target from the picker's perspective: we need to estimate
|
||||
// it the same way the picker does.
|
||||
uint64_t total_sst = 0, total_blob = 0;
|
||||
uint64_t total_sst = 0;
|
||||
uint64_t total_blob = 0;
|
||||
for (const auto& f : files) {
|
||||
total_sst += f.size;
|
||||
total_blob += f.blob_size;
|
||||
|
||||
@@ -573,7 +573,8 @@ bool UniversalCompactionBuilder::IsInputFilesNonOverlapping(Compaction* c) {
|
||||
auto comparator = icmp_->user_comparator();
|
||||
int first_iter = 1;
|
||||
|
||||
InputFileInfo prev, curr;
|
||||
InputFileInfo prev;
|
||||
InputFileInfo curr;
|
||||
|
||||
SmallestKeyHeap smallest_key_priority_q =
|
||||
create_level_heap(c, icmp_->user_comparator());
|
||||
@@ -1421,7 +1422,8 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
// Since we need to go from lower level up and this is in the reverse
|
||||
// order, compared to level order, we first write to an reversed
|
||||
// data structure and finally copy them to compaction inputs.
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
picker_->GetRange(second_last_level_inputs, &smallest, &largest);
|
||||
std::vector<CompactionInputFiles> inputs_reverse;
|
||||
for (auto it = ++(++sorted_runs_.rbegin()); it != sorted_runs_.rend(); it++) {
|
||||
|
||||
@@ -2783,7 +2783,8 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
|
||||
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(dbname_, info.db_name);
|
||||
std::string db_id, db_session_id;
|
||||
std::string db_id;
|
||||
std::string db_session_id;
|
||||
ASSERT_OK(db_->GetDbIdentity(db_id));
|
||||
ASSERT_EQ(db_id, info.db_id);
|
||||
ASSERT_OK(db_->GetDbSessionId(db_session_id));
|
||||
|
||||
@@ -168,6 +168,9 @@ class SubcompactionState {
|
||||
? &proximal_level_outputs_
|
||||
: &compaction_outputs_;
|
||||
}
|
||||
SubcompactionState& operator=(SubcompactionState&&) = delete;
|
||||
|
||||
~SubcompactionState() = default;
|
||||
|
||||
// Add all the new files from this compaction to version_edit
|
||||
void AddOutputsEdit(VersionEdit* out_edit) const {
|
||||
|
||||
@@ -389,7 +389,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
// the range dels in both cold and hot SSTs
|
||||
auto snap = db_->GetSnapshot();
|
||||
latest_cold_seq = seq_history[2];
|
||||
std::string start = Key(25), end = Key(35);
|
||||
std::string start = Key(25);
|
||||
std::string end = Key(35);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
@@ -620,7 +621,8 @@ TEST_F(TieredCompactionTest, DISABLED_RangeBasedTieredStorageUniversal) {
|
||||
hot_start = Key(50);
|
||||
hot_end = Key(100);
|
||||
}
|
||||
std::string start = Key(1), end = Key(70);
|
||||
std::string start = Key(1);
|
||||
std::string end = Key(70);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
ASSERT_OK(Flush());
|
||||
@@ -1268,7 +1270,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
// the range dels in bottommost
|
||||
auto snap = db_->GetSnapshot();
|
||||
|
||||
std::string start = Key(25), end = Key(35);
|
||||
std::string start = Key(25);
|
||||
std::string end = Key(35);
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end));
|
||||
// add one small key and large key in the input level, to make sure it's able
|
||||
@@ -3045,9 +3048,10 @@ TEST_P(PrecludeLastLevelTest, RangeDelsCauseFileEndpointsToOverlap) {
|
||||
verify_db();
|
||||
|
||||
// Rewrite the middle file only. File endpoints should not change.
|
||||
std::string begin_key_buf = Key(kNumKeysPerFile + 1),
|
||||
end_key_buf = Key(kNumKeysPerFile + 2);
|
||||
Slice begin_key(begin_key_buf), end_key(end_key_buf);
|
||||
std::string begin_key_buf = Key(kNumKeysPerFile + 1);
|
||||
std::string end_key_buf = Key(kNumKeysPerFile + 2);
|
||||
Slice begin_key(begin_key_buf);
|
||||
Slice end_key(end_key_buf);
|
||||
ASSERT_OK(db_->SuggestCompactRange(db_->DefaultColumnFamily(), &begin_key,
|
||||
&end_key));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
@@ -315,8 +315,9 @@ class ComparatorDBTest
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest,
|
||||
testing::Values(kLatestBbtFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, ComparatorDBTest,
|
||||
testing::Values(BlockBasedTableOptions::kLatestSupportedFormatVersion));
|
||||
|
||||
TEST_P(ComparatorDBTest, Bytewise) {
|
||||
for (int rand_seed = 301; rand_seed < 306; rand_seed++) {
|
||||
|
||||
+12
-6
@@ -143,7 +143,8 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
|
||||
void Build(int n, int start, int flush_every) {
|
||||
std::string key_space, value_space;
|
||||
std::string key_space;
|
||||
std::string value_space;
|
||||
WriteBatch batch;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
|
||||
@@ -773,7 +774,8 @@ TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
|
||||
// Write must eventually fail because of corrupted table
|
||||
Status s;
|
||||
std::string tmp1, tmp2;
|
||||
std::string tmp1;
|
||||
std::string tmp2;
|
||||
bool failed = false;
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2));
|
||||
@@ -792,7 +794,8 @@ TEST_F(CorruptionTest, UnrelatedKeys) {
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
ASSERT_NOK(db_->VerifyChecksum());
|
||||
|
||||
std::string tmp1, tmp2;
|
||||
std::string tmp1;
|
||||
std::string tmp2;
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2)));
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
|
||||
@@ -940,7 +943,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
db_.reset();
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
std::string start, end;
|
||||
std::string start;
|
||||
std::string end;
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(3, &start), Key(7, &end)));
|
||||
@@ -976,7 +980,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(10, 0, 0);
|
||||
std::string start, end;
|
||||
std::string start;
|
||||
std::string end;
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(5, &start), Key(15, &end)));
|
||||
auto snap = db_->GetSnapshot();
|
||||
@@ -1012,7 +1017,8 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
std::string start, end;
|
||||
std::string start;
|
||||
std::string end;
|
||||
Build(10);
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(3, &start), Key(7, &end)));
|
||||
|
||||
+181
-12
@@ -13,6 +13,9 @@
|
||||
#include "db/log_writer.h"
|
||||
#include "db/manifest_ops.h"
|
||||
#include "db/version_edit.h"
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
#include "env/io_posix.h"
|
||||
#endif
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -86,11 +89,9 @@ class MyFlushBlockPolicyFactory : public FlushBlockPolicyFactory {
|
||||
explicit MyFlushBlockPolicyFactory(const int num_keys_in_block)
|
||||
: num_keys_in_block_(num_keys_in_block) {}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "MyFlushBlockPolicyFactory";
|
||||
}
|
||||
const char* Name() const override { return "MyFlushBlockPolicyFactory"; }
|
||||
|
||||
virtual FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
const BlockBasedTableOptions& /*table_options*/,
|
||||
const BlockBuilder& data_block_builder) const override {
|
||||
return new MyFlushBlockPolicy(num_keys_in_block_, data_block_builder);
|
||||
@@ -1234,7 +1235,10 @@ TEST_F(DBBasicTest, EnableDirectIOWithZeroBuf) {
|
||||
|
||||
TEST_F(DBBasicTest, UniqueSession) {
|
||||
Options options = CurrentOptions();
|
||||
std::string sid1, sid2, sid3, sid4;
|
||||
std::string sid1;
|
||||
std::string sid2;
|
||||
std::string sid3;
|
||||
std::string sid4;
|
||||
|
||||
ASSERT_OK(db_->GetDbSessionId(sid1));
|
||||
Reopen(options);
|
||||
@@ -1685,7 +1689,8 @@ TEST_P(DBBasicGetWithParam, TimedPutBasic) {
|
||||
if (option_config_ != kRowCache) {
|
||||
std::string range_del_begin = "b";
|
||||
std::string range_del_end = "baz";
|
||||
Slice begin_rdel = range_del_begin, end_rdel = range_del_end;
|
||||
Slice begin_rdel = range_del_begin;
|
||||
Slice end_rdel = range_del_end;
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), handles_[1], begin_rdel, end_rdel));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "bar", nullptr, use_coroutine_));
|
||||
@@ -2308,7 +2313,8 @@ TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
for (bool with_manifest : {false, true}) {
|
||||
for (bool write_file : {false, true}) {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
std::string id1, tmp;
|
||||
std::string id1;
|
||||
std::string tmp;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
ASSERT_GE(id1.size(), kMinIdSize);
|
||||
|
||||
@@ -2410,7 +2416,8 @@ TEST_F(DBBasicTest, LockFileRecovery) {
|
||||
for (bool ber : {false, true}) {
|
||||
options.best_efforts_recovery = ber;
|
||||
DestroyAndReopen(options);
|
||||
std::string id1, id2;
|
||||
std::string id1;
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
Close();
|
||||
|
||||
@@ -4973,6 +4980,10 @@ TEST_P(DBMultiGetAsyncIOTest, GetFromL1AndL2WithRangeDelInL1) {
|
||||
TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) {
|
||||
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
|
||||
|
||||
const bool previous_enable_io_uring = enable_io_uring;
|
||||
Defer restore_enable_io_uring([previous_enable_io_uring]() {
|
||||
enable_io_uring = previous_enable_io_uring;
|
||||
});
|
||||
enable_io_uring = false;
|
||||
ReopenDB();
|
||||
|
||||
@@ -4999,6 +5010,164 @@ TEST_P(DBMultiGetAsyncIOTest, GetNoIOUring) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBMultiGetAsyncIOTest, AsyncIOSupportCheckedOnReadThread) {
|
||||
#ifndef ROCKSDB_IOURING_PRESENT
|
||||
ROCKSDB_GTEST_SKIP("This test requires io_uring support");
|
||||
#else
|
||||
if (use_coroutine_) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"This test targets synchronous MultiGet with async IO");
|
||||
return;
|
||||
}
|
||||
if (!CheckFSFeatureSupport(db_->GetFileSystem(), FSSupportedOps::kAsyncIO)) {
|
||||
ROCKSDB_GTEST_SKIP("io_uring is unavailable on the test thread");
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
std::atomic<uint64_t> read_thread_id{0};
|
||||
int read_async_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixFileSystem::SupportedOps:CreateIOUring", [&](void* arg) {
|
||||
if (read_thread_id.load(std::memory_order_acquire) !=
|
||||
Env::Default()->GetThreadID()) {
|
||||
return;
|
||||
}
|
||||
auto** iu = static_cast<struct io_uring**>(arg);
|
||||
if (*iu != nullptr) {
|
||||
DeleteIOUring(*iu);
|
||||
*iu = nullptr;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RandomAccessFileReader::ReadAsync:InjectStatus",
|
||||
[&](void*) { ++read_async_count; });
|
||||
Defer cleanup([]() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<std::string> values;
|
||||
std::thread read_thread([&]() {
|
||||
read_thread_id.store(Env::Default()->GetThreadID(),
|
||||
std::memory_order_release);
|
||||
values =
|
||||
MultiGet({Key(33), Key(54), Key(102)}, /*snapshot=*/nullptr,
|
||||
/*async=*/true, optimize_multiget_for_io_, use_coroutine_);
|
||||
});
|
||||
read_thread.join();
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
|
||||
ASSERT_EQ(values[1], "val_l1_" + std::to_string(54));
|
||||
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
|
||||
ASSERT_EQ(read_async_count, 0);
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
// SupportedOps is advisory, so ReadAsync can still report NotSupported when a
|
||||
// request is submitted. MultiGet must then read synchronously rather than
|
||||
// returning that error to the caller.
|
||||
TEST_P(DBMultiGetAsyncIOTest, ReadAsyncNotSupportedFallsBackToSync) {
|
||||
const bool previous_enable_io_uring = enable_io_uring;
|
||||
Defer cleanup([previous_enable_io_uring]() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
enable_io_uring = previous_enable_io_uring;
|
||||
});
|
||||
|
||||
enable_io_uring = true;
|
||||
PrepareDBForTest();
|
||||
|
||||
int sync_fallback_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RandomAccessFileReader::ReadAsync:InjectStatus", [](void* arg) {
|
||||
*static_cast<IOStatus*>(arg) =
|
||||
IOStatus::NotSupported("ReadAsync: failed to init io_uring");
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"AsyncFileReader::MultiReadAsyncImpl:SyncFallback",
|
||||
[&](void*) { ++sync_fallback_count; });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"AsyncFileReader::Wait:InjectPollStatus", [](void* arg) {
|
||||
*static_cast<IOStatus*>(arg) = IOStatus::IOError("injected Poll error");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
|
||||
std::vector<std::string> values =
|
||||
MultiGet(key_strs, /*snapshot=*/nullptr, /*async=*/true,
|
||||
optimize_multiget_for_io_, use_coroutine_);
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
|
||||
ASSERT_EQ(values[1], "val_l1_" + std::to_string(54));
|
||||
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
// Both synchronous async IO paths use AsyncFileReader when reads do not go
|
||||
// through the coroutine read executor.
|
||||
if (!UseCoroutineRead()) {
|
||||
ASSERT_GT(sync_fallback_count, 0);
|
||||
}
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_IOURING_PRESENT
|
||||
TEST_P(DBMultiGetAsyncIOTest, MixedReadAsyncFallbackPropagatesPollError) {
|
||||
if (UseCoroutineRead()) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"This test targets the synchronous MultiGet AsyncFileReader path");
|
||||
return;
|
||||
}
|
||||
if (!CheckFSFeatureSupport(db_->GetFileSystem(), FSSupportedOps::kAsyncIO)) {
|
||||
ROCKSDB_GTEST_SKIP("io_uring is unavailable on the test thread");
|
||||
return;
|
||||
}
|
||||
|
||||
PrepareDBForTest();
|
||||
|
||||
std::atomic<int> read_async_count{0};
|
||||
std::atomic<int> sync_fallback_count{0};
|
||||
Defer cleanup([]() {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RandomAccessFileReader::ReadAsync:InjectStatus", [&](void* arg) {
|
||||
if (read_async_count.fetch_add(1) % 2 == 0) {
|
||||
*static_cast<IOStatus*>(arg) =
|
||||
IOStatus::NotSupported("injected ReadAsync fallback");
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"AsyncFileReader::MultiReadAsyncImpl:SyncFallback",
|
||||
[&](void*) { ++sync_fallback_count; });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"AsyncFileReader::Wait:InjectPollStatus", [](void* arg) {
|
||||
*static_cast<IOStatus*>(arg) = IOStatus::IOError("injected Poll error");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<std::string> key_strs{Key(33), Key(54), Key(102)};
|
||||
std::vector<PinnableSlice> values;
|
||||
std::vector<Status> statuses;
|
||||
MultiGet(key_strs, /*snapshot=*/nullptr, /*async=*/true,
|
||||
optimize_multiget_for_io_, use_coroutine_, values, statuses);
|
||||
|
||||
ASSERT_EQ(statuses.size(), 3);
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], "val_l1_" + std::to_string(33));
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], "val_l1_" + std::to_string(102));
|
||||
ASSERT_EQ(read_async_count, 3);
|
||||
ASSERT_EQ(sync_fallback_count, 2);
|
||||
}
|
||||
#endif // ROCKSDB_IOURING_PRESENT
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMultiGetAsyncIOTest, DBMultiGetAsyncIOTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
#endif // USE_COROUTINES
|
||||
@@ -6376,9 +6545,9 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
ASSERT_TRUE(CheckUncompressableValue(key_uncmp[i], values[i].ToString()));
|
||||
}
|
||||
if (compression_enabled() && !has_compressed_cache()) {
|
||||
expected_reads += (read_from_cache ? 3 : 3);
|
||||
expected_reads += 3;
|
||||
} else {
|
||||
expected_reads += (read_from_cache ? 4 : 4);
|
||||
expected_reads += 4;
|
||||
}
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads);
|
||||
|
||||
@@ -6445,7 +6614,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
public:
|
||||
FakeDirectIOSequentialFile(std::unique_ptr<SequentialFile>&& file)
|
||||
: SequentialFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIOSequentialFile() {}
|
||||
~FakeDirectIOSequentialFile() override {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
@@ -6458,7 +6627,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
public:
|
||||
FakeDirectIORandomAccessFile(std::unique_ptr<RandomAccessFile>&& file)
|
||||
: RandomAccessFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIORandomAccessFile() {}
|
||||
~FakeDirectIORandomAccessFile() override {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
@@ -1921,7 +1921,10 @@ TEST_F(CacheKeyTest, DBImplSessionIdStructure) {
|
||||
// counter."
|
||||
std::string session_id1 = DBImpl::GenerateDbSessionId(/*env*/ nullptr);
|
||||
std::string session_id2 = DBImpl::GenerateDbSessionId(/*env*/ nullptr);
|
||||
uint64_t upper1, upper2, lower1, lower2;
|
||||
uint64_t upper1;
|
||||
uint64_t upper2;
|
||||
uint64_t lower1;
|
||||
uint64_t lower2;
|
||||
ASSERT_OK(DecodeSessionId(session_id1, &upper1, &lower1));
|
||||
ASSERT_OK(DecodeSessionId(session_id2, &upper2, &lower2));
|
||||
// Because generated in same process
|
||||
@@ -1981,7 +1984,8 @@ struct CacheKeyDecoder {
|
||||
}
|
||||
|
||||
void Decode(const CacheKey& key) {
|
||||
uint64_t file_num_etc64, offset_etc64;
|
||||
uint64_t file_num_etc64;
|
||||
uint64_t offset_etc64;
|
||||
DeconstructNonemptyCacheKey(key, &file_num_etc64, &offset_etc64);
|
||||
|
||||
// First decode session counter
|
||||
|
||||
@@ -917,14 +917,15 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::Values(
|
||||
std::make_tuple(kAutoBloom,
|
||||
FilterPartitioning::kCoupledPartitionedFilter,
|
||||
kLatestBbtFormatVersion),
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion),
|
||||
std::make_tuple(kAutoBloom,
|
||||
FilterPartitioning::kDecoupledPartitionedFilter,
|
||||
kLatestBbtFormatVersion),
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion),
|
||||
std::make_tuple(kAutoBloom, FilterPartitioning::kUnpartitionedFilter,
|
||||
kLatestBbtFormatVersion),
|
||||
std::make_tuple(kAutoRibbon, FilterPartitioning::kUnpartitionedFilter,
|
||||
kLatestBbtFormatVersion)));
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion),
|
||||
std::make_tuple(
|
||||
kAutoRibbon, FilterPartitioning::kUnpartitionedFilter,
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion)));
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
|
||||
+4
-2
@@ -98,7 +98,8 @@ TEST_F(DBClipTest, TestClipRange) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("10", FilesPerLevel(0));
|
||||
auto begin_key = Key(251), end_key = Key(751);
|
||||
auto begin_key = Key(251);
|
||||
auto end_key = Key(751);
|
||||
ASSERT_OK(
|
||||
db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key, end_key));
|
||||
|
||||
@@ -158,7 +159,8 @@ TEST_F(DBClipTest, TestClipRange) {
|
||||
}
|
||||
ASSERT_EQ("5,5,3", FilesPerLevel(0));
|
||||
|
||||
auto begin_key_2 = Key(222), end_key_2 = Key(888);
|
||||
auto begin_key_2 = Key(222);
|
||||
auto end_key_2 = Key(888);
|
||||
|
||||
ASSERT_OK(db_->ClipColumnFamily(db_->DefaultColumnFamily(), begin_key_2,
|
||||
end_key_2));
|
||||
|
||||
@@ -601,7 +601,10 @@ TEST_P(DBTestCompactionFilterWithCompactParam,
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
std::string one, two, three, four;
|
||||
std::string one;
|
||||
std::string two;
|
||||
std::string three;
|
||||
std::string four;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
PutFixed64(&three, 3);
|
||||
|
||||
+309
-21
@@ -7,6 +7,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
@@ -26,6 +29,8 @@
|
||||
#include "rocksdb/file_checksum.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "table/block_based/block_based_table_factory.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testutil.h"
|
||||
@@ -173,6 +178,264 @@ class DBCompactionTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
struct FileCuttingTableFactoryState {
|
||||
std::atomic<int> cut_requests{0};
|
||||
int max_cut_requests = 1;
|
||||
bool allow_empty_builder_cut = false;
|
||||
};
|
||||
|
||||
class FileCuttingTableBuilder : public TableBuilder {
|
||||
public:
|
||||
FileCuttingTableBuilder(std::unique_ptr<TableBuilder> base_builder,
|
||||
std::string cut_before_user_key,
|
||||
std::shared_ptr<FileCuttingTableFactoryState> state)
|
||||
: base_builder_(std::move(base_builder)),
|
||||
cut_before_user_key_(std::move(cut_before_user_key)),
|
||||
state_(std::move(state)) {}
|
||||
|
||||
void Add(const Slice& key, const Slice& value) override {
|
||||
base_builder_->Add(key, value);
|
||||
}
|
||||
|
||||
TableBuilderAddResult TryAdd(const Slice& key, const Slice& value,
|
||||
TableBuilderAddContext* context) override {
|
||||
const Slice user_key = ExtractUserKey(key);
|
||||
const bool cut_request_limit_not_reached =
|
||||
state_->cut_requests.load() < state_->max_cut_requests;
|
||||
const bool should_cut =
|
||||
cut_request_limit_not_reached &&
|
||||
(state_->allow_empty_builder_cut || !base_builder_->IsEmpty()) &&
|
||||
user_key.compare(Slice(cut_before_user_key_)) == 0;
|
||||
if (should_cut) {
|
||||
state_->cut_requests++;
|
||||
context->new_table_reason = TableBuilderNewTableReason::kBuilderPolicy;
|
||||
return TableBuilderAddResult::kRequiresNewTable;
|
||||
}
|
||||
return base_builder_->TryAdd(key, value, context);
|
||||
}
|
||||
|
||||
Status status() const override { return base_builder_->status(); }
|
||||
|
||||
IOStatus io_status() const override { return base_builder_->io_status(); }
|
||||
|
||||
Status Finish() override { return base_builder_->Finish(); }
|
||||
|
||||
void Abandon() override { base_builder_->Abandon(); }
|
||||
|
||||
uint64_t NumEntries() const override { return base_builder_->NumEntries(); }
|
||||
|
||||
bool IsEmpty() const override { return base_builder_->IsEmpty(); }
|
||||
|
||||
uint64_t PreCompressionSize() const override {
|
||||
return base_builder_->PreCompressionSize();
|
||||
}
|
||||
|
||||
uint64_t FileSize() const override { return base_builder_->FileSize(); }
|
||||
|
||||
uint64_t EstimatedFileSize() const override {
|
||||
return base_builder_->EstimatedFileSize();
|
||||
}
|
||||
|
||||
uint64_t EstimatedTailSize() const override {
|
||||
return base_builder_->EstimatedTailSize();
|
||||
}
|
||||
|
||||
uint64_t GetTailSize() const override { return base_builder_->GetTailSize(); }
|
||||
|
||||
bool NeedCompact() const override { return base_builder_->NeedCompact(); }
|
||||
|
||||
TableProperties GetTableProperties() const override {
|
||||
return base_builder_->GetTableProperties();
|
||||
}
|
||||
|
||||
std::string GetFileChecksum() const override {
|
||||
return base_builder_->GetFileChecksum();
|
||||
}
|
||||
|
||||
const char* GetFileChecksumFuncName() const override {
|
||||
return base_builder_->GetFileChecksumFuncName();
|
||||
}
|
||||
|
||||
void SetSeqnoTimeTableProperties(const SeqnoToTimeMapping& relevant_mapping,
|
||||
uint64_t oldest_ancestor_time) override {
|
||||
base_builder_->SetSeqnoTimeTableProperties(relevant_mapping,
|
||||
oldest_ancestor_time);
|
||||
}
|
||||
|
||||
uint64_t GetWorkerCPUMicros() const override {
|
||||
return base_builder_->GetWorkerCPUMicros();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<TableBuilder> base_builder_;
|
||||
std::string cut_before_user_key_;
|
||||
std::shared_ptr<FileCuttingTableFactoryState> state_;
|
||||
};
|
||||
|
||||
class FileCuttingTableFactory : public TableFactory {
|
||||
public:
|
||||
FileCuttingTableFactory(std::string cut_before_user_key,
|
||||
std::shared_ptr<FileCuttingTableFactoryState> state)
|
||||
: cut_before_user_key_(std::move(cut_before_user_key)),
|
||||
state_(std::move(state)) {}
|
||||
|
||||
const char* Name() const override { return "FileCuttingTableFactory"; }
|
||||
|
||||
using TableFactory::NewTableReader;
|
||||
|
||||
Status NewTableReader(
|
||||
const ReadOptions& read_options,
|
||||
const TableReaderOptions& table_reader_options,
|
||||
std::unique_ptr<RandomAccessFileReader>&& file, uint64_t file_size,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
bool prefetch_index_and_filter_in_cache) const override {
|
||||
return base_factory_.NewTableReader(
|
||||
read_options, table_reader_options, std::move(file), file_size,
|
||||
table_reader, prefetch_index_and_filter_in_cache);
|
||||
}
|
||||
|
||||
TableBuilder* NewTableBuilder(
|
||||
const TableBuilderOptions& table_builder_options,
|
||||
WritableFileWriter* file) const override {
|
||||
return new FileCuttingTableBuilder(
|
||||
std::unique_ptr<TableBuilder>(
|
||||
base_factory_.NewTableBuilder(table_builder_options, file)),
|
||||
cut_before_user_key_, state_);
|
||||
}
|
||||
|
||||
std::unique_ptr<TableFactory> Clone() const override {
|
||||
return std::make_unique<FileCuttingTableFactory>(cut_before_user_key_,
|
||||
state_);
|
||||
}
|
||||
|
||||
bool IsDeleteRangeSupported() const override {
|
||||
return base_factory_.IsDeleteRangeSupported();
|
||||
}
|
||||
|
||||
std::string GetPrintableOptions() const override {
|
||||
return base_factory_.GetPrintableOptions();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string cut_before_user_key_;
|
||||
std::shared_ptr<FileCuttingTableFactoryState> state_;
|
||||
BlockBasedTableFactory base_factory_;
|
||||
};
|
||||
|
||||
TEST_F(DBCompactionTest, TableBuilderCanRequestNewTableDuringCompaction) {
|
||||
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = 10;
|
||||
options.target_file_size_base = 1 << 30;
|
||||
options.table_factory =
|
||||
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
|
||||
Reopen(options);
|
||||
|
||||
for (int key : {0, 2, 4, 6, 8}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int key : {1, 3, 5, 7, 9}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_EQ(1, factory_state->cut_requests.load());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(1));
|
||||
for (int key = 0; key < 10; ++key) {
|
||||
ASSERT_EQ("value" + std::to_string(key), Get(Key(key)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TableBuilderCutRequestForLevelZeroFailsCompaction) {
|
||||
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 1;
|
||||
options.table_factory =
|
||||
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
|
||||
Reopen(options);
|
||||
|
||||
for (int key : {0, 2, 4, 6, 8}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int key : {1, 3, 5, 7, 9}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
|
||||
ASSERT_NE(std::string::npos, status.ToString().find("level 0 output"))
|
||||
<< status.ToString();
|
||||
ASSERT_EQ(1, factory_state->cut_requests.load());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TableBuilderCutOnEmptyBuilderFailsCompaction) {
|
||||
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
|
||||
factory_state->allow_empty_builder_cut = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.allow_data_in_errors = false;
|
||||
options.level0_file_num_compaction_trigger = 10;
|
||||
options.target_file_size_base = 1 << 30;
|
||||
options.table_factory =
|
||||
std::make_shared<FileCuttingTableFactory>(Key(0), factory_state);
|
||||
Reopen(options);
|
||||
|
||||
for (int key : {0, 2, 4, 6, 8}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int key : {1, 3, 5, 7, 9}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
|
||||
ASSERT_NE(std::string::npos, status.ToString().find("key: hidden"))
|
||||
<< status.ToString();
|
||||
ASSERT_EQ(1, factory_state->cut_requests.load());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TableBuilderRepeatedCutRequestFailsCompaction) {
|
||||
auto factory_state = std::make_shared<FileCuttingTableFactoryState>();
|
||||
factory_state->max_cut_requests = 2;
|
||||
factory_state->allow_empty_builder_cut = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = 10;
|
||||
options.target_file_size_base = 1 << 30;
|
||||
options.table_factory =
|
||||
std::make_shared<FileCuttingTableFactory>(Key(5), factory_state);
|
||||
Reopen(options);
|
||||
|
||||
for (int key : {0, 2, 4, 6, 8}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int key : {1, 3, 5, 7, 9}) {
|
||||
ASSERT_OK(Put(Key(key), "value" + std::to_string(key)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Status status = db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(status.IsCorruption()) << status.ToString();
|
||||
ASSERT_EQ(2, factory_state->cut_requests.load());
|
||||
}
|
||||
|
||||
class DBCompactionTestWithParam
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
|
||||
@@ -2458,9 +2721,12 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
|
||||
|
||||
// Delete files in range [0, 299] (inclusive)
|
||||
{
|
||||
auto begin_str1 = Key(0), end_str1 = Key(100);
|
||||
auto begin_str2 = Key(100), end_str2 = Key(200);
|
||||
auto begin_str3 = Key(200), end_str3 = Key(299);
|
||||
auto begin_str1 = Key(0);
|
||||
auto end_str1 = Key(100);
|
||||
auto begin_str2 = Key(100);
|
||||
auto end_str2 = Key(200);
|
||||
auto begin_str3 = Key(200);
|
||||
auto end_str3 = Key(299);
|
||||
std::vector<RangeOpt> ranges;
|
||||
ranges.emplace_back(begin_str1, end_str1);
|
||||
ranges.emplace_back(begin_str2, end_str2);
|
||||
@@ -2482,12 +2748,18 @@ TEST_P(DBDeleteFileRangeTest, DeleteFilesInRanges) {
|
||||
|
||||
// Delete files in range [600, 999) (exclusive)
|
||||
{
|
||||
auto begin_str1 = Key(600), end_str1 = Key(800);
|
||||
auto begin_str2 = Key(700), end_str2 = Key(900);
|
||||
auto begin_str3 = Key(800), end_str3 = Key(999);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
auto begin_str1 = Key(600);
|
||||
auto end_str1 = Key(800);
|
||||
auto begin_str2 = Key(700);
|
||||
auto end_str2 = Key(900);
|
||||
auto begin_str3 = Key(800);
|
||||
auto end_str3 = Key(999);
|
||||
Slice begin1(begin_str1);
|
||||
Slice end1(end_str1);
|
||||
Slice begin2(begin_str2);
|
||||
Slice end2(end_str2);
|
||||
Slice begin3(begin_str3);
|
||||
Slice end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.emplace_back(&begin1, &end1);
|
||||
ranges.emplace_back(&begin2, &end2);
|
||||
@@ -2574,8 +2846,10 @@ TEST_P(DBDeleteFileRangeTest, DeleteFileRangeFileEndpointsOverlapBug) {
|
||||
|
||||
// Verify `DeleteFilesInRange` can't drop only file 0 which would cause
|
||||
// "1 -> vals[0]" to reappear.
|
||||
std::string begin_str = Key(0), end_str = Key(1);
|
||||
Slice begin = begin_str, end = end_str;
|
||||
std::string begin_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice begin = begin_str;
|
||||
Slice end = end_str;
|
||||
ASSERT_OK(
|
||||
DeleteFilesInRange(db_.get(), db_->DefaultColumnFamily(), &begin, &end));
|
||||
ASSERT_EQ(vals[1], GetValue(Key(1)));
|
||||
@@ -2883,7 +3157,8 @@ TEST_P(DBCompactionTestWithParam, LevelCompactionCFPathUse) {
|
||||
|
||||
std::vector<Options> option_vector;
|
||||
option_vector.emplace_back(options);
|
||||
ColumnFamilyOptions cf_opt1(options), cf_opt2(options);
|
||||
ColumnFamilyOptions cf_opt1(options);
|
||||
ColumnFamilyOptions cf_opt2(options);
|
||||
// Configure CF1 specific paths.
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 500 * 1024);
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 4 * 1024 * 1024);
|
||||
@@ -3659,7 +3934,10 @@ TEST_P(DBCompactionTestWithParam, CompressLevelCompaction) {
|
||||
// move to level 3 will not be allowed
|
||||
options.compression_per_level = {kNoCompression, kNoCompression,
|
||||
kZlibCompression};
|
||||
int matches = 0, didnt_match = 0, trivial_move = 0, non_trivial = 0;
|
||||
int matches = 0;
|
||||
int didnt_match = 0;
|
||||
int trivial_move = 0;
|
||||
int non_trivial = 0;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"Compaction::InputCompressionMatchesOutput:Matches",
|
||||
@@ -4735,7 +5013,8 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
std::vector<LiveFileMetaData> pre_release_metadata, post_release_metadata;
|
||||
std::vector<LiveFileMetaData> pre_release_metadata;
|
||||
std::vector<LiveFileMetaData> post_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&pre_release_metadata);
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
@@ -4882,7 +5161,8 @@ TEST_F(DBCompactionTest, NoCompactBottomLevelFilesWithDeletions) {
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr));
|
||||
ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
std::vector<LiveFileMetaData> pre_release_metadata, post_release_metadata;
|
||||
std::vector<LiveFileMetaData> pre_release_metadata;
|
||||
std::vector<LiveFileMetaData> post_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&pre_release_metadata);
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
@@ -6432,7 +6712,8 @@ TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) {
|
||||
CompactRangeOptions compact_range_opts;
|
||||
ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr));
|
||||
|
||||
uint64_t get_prop_tmp, num_memtable_entries = 0;
|
||||
uint64_t get_prop_tmp;
|
||||
uint64_t num_memtable_entries = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables,
|
||||
&get_prop_tmp));
|
||||
num_memtable_entries += get_prop_tmp;
|
||||
@@ -8201,7 +8482,8 @@ TEST_F(DBCompactionTest, ManualCompactionBottomLevelOptimized) {
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ManualCompactionMax) {
|
||||
uint64_t l1_avg_size = 0, l2_avg_size = 0;
|
||||
uint64_t l1_avg_size = 0;
|
||||
uint64_t l2_avg_size = 0;
|
||||
auto generate_sst_func = [&]() {
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
@@ -9389,7 +9671,8 @@ TEST_P(DBCompactionTestL0FilesMisorderCorruptionWithParam,
|
||||
// `start` and `end` is carefully chosen so that compact range:
|
||||
// (1) doesn't overlap with memtable therefore the memtable won't be flushed
|
||||
// (2) should target at compacting s0 with s1 and s2
|
||||
Slice start("k3"), end("k5");
|
||||
Slice start("k3");
|
||||
Slice end("k5");
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
// After compaction, we have LSM tree:
|
||||
//
|
||||
@@ -10904,8 +11187,12 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
|
||||
env_->SetMockSleep();
|
||||
|
||||
// Track all temperature file creations
|
||||
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
|
||||
total_ice = 0, total_unknown = 0;
|
||||
int total_hot = 0;
|
||||
int total_warm = 0;
|
||||
int total_cool = 0;
|
||||
int total_cold = 0;
|
||||
int total_ice = 0;
|
||||
int total_unknown = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
Temperature temperature = *(static_cast<Temperature*>(arg));
|
||||
@@ -11741,7 +12028,8 @@ TEST_F(DBCompactionTest, DrainUnnecessaryLevelsAfterMultiplierChanged) {
|
||||
// necessary, unnecessary levels should to be drained.
|
||||
const int kBaseLevelBytes = 256 << 10; // 256KB
|
||||
const int kFileBytes = 64 << 10; // 64KB
|
||||
const int kInitMultiplier = 2, kChangedMultiplier = 10;
|
||||
const int kInitMultiplier = 2;
|
||||
const int kChangedMultiplier = 10;
|
||||
const int kNumFiles = 32;
|
||||
const int kNumLevels = 5;
|
||||
const int kValueBytes = 1 << 10; // 1KB
|
||||
|
||||
+40
-2
@@ -1357,7 +1357,8 @@ TEST_F(DBTest2, DuplicateSnapshot) {
|
||||
options = CurrentOptions(options);
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
DBImpl* dbi = dbfull();
|
||||
SequenceNumber oldest_ww_snap, first_ww_snap;
|
||||
SequenceNumber oldest_ww_snap;
|
||||
SequenceNumber first_ww_snap;
|
||||
|
||||
ASSERT_OK(Put("k", "v")); // inc seq
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
@@ -2380,10 +2381,12 @@ TEST_F(DBTest2, PausingManualCompaction1) {
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<std::string> files_before_compact, files_after_compact;
|
||||
std::vector<std::string> files_before_compact;
|
||||
std::vector<std::string> files_after_compact;
|
||||
// Remember file name before compaction is triggered
|
||||
std::vector<LiveFileMetaData> files_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&files_meta);
|
||||
files_before_compact.reserve(files_meta.size());
|
||||
for (const auto& file : files_meta) {
|
||||
files_before_compact.push_back(file.name);
|
||||
}
|
||||
@@ -2399,6 +2402,7 @@ TEST_F(DBTest2, PausingManualCompaction1) {
|
||||
// Get file names after compaction is stopped
|
||||
files_meta.clear();
|
||||
dbfull()->GetLiveFilesMetaData(&files_meta);
|
||||
files_after_compact.reserve(files_meta.size());
|
||||
for (const auto& file : files_meta) {
|
||||
files_after_compact.push_back(file.name);
|
||||
}
|
||||
@@ -3513,6 +3517,40 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LiveFilesStorageInfoWithConcurrentSetOptions) {
|
||||
// SetOptions() writes a new OPTIONS file and deletes all but the newest
|
||||
// two. Without file deletions disabled by the caller, the OPTIONS file
|
||||
// chosen by GetLiveFilesStorageInfo must still survive until its size is
|
||||
// read.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::GetLiveFilesStorageInfo:BeforeOptionsFileSize",
|
||||
[&](void* /*arg*/) {
|
||||
ASSERT_OK(db_->SetOptions({{"disable_auto_compactions", "true"}}));
|
||||
ASSERT_OK(db_->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<LiveFileStorageInfo> infos;
|
||||
ASSERT_OK(
|
||||
db_->GetLiveFilesStorageInfo(LiveFilesStorageInfoOptions(), &infos));
|
||||
ASSERT_TRUE(dbfull()->IsFileDeletionsEnabled());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LiveFilesStorageInfoReadOnlyKeepsDeletionsEnabled) {
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(ReadOnlyReopen(CurrentOptions()));
|
||||
|
||||
LiveFilesStorageInfoOptions opts;
|
||||
opts.wal_size_for_flush = std::numeric_limits<uint64_t>::max();
|
||||
std::vector<LiveFileStorageInfo> infos;
|
||||
ASSERT_OK(db_->GetLiveFilesStorageInfo(opts, &infos));
|
||||
ASSERT_TRUE(dbfull()->IsFileDeletionsEnabled());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TestNumPread) {
|
||||
Options options = CurrentOptions();
|
||||
bool prefetch_supported =
|
||||
|
||||
+2
-1
@@ -61,7 +61,8 @@ TEST_F(DBEtc3Test, AutoTuneManifestSize) {
|
||||
// auto-tuning phases continue to test the unrelaxed background threshold
|
||||
// even though CF manipulation itself is foreground. Phase-specific foreground
|
||||
// checks disable that piggy-backed background write.
|
||||
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
|
||||
uint64_t prev_manifest_num = 0;
|
||||
uint64_t cur_manifest_num = 0;
|
||||
std::deque<ColumnFamilyHandle*> handles;
|
||||
int counter = 5;
|
||||
auto UpdateManifestNumsFrom = [&](uint64_t before_manifest_num) {
|
||||
|
||||
+82
-7
@@ -17,6 +17,7 @@
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -198,7 +199,8 @@ Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) {
|
||||
|
||||
Status DBImpl::AppendColumnFamilyDropsToManifest(
|
||||
const std::string& manifest_path, uint64_t manifest_size,
|
||||
const std::vector<uint32_t>& cf_ids) {
|
||||
const std::vector<uint32_t>& cf_ids,
|
||||
uint64_t committed_options_file_number) {
|
||||
const WriteOptions write_options;
|
||||
// Snapshot manifest_preallocation_size_ under the DB mutex per VersionSet's
|
||||
// contract (the field is mutated by SetDBOptions under this mutex).
|
||||
@@ -208,21 +210,44 @@ Status DBImpl::AppendColumnFamilyDropsToManifest(
|
||||
preallocation_size = versions_->manifest_preallocation_size_;
|
||||
}
|
||||
return versions_->AppendColumnFamilyDropsToManifest(
|
||||
manifest_path, manifest_size, cf_ids, write_options, preallocation_size);
|
||||
manifest_path, manifest_size, cf_ids, write_options, preallocation_size,
|
||||
committed_options_file_number);
|
||||
}
|
||||
|
||||
Status DBImpl::CreateOptionsFileForSubsetCheckpoint(
|
||||
const std::string& checkpoint_dir,
|
||||
const SubsetCheckpointOptionsSnapshot& options_snapshot) {
|
||||
if (options_snapshot.options_file_number == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// The snapshot's CF options are shallow copies that share live pointer
|
||||
// sub-options (e.g. RibbonFilterPolicy, whose bloom_before_level is mutated
|
||||
// in place by SetOptions). Hold options_mutex_, as WriteOptionsFile does, so
|
||||
// SetOptions cannot change them between writing the file and verifying it.
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
|
||||
const std::string options_path =
|
||||
OptionsFileName(checkpoint_dir, options_snapshot.options_file_number);
|
||||
return PersistRocksDBOptions(
|
||||
WriteOptions(), options_snapshot.db_options, options_snapshot.cf_names,
|
||||
options_snapshot.cf_options, options_path, fs_.get());
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFilesStorageInfo(
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
std::vector<LiveFileStorageInfo>* files) {
|
||||
return GetLiveFilesStorageInfoImpl(opts, /*include_cf_ids=*/{}, files,
|
||||
/*excluded_cf_ids=*/nullptr);
|
||||
/*excluded_cf_ids=*/nullptr,
|
||||
/*options_snapshot=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFilesStorageInfoForSubsetCheckpoint(
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
const std::vector<uint32_t>& include_cf_ids,
|
||||
std::vector<LiveFileStorageInfo>* files,
|
||||
std::vector<uint32_t>* excluded_cf_ids) {
|
||||
std::vector<uint32_t>* excluded_cf_ids,
|
||||
SubsetCheckpointOptionsSnapshot* options_snapshot) {
|
||||
// Internal contract: caller must coalesce ids and always include the default
|
||||
// CF (it cannot be dropped, so a subset checkpoint without it would produce
|
||||
// an unopenable DB). CheckpointImpl enforces this before calling here.
|
||||
@@ -230,15 +255,17 @@ Status DBImpl::GetLiveFilesStorageInfoForSubsetCheckpoint(
|
||||
assert(std::find(include_cf_ids.begin(), include_cf_ids.end(),
|
||||
default_cf_handle_->GetID()) != include_cf_ids.end());
|
||||
assert(excluded_cf_ids != nullptr);
|
||||
assert(options_snapshot != nullptr);
|
||||
return GetLiveFilesStorageInfoImpl(opts, include_cf_ids, files,
|
||||
excluded_cf_ids);
|
||||
excluded_cf_ids, options_snapshot);
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFilesStorageInfoImpl(
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
const std::vector<uint32_t>& include_cf_ids,
|
||||
std::vector<LiveFileStorageInfo>* files,
|
||||
std::vector<uint32_t>* excluded_cf_ids) {
|
||||
std::vector<uint32_t>* excluded_cf_ids,
|
||||
SubsetCheckpointOptionsSnapshot* options_snapshot) {
|
||||
// To avoid returning partial results, only move results to files on success.
|
||||
assert(files);
|
||||
files->clear();
|
||||
@@ -246,6 +273,7 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
|
||||
|
||||
const bool cf_subset = !include_cf_ids.empty();
|
||||
assert(!cf_subset || excluded_cf_ids != nullptr);
|
||||
assert(!cf_subset || options_snapshot != nullptr);
|
||||
const std::unordered_set<uint32_t> include_cf_id_set(include_cf_ids.begin(),
|
||||
include_cf_ids.end());
|
||||
// Tracks which requested ids remain to be observed as live under the DB
|
||||
@@ -413,6 +441,29 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
|
||||
"Requested column family id was not live at checkpoint time");
|
||||
}
|
||||
|
||||
if (cf_subset) {
|
||||
assert(options_snapshot != nullptr);
|
||||
assert(excluded_cf_ids != nullptr);
|
||||
*options_snapshot = SubsetCheckpointOptionsSnapshot();
|
||||
if (!excluded_cf_ids->empty()) {
|
||||
options_snapshot->options_file_number = versions_->NewFileNumber();
|
||||
options_snapshot->commit_options_file_number =
|
||||
versions_->has_committed_options_file_number();
|
||||
options_snapshot->db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
ColumnFamilySet* column_family_set = versions_->GetColumnFamilySet();
|
||||
assert(column_family_set != nullptr);
|
||||
for (auto* cfd : *column_family_set) {
|
||||
assert(cfd != nullptr);
|
||||
if (!cfd->IsDropped() &&
|
||||
include_cf_id_set.find(cfd->GetID()) != include_cf_id_set.end()) {
|
||||
options_snapshot->cf_names.push_back(cfd->GetName());
|
||||
options_snapshot->cf_options.push_back(cfd->GetLatestCFOptions());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capture some final info before releasing mutex
|
||||
const uint64_t manifest_number = versions_->manifest_file_number();
|
||||
uint64_t manifest_size = versions_->manifest_file_size();
|
||||
@@ -424,13 +475,37 @@ Status DBImpl::GetLiveFilesStorageInfoImpl(
|
||||
}
|
||||
}
|
||||
const uint64_t options_number = versions_->options_file_number();
|
||||
const uint64_t options_size = versions_->options_file_size_;
|
||||
const uint64_t min_log_num = MinLogNumberToKeep();
|
||||
// Ensure consistency with manifest for track_and_verify_wals_in_manifest
|
||||
const uint64_t max_log_num = cur_wal_number_;
|
||||
|
||||
if (options_number != 0) {
|
||||
// A concurrent SetOptions() may otherwise delete this OPTIONS file before
|
||||
// its size is read below, even when the caller has not disabled deletions.
|
||||
DisableFileDeletionsWithLock().PermitUncheckedError();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
|
||||
uint64_t options_size = 0;
|
||||
if (options_number != 0) {
|
||||
// File deletions must be disabled by callers while they copy this snapshot.
|
||||
// The number belongs to the MANIFEST/CF snapshot captured above, while the
|
||||
// immutable file's size can be read without holding the DB mutex.
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFilesStorageInfo:BeforeOptionsFileSize");
|
||||
Status options_s = GetEnv()->GetFileSize(
|
||||
OptionsFileName(GetName(), options_number), &options_size);
|
||||
{
|
||||
// Not EnableFileDeletions(): read-only DB implementations override it to
|
||||
// return NotSupported without undoing DisableFileDeletionsWithLock().
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(disable_delete_obsolete_files_ > 0);
|
||||
--disable_delete_obsolete_files_;
|
||||
}
|
||||
if (!options_s.ok()) {
|
||||
return options_s;
|
||||
}
|
||||
}
|
||||
|
||||
std::string manifest_fname = DescriptorFileName(manifest_number);
|
||||
{ // MANIFEST
|
||||
results.emplace_back();
|
||||
|
||||
+52
-14
@@ -157,7 +157,8 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
env_->SetBackgroundThreads(0, Env::HIGH);
|
||||
|
||||
std::thread::id tid;
|
||||
int num_flushes = 0, num_compactions = 0;
|
||||
int num_flushes = 0;
|
||||
int num_compactions = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BGWorkFlush", [&](void* /*arg*/) {
|
||||
if (tid == std::thread::id()) {
|
||||
@@ -1017,7 +1018,9 @@ TEST_F(DBFlushTest, MemPurgeBasic) {
|
||||
std::string KEY7 = "IamKey7";
|
||||
std::string KEY8 = "IamKey8";
|
||||
std::string KEY9 = "IamKey9";
|
||||
std::string RNDKEY1, RNDKEY2, RNDKEY3;
|
||||
std::string RNDKEY1;
|
||||
std::string RNDKEY2;
|
||||
std::string RNDKEY3;
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
// Heavy overwrite workload,
|
||||
@@ -1026,8 +1029,18 @@ TEST_F(DBFlushTest, MemPurgeBasic) {
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_KEYS_LENGTH = 57;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9, p_rv1,
|
||||
p_rv2, p_rv3;
|
||||
std::string p_v1;
|
||||
std::string p_v2;
|
||||
std::string p_v3;
|
||||
std::string p_v4;
|
||||
std::string p_v5;
|
||||
std::string p_v6;
|
||||
std::string p_v7;
|
||||
std::string p_v8;
|
||||
std::string p_v9;
|
||||
std::string p_rv1;
|
||||
std::string p_rv2;
|
||||
std::string p_rv3;
|
||||
|
||||
// Insert a very first set of keys that will be
|
||||
// mempurged at least once.
|
||||
@@ -1394,7 +1407,14 @@ TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
|
||||
std::string key, value, p_v1, p_v2, p_v3, p_v3b, p_v4, p_v5;
|
||||
std::string key;
|
||||
std::string value;
|
||||
std::string p_v1;
|
||||
std::string p_v2;
|
||||
std::string p_v3;
|
||||
std::string p_v3b;
|
||||
std::string p_v4;
|
||||
std::string p_v5;
|
||||
int count = 0;
|
||||
const int EXPECTED_COUNT_FORLOOP = 3;
|
||||
const int EXPECTED_COUNT_END = 4;
|
||||
@@ -1597,7 +1617,15 @@ TEST_F(DBFlushTest, MemPurgeAndCompactionFilter) {
|
||||
Random rnd(53);
|
||||
const size_t NUM_REPEAT = 1000;
|
||||
const size_t RAND_VALUES_LENGTH = 10240;
|
||||
std::string p_v1, p_v2, p_v3, p_v4, p_v5, p_v6, p_v7, p_v8, p_v9;
|
||||
std::string p_v1;
|
||||
std::string p_v2;
|
||||
std::string p_v3;
|
||||
std::string p_v4;
|
||||
std::string p_v5;
|
||||
std::string p_v6;
|
||||
std::string p_v7;
|
||||
std::string p_v8;
|
||||
std::string p_v9;
|
||||
|
||||
p_v1 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
p_v2 = rnd.RandomString(RAND_VALUES_LENGTH);
|
||||
@@ -1691,7 +1719,8 @@ TEST_F(DBFlushTest, DISABLED_MemPurgeWALSupport) {
|
||||
keys.push_back("IamKey" + std::to_string(k));
|
||||
}
|
||||
|
||||
std::string RNDKEY, RNDVALUE;
|
||||
std::string RNDKEY;
|
||||
std::string RNDVALUE;
|
||||
const std::string NOT_FOUND = "NOT_FOUND";
|
||||
|
||||
// Heavy overwrite workload,
|
||||
@@ -1700,7 +1729,8 @@ TEST_F(DBFlushTest, DISABLED_MemPurgeWALSupport) {
|
||||
const size_t NUM_REPEAT = 100;
|
||||
const size_t RAND_KEY_LENGTH = 4096;
|
||||
const size_t RAND_VALUES_LENGTH = 1024;
|
||||
std::vector<std::string> values_default(KVSIZE), values_pikachu(KVSIZE);
|
||||
std::vector<std::string> values_default(KVSIZE);
|
||||
std::vector<std::string> values_pikachu(KVSIZE);
|
||||
|
||||
// Insert a very first set of keys that will be
|
||||
// mempurged at least once.
|
||||
@@ -1888,7 +1918,8 @@ TEST_F(DBFlushTest, MemPurgeCorrectLogNumberAndSSTFileCreation) {
|
||||
const uint64_t NUM_RAND_INSERTS = 500;
|
||||
const uint64_t RAND_VALUES_LENGTH = 10240;
|
||||
|
||||
std::string key, value;
|
||||
std::string key;
|
||||
std::string value;
|
||||
std::vector<std::string> values(9, "");
|
||||
|
||||
// Keys used to check that no SST file disappeared.
|
||||
@@ -3452,7 +3483,8 @@ TEST_P(DBAtomicFlushTest, BgThreadNoWaitAfterManifestError) {
|
||||
{"BgFlushThr2:WaitToCommit", "BgFlushThr1:BeforeWriteManifest"},
|
||||
});
|
||||
|
||||
std::thread::id bg_flush_thr1, bg_flush_thr2;
|
||||
std::thread::id bg_flush_thr1;
|
||||
std::thread::id bg_flush_thr2;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCallFlush:start", [&](void*) {
|
||||
if (bg_flush_thr1 == std::thread::id()) {
|
||||
@@ -4227,9 +4259,14 @@ TEST_F(DBFlushTest, SuperBlockAlignmentValueDeltaEscape) {
|
||||
|
||||
for (bool separate_kv : {false, true}) {
|
||||
SCOPED_TRACE("separate_kv=" + std::to_string(separate_kv));
|
||||
std::vector<std::string> get7, iter7, get8, iter8;
|
||||
int pad7 = 0, pad8 = 0;
|
||||
bool delta7 = false, delta8 = false;
|
||||
std::vector<std::string> get7;
|
||||
std::vector<std::string> iter7;
|
||||
std::vector<std::string> get8;
|
||||
std::vector<std::string> iter8;
|
||||
int pad7 = 0;
|
||||
int pad8 = 0;
|
||||
bool delta7 = false;
|
||||
bool delta8 = false;
|
||||
build_and_read(7, separate_kv, &get7, &iter7, &pad7, &delta7);
|
||||
build_and_read(/*format_version=*/8, separate_kv, &get8, &iter8, &pad8,
|
||||
&delta8);
|
||||
@@ -4396,7 +4433,8 @@ TEST_F(DBFlushTest, FlushAfterReadPathRangeTombstoneInsertion) {
|
||||
// already immutable. Without the fix, this succeeds and creates
|
||||
// an entry count mismatch.
|
||||
cfh->cfd()->mem()->AddLogicallyRedundantRangeTombstone(
|
||||
1 /* seq */, "b", "f", cfh->cfd()->GetIngestSstLock());
|
||||
1 /* seq */, "b", "f", 0 /* version_number */,
|
||||
cfh->cfd()->GetIngestSstLock());
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class DBFollowerTest : public DBTestBase {
|
||||
Reopen(CurrentOptions());
|
||||
}
|
||||
|
||||
~DBFollowerTest() {
|
||||
~DBFollowerTest() override {
|
||||
follower_.reset();
|
||||
EXPECT_EQ(DestroyDB(follower_name_, CurrentOptions()), Status::OK());
|
||||
Destroy(CurrentOptions());
|
||||
|
||||
@@ -18,6 +18,8 @@ class CompactedDBImpl : public DBImpl {
|
||||
// No copying allowed
|
||||
CompactedDBImpl(const CompactedDBImpl&) = delete;
|
||||
void operator=(const CompactedDBImpl&) = delete;
|
||||
CompactedDBImpl(CompactedDBImpl&&) = delete;
|
||||
void operator=(CompactedDBImpl&&) = delete;
|
||||
|
||||
~CompactedDBImpl() override;
|
||||
|
||||
|
||||
+487
-166
@@ -89,7 +89,6 @@
|
||||
#include "options/options_helper.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "rocksdb/lazy_wide_columns.h"
|
||||
#include "util/udt_util.h"
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#include "port/jemalloc_helper.h"
|
||||
#endif
|
||||
@@ -366,6 +365,20 @@ Status DBImpl::ResumeImpl(DBRecoverContext context,
|
||||
const ReadOptions read_options(io_activity);
|
||||
const WriteOptions write_options(io_activity);
|
||||
|
||||
if (lock_wal_owner_thread_id_counts_.count(env_->GetThreadID()) > 0) {
|
||||
return Status::Aborted(
|
||||
"Likely deadlock as the same thread called LockWAL()");
|
||||
}
|
||||
// Recovery replaces WALs, flushes memtables, and can reserve the sequence
|
||||
// numbers of an indeterminate WAL write. Defer it while any LockWAL() is held
|
||||
// so the frozen DB state (latest sequence number and current WAL) promised by
|
||||
// LockWAL() stays intact. UnlockWAL() and shutdown signal bg_cv_.
|
||||
while (!lock_wal_owner_thread_id_counts_.empty() && !shutdown_initiated_ &&
|
||||
!shutting_down_.load(std::memory_order_acquire)) {
|
||||
TEST_SYNC_POINT("DBImpl::ResumeImpl:WaitForUnlockWAL");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
assert(static_cast<size_t>(unscheduled_flushes_) <= flush_queue_.size());
|
||||
// Recovery rebuilds flush requests for every column family with pending
|
||||
// immutable data after scheduled workers exit, so every existing request is
|
||||
@@ -1054,6 +1067,8 @@ Status DBImpl::CloseHelper() {
|
||||
// continuing with the shutdown
|
||||
mutex_.Lock();
|
||||
shutdown_initiated_ = true;
|
||||
// Wake a recovery waiting in ResumeImpl() for UnlockWAL().
|
||||
bg_cv_.SignalAll();
|
||||
error_handler_.CancelErrorRecoveryForShutDown();
|
||||
while (!error_handler_.ReadyForShutdown()) {
|
||||
bg_cv_.Wait();
|
||||
@@ -1350,7 +1365,7 @@ DBImpl::~DBImpl() {
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
|
||||
closing_status_ = CloseImpl();
|
||||
closing_status_ = DBImpl::CloseImpl();
|
||||
closing_status_.PermitUncheckedError();
|
||||
}
|
||||
ThreadStatusUtil::SetThreadOperation(cur_op_type);
|
||||
@@ -1589,7 +1604,7 @@ size_t DBImpl::EstimateInMemoryStatsHistorySize() const {
|
||||
// non-empty map, stats_history_.begin() guaranteed to exist
|
||||
for (const auto& pairs : stats_history_.begin()->second) {
|
||||
size_per_slice +=
|
||||
pairs.first.capacity() + sizeof(pairs.first) + sizeof(pairs.second);
|
||||
pairs.first.capacity() + sizeof(std::string) + sizeof(pairs.second);
|
||||
}
|
||||
size_total = size_per_slice * stats_history_.size();
|
||||
return size_total;
|
||||
@@ -4498,8 +4513,14 @@ Status DBImpl::WrapUpCreateColumnFamilies(
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Attempt both follow-up actions even if one fails
|
||||
Status s = WriteOptionsFile(write_options, false /*db_mutex_already_held*/);
|
||||
// Tracked creates already published their OPTIONS snapshot in the same
|
||||
// MANIFEST edit. Legacy creates retain the historical post-create write.
|
||||
Status s;
|
||||
if (immutable_db_options_.track_options_file_number_in_manifest) {
|
||||
MaybeDeleteObsoleteOptionsFiles();
|
||||
} else {
|
||||
s = WriteOptionsFile(write_options, false /*db_mutex_already_held*/);
|
||||
}
|
||||
if (register_worker) {
|
||||
s.UpdateIfOk(RegisterRecordSeqnoTimeWorker());
|
||||
}
|
||||
@@ -4528,21 +4549,14 @@ Status DBImpl::CreateColumnFamilies(
|
||||
std::vector<ColumnFamilyHandle*>* handles) {
|
||||
assert(handles != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
handles->clear();
|
||||
size_t num_cf = column_family_names.size();
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
for (size_t i = 0; i < num_cf; i++) {
|
||||
ColumnFamilyHandle* handle;
|
||||
s = CreateColumnFamilyImpl(read_options, write_options, cf_options,
|
||||
column_family_names[i], &handle);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
handles->push_back(handle);
|
||||
success_once = true;
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.reserve(column_family_names.size());
|
||||
for (const auto& name : column_family_names) {
|
||||
column_families.emplace_back(name, cf_options);
|
||||
}
|
||||
if (success_once) {
|
||||
Status s = CreateColumnFamiliesImpl(read_options, write_options,
|
||||
column_families, handles);
|
||||
if (!handles->empty()) {
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(write_options, {&cf_options}));
|
||||
}
|
||||
return s;
|
||||
@@ -4554,25 +4568,14 @@ Status DBImpl::CreateColumnFamilies(
|
||||
std::vector<ColumnFamilyHandle*>* handles) {
|
||||
assert(handles != nullptr);
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
handles->clear();
|
||||
size_t num_cf = column_families.size();
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
Status s = CreateColumnFamiliesImpl(read_options, write_options,
|
||||
column_families, handles);
|
||||
std::vector<const ColumnFamilyOptions*> cf_opts;
|
||||
cf_opts.reserve(num_cf);
|
||||
for (size_t i = 0; i < num_cf; i++) {
|
||||
ColumnFamilyHandle* handle;
|
||||
s = CreateColumnFamilyImpl(read_options, write_options,
|
||||
column_families[i].options,
|
||||
column_families[i].name, &handle);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
handles->push_back(handle);
|
||||
success_once = true;
|
||||
cf_opts.reserve(handles->size());
|
||||
for (size_t i = 0; i < handles->size(); ++i) {
|
||||
cf_opts.push_back(&column_families[i].options);
|
||||
}
|
||||
if (success_once) {
|
||||
if (!handles->empty()) {
|
||||
s.UpdateIfOk(WrapUpCreateColumnFamilies(write_options, cf_opts));
|
||||
}
|
||||
return s;
|
||||
@@ -4583,37 +4586,126 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
Status s = CreateColumnFamiliesImpl(
|
||||
read_options, write_options,
|
||||
{ColumnFamilyDescriptor(column_family_name, cf_options)}, &handles);
|
||||
*handle = handles.empty() ? nullptr : handles.front();
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamiliesImpl(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles) {
|
||||
options_mutex_.AssertHeld();
|
||||
*handle = nullptr;
|
||||
assert(handles != nullptr);
|
||||
handles->clear();
|
||||
|
||||
// The empty string is reserved as a "no/unknown column family name" sentinel
|
||||
// in various APIs and serialization formats (e.g.
|
||||
// TablePropertiesCollectorFactory::Context::kUnknownColumnFamily, table
|
||||
// properties), so reject it to avoid ambiguity. Note: prior to this check,
|
||||
// CreateColumnFamily("") would silently "succeed" but the CF was not
|
||||
// persisted in the manifest, causing data loss across DB reopens.
|
||||
if (column_family_name.empty()) {
|
||||
return Status::InvalidArgument(
|
||||
"Column family name cannot be the empty string");
|
||||
}
|
||||
|
||||
Status s;
|
||||
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
s = ColumnFamilyData::ValidateOptions(db_options, cf_options);
|
||||
if (s.ok()) {
|
||||
for (auto& cf_path : cf_options.cf_paths) {
|
||||
s = env_->CreateDirIfMissing(cf_path.path);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
std::unordered_set<std::string> unavailable_names;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ColumnFamilySet* column_family_set = versions_->GetColumnFamilySet();
|
||||
assert(column_family_set != nullptr);
|
||||
for (auto* cfd : *column_family_set) {
|
||||
if (!cfd->IsDropped()) {
|
||||
unavailable_names.insert(cfd->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
std::vector<ColumnFamilyDescriptor> prepared_column_families;
|
||||
prepared_column_families.reserve(column_families.size());
|
||||
Status preparation_status;
|
||||
for (const auto& column_family : column_families) {
|
||||
const std::string& column_family_name = column_family.name;
|
||||
|
||||
// The empty string is reserved as a "no/unknown column family name"
|
||||
// sentinel in APIs and serialization formats.
|
||||
if (column_family_name.empty()) {
|
||||
preparation_status = Status::InvalidArgument(
|
||||
"Column family name cannot be the empty string");
|
||||
break;
|
||||
}
|
||||
preparation_status =
|
||||
ColumnFamilyData::ValidateOptions(db_options, column_family.options);
|
||||
if (preparation_status.ok()) {
|
||||
for (const auto& cf_path : column_family.options.cf_paths) {
|
||||
preparation_status = env_->CreateDirIfMissing(cf_path.path);
|
||||
if (!preparation_status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!preparation_status.ok()) {
|
||||
break;
|
||||
}
|
||||
if (unavailable_names.count(column_family_name) != 0) {
|
||||
preparation_status =
|
||||
Status::InvalidArgument("Column family already exists");
|
||||
break;
|
||||
}
|
||||
unavailable_names.insert(column_family_name);
|
||||
prepared_column_families.emplace_back(
|
||||
column_family_name,
|
||||
SanitizeCfOptions(immutable_db_options_, /*read_only=*/false,
|
||||
column_family.options));
|
||||
}
|
||||
|
||||
if (prepared_column_families.empty()) {
|
||||
return preparation_status;
|
||||
}
|
||||
|
||||
uint64_t options_file_number = 0;
|
||||
uint64_t options_file_size = 0;
|
||||
Status s = PersistOptionsFileForColumnFamilyManipulation(
|
||||
write_options, prepared_column_families, {}, &options_file_number,
|
||||
&options_file_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// TEST_SYNC_POINT handles the disabled singleton case internally.
|
||||
// @lint-ignore NULLSAFECLANG nullable-dereference
|
||||
TEST_SYNC_POINT("DBImpl::CreateColumnFamilyImpl:AfterPersistOptions");
|
||||
|
||||
for (size_t i = 0; i < prepared_column_families.size(); ++i) {
|
||||
const auto& column_family = prepared_column_families[i];
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
s = CreateColumnFamilyWithPreparedOptions(
|
||||
read_options, write_options, column_family.options, column_family.name,
|
||||
i == 0 ? options_file_number : 0, i == 0 ? options_file_size : 0,
|
||||
&handle);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
handles->push_back(handle);
|
||||
}
|
||||
if (s.ok() && !preparation_status.ok()) {
|
||||
s = preparation_status;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamilyWithPreparedOptions(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const ColumnFamilyOptions& sanitized_cf_options,
|
||||
const std::string& column_family_name, uint64_t options_file_number,
|
||||
uint64_t options_file_size, ColumnFamilyHandle** handle) {
|
||||
options_mutex_.AssertHeld();
|
||||
*handle = nullptr;
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ColumnFamilySet* column_family_set = versions_->GetColumnFamilySet();
|
||||
assert(column_family_set != nullptr);
|
||||
if (column_family_set->GetColumnFamily(column_family_name) != nullptr) {
|
||||
return Status::InvalidArgument("Column family already exists");
|
||||
}
|
||||
}
|
||||
|
||||
Status s;
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -4627,22 +4719,26 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
uint32_t new_id = versions_->GetColumnFamilySet()->GetNextColumnFamilyID();
|
||||
edit.SetColumnFamily(new_id);
|
||||
edit.SetLogNumber(cur_wal_number_);
|
||||
edit.SetComparatorName(cf_options.comparator->Name());
|
||||
edit.SetComparatorName(sanitized_cf_options.comparator->Name());
|
||||
edit.SetPersistUserDefinedTimestamps(
|
||||
cf_options.persist_user_defined_timestamps);
|
||||
sanitized_cf_options.persist_user_defined_timestamps);
|
||||
if (options_file_number != 0) {
|
||||
edit.SetCommittedOptionsFileNumber(options_file_number);
|
||||
}
|
||||
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
{ // write thread
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
// LogAndApply will both write the creation in MANIFEST and create
|
||||
// ColumnFamilyData object
|
||||
s = versions_->LogAndApply(nullptr, read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir(), false,
|
||||
&cf_options);
|
||||
s = versions_->LogAndApplyColumnFamilyAddWithSanitizedOptions(
|
||||
read_options, write_options, &edit, &mutex_, directories_.GetDbDir(),
|
||||
sanitized_cf_options);
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
if (s.ok() && options_file_number != 0) {
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto* cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
@@ -4654,7 +4750,8 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
auto* cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
assert(cfd != nullptr);
|
||||
MaybeInitBlobDirectWriteColumnFamily(cfd, cf_options, column_family_name);
|
||||
MaybeInitBlobDirectWriteColumnFamily(cfd, sanitized_cf_options,
|
||||
column_family_name);
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
|
||||
if (!cfd->mem()->IsSnapshotSupported()) {
|
||||
@@ -4675,7 +4772,6 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
} // InstrumentedMutexLock l(&mutex_)
|
||||
|
||||
sv_context.Clean();
|
||||
// this is outside the mutex
|
||||
if (s.ok()) {
|
||||
NewThreadStatusCfInfo(
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(*handle)->cfd());
|
||||
@@ -4688,8 +4784,12 @@ Status DBImpl::DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s = DropColumnFamilyImpl(column_family);
|
||||
if (s.ok()) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
s = WriteOptionsFile(WriteOptions(), false /*db_mutex_already_held*/);
|
||||
if (immutable_db_options_.track_options_file_number_in_manifest) {
|
||||
MaybeDeleteObsoleteOptionsFiles();
|
||||
} else {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
s = WriteOptionsFile(WriteOptions(), false /*db_mutex_already_held*/);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -4699,6 +4799,40 @@ Status DBImpl::DropColumnFamilies(
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
Status s;
|
||||
bool success_once = false;
|
||||
std::vector<ColumnFamilyHandle*> validated_column_families;
|
||||
std::vector<uint32_t> excluded_cf_ids;
|
||||
std::unordered_set<uint32_t> seen_cf_ids;
|
||||
for (auto* handle : column_families) {
|
||||
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(handle)->cfd();
|
||||
assert(cfd != nullptr);
|
||||
if (cfd->GetID() == 0) {
|
||||
s = Status::InvalidArgument("Can't drop default column family");
|
||||
break;
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd->IsDropped() || !seen_cf_ids.insert(cfd->GetID()).second) {
|
||||
s = Status::InvalidArgument("Column family already dropped!\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
validated_column_families.push_back(handle);
|
||||
excluded_cf_ids.push_back(cfd->GetID());
|
||||
}
|
||||
|
||||
uint64_t options_file_number = 0;
|
||||
uint64_t options_file_size = 0;
|
||||
if (s.ok() && !validated_column_families.empty()) {
|
||||
s = PersistOptionsFileForColumnFamilyManipulation(
|
||||
WriteOptions(), {}, excluded_cf_ids, &options_file_number,
|
||||
&options_file_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
const Status validation_status = s;
|
||||
s = Status::OK();
|
||||
|
||||
ColumnFamilySet* const column_family_set = versions_->GetColumnFamilySet();
|
||||
const bool batch_flushable_mem_accounting =
|
||||
write_buffer_manager_ != nullptr &&
|
||||
@@ -4715,20 +4849,31 @@ Status DBImpl::DropColumnFamilies(
|
||||
column_family_set->EndBulkFlushableMemAccountingUpdate();
|
||||
}
|
||||
});
|
||||
for (auto* handle : column_families) {
|
||||
s = DropColumnFamilyImpl(handle);
|
||||
for (size_t i = 0; i < validated_column_families.size(); ++i) {
|
||||
s = DropColumnFamilyWithOptionsFile(
|
||||
validated_column_families[i],
|
||||
validation_status.ok() && i + 1 == validated_column_families.size()
|
||||
? options_file_number
|
||||
: 0,
|
||||
validation_status.ok() && i + 1 == validated_column_families.size()
|
||||
? options_file_size
|
||||
: 0);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
success_once = true;
|
||||
}
|
||||
}
|
||||
if (s.ok() && !validation_status.ok()) {
|
||||
s = validation_status;
|
||||
}
|
||||
if (success_once) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
Status persist_options_status =
|
||||
WriteOptionsFile(WriteOptions(), false /*db_mutex_already_held*/);
|
||||
if (s.ok() && !persist_options_status.ok()) {
|
||||
s = persist_options_status;
|
||||
if (immutable_db_options_.track_options_file_number_in_manifest) {
|
||||
MaybeDeleteObsoleteOptionsFiles();
|
||||
} else {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
s.UpdateIfOk(
|
||||
WriteOptionsFile(WriteOptions(), false /*db_mutex_already_held*/));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -4737,23 +4882,57 @@ Status DBImpl::DropColumnFamilies(
|
||||
Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
assert(cfd != nullptr);
|
||||
if (cfd->GetID() == 0) {
|
||||
return Status::InvalidArgument("Can't drop default column family");
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd->IsDropped()) {
|
||||
return Status::InvalidArgument("Column family already dropped!\n");
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t options_file_number = 0;
|
||||
uint64_t options_file_size = 0;
|
||||
Status s = PersistOptionsFileForColumnFamilyManipulation(
|
||||
WriteOptions(), {}, {cfd->GetID()}, &options_file_number,
|
||||
&options_file_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return DropColumnFamilyWithOptionsFile(column_family, options_file_number,
|
||||
options_file_size);
|
||||
}
|
||||
|
||||
Status DBImpl::DropColumnFamilyWithOptionsFile(
|
||||
ColumnFamilyHandle* column_family, uint64_t options_file_number,
|
||||
uint64_t options_file_size) {
|
||||
options_mutex_.AssertHeld();
|
||||
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
assert(cfd != nullptr);
|
||||
if (cfd->GetID() == 0) {
|
||||
return Status::InvalidArgument("Can't drop default column family");
|
||||
}
|
||||
|
||||
bool cf_support_snapshot = cfd->mem()->IsSnapshotSupported();
|
||||
|
||||
Status s;
|
||||
VersionEdit edit;
|
||||
edit.DropColumnFamily();
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
if (options_file_number != 0) {
|
||||
edit.SetCommittedOptionsFileNumber(options_file_number);
|
||||
}
|
||||
|
||||
Status s;
|
||||
// Save re-aquiring lock for RegisterRecordSeqnoTimeWorker when not
|
||||
// applicable
|
||||
MinAndMaxPreserveSeconds preserve_info;
|
||||
@@ -4770,6 +4949,9 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
s = versions_->LogAndApply(cfd, read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
if (s.ok() && options_file_number != 0) {
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
}
|
||||
if (s.ok() && cfd->blob_partition_manager() != nullptr) {
|
||||
UnregisterBlobDirectWriteColumnFamily();
|
||||
}
|
||||
@@ -5906,7 +6088,8 @@ void DBImpl::GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
std::string start_with_ts;
|
||||
std::string limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
range.start, range.limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
assert(start.has_value());
|
||||
@@ -5968,7 +6151,8 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
// Add timestamp if needed
|
||||
std::string start_with_ts, limit_with_ts;
|
||||
std::string start_with_ts;
|
||||
std::string limit_with_ts;
|
||||
auto [start, limit] = MaybeAddTimestampsToRange(
|
||||
range[i].start, range[i].limit, ts_sz, &start_with_ts, &limit_with_ts);
|
||||
assert(start.has_value());
|
||||
@@ -6101,7 +6285,10 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
continue;
|
||||
}
|
||||
std::vector<FileMetaData*> level_files;
|
||||
InternalKey begin_storage, end_storage, *begin_key, *end_key;
|
||||
InternalKey begin_storage;
|
||||
InternalKey end_storage;
|
||||
InternalKey* begin_key;
|
||||
InternalKey* end_key;
|
||||
if (begin == nullptr) {
|
||||
begin_key = nullptr;
|
||||
} else {
|
||||
@@ -6340,7 +6527,8 @@ std::string DBImpl::GenerateDbSessionId(Env*) {
|
||||
// See SemiStructuredUniqueIdGen for its desirable properties.
|
||||
auto gen = DbSessionIdGen();
|
||||
|
||||
uint64_t lo, hi;
|
||||
uint64_t lo;
|
||||
uint64_t hi;
|
||||
gen->GenerateNext(&hi, &lo);
|
||||
if (lo == 0) {
|
||||
// Avoid emitting session ID with lo==0, so that SST unique
|
||||
@@ -6663,37 +6851,46 @@ Status DBImpl::WriteOptionsFile(const WriteOptions& write_options,
|
||||
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
const bool track_options_file_number =
|
||||
immutable_db_options_.track_options_file_number_in_manifest;
|
||||
|
||||
// Unlock during expensive operations.
|
||||
mutex_.Unlock();
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:1");
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:2");
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::WriteOptionsFile:PersistOptions",
|
||||
&db_options);
|
||||
|
||||
std::string file_name =
|
||||
TempOptionsFileName(GetName(), versions_->NewFileNumber());
|
||||
Status s = PersistRocksDBOptions(write_options, db_options, cf_names, cf_opts,
|
||||
file_name, fs_.get());
|
||||
uint64_t options_file_number = 0;
|
||||
uint64_t options_file_size = 0;
|
||||
Status s = PersistOptionsFile(write_options, db_options, cf_names, cf_opts,
|
||||
&options_file_number, &options_file_size);
|
||||
if (s.ok()) {
|
||||
// TEST_SYNC_POINT handles the disabled singleton case internally.
|
||||
// @lint-ignore NULLSAFECLANG nullable-dereference
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:AfterPersistOptions");
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = RenameTempFileToOptionsFile(file_name,
|
||||
db_options.compaction_service != nullptr);
|
||||
}
|
||||
|
||||
if (!s.ok() && GetEnv()->FileExists(file_name).ok()) {
|
||||
if (!GetEnv()->DeleteFile(file_name).ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete temp options file %s",
|
||||
file_name.c_str());
|
||||
mutex_.Lock();
|
||||
if (track_options_file_number) {
|
||||
VersionEdit edit;
|
||||
edit.SetCommittedOptionsFileNumber(options_file_number);
|
||||
edit.MarkOptionsFileManipulation();
|
||||
s = versions_->LogAndApplyToDefaultColumnFamily(
|
||||
ReadOptions(), write_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
} else {
|
||||
versions_->ApplyLegacyOptionsFileNumber(options_file_number);
|
||||
}
|
||||
if (s.ok()) {
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
}
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unnable to persist options -- %s", s.ToString().c_str());
|
||||
s = Status::IOError("Unable to persist options.", s.ToString().c_str());
|
||||
if (s.ok()) {
|
||||
MaybeDeleteObsoleteOptionsFiles();
|
||||
}
|
||||
|
||||
// Restore lock if appropriate
|
||||
@@ -6703,9 +6900,113 @@ Status DBImpl::WriteOptionsFile(const WriteOptions& write_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::PersistOptionsFile(
|
||||
const WriteOptions& write_options, const DBOptions& db_options,
|
||||
const std::vector<std::string>& cf_names,
|
||||
const std::vector<ColumnFamilyOptions>& cf_opts,
|
||||
uint64_t* options_file_number, uint64_t* options_file_size) {
|
||||
assert(options_file_number != nullptr);
|
||||
assert(options_file_size != nullptr);
|
||||
*options_file_number = 0;
|
||||
*options_file_size = 0;
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:1");
|
||||
TEST_SYNC_POINT("DBImpl::WriteOptionsFile:2");
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::WriteOptionsFile:PersistOptions",
|
||||
const_cast<DBOptions*>(&db_options));
|
||||
|
||||
const std::string file_name =
|
||||
TempOptionsFileName(GetName(), versions_->NewFileNumber());
|
||||
*options_file_number = versions_->NewFileNumber();
|
||||
|
||||
Status s = PersistRocksDBOptions(write_options, db_options, cf_names, cf_opts,
|
||||
file_name, fs_.get());
|
||||
if (s.ok()) {
|
||||
s = RenameTempFileToOptionsFile(file_name, *options_file_number,
|
||||
options_file_size);
|
||||
}
|
||||
|
||||
if (!s.ok() && GetEnv()->FileExists(file_name).ok()) {
|
||||
if (!GetEnv()->DeleteFile(file_name).ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete temp options file %s",
|
||||
file_name.c_str());
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to persist options -- %s", s.ToString().c_str());
|
||||
s = Status::IOError("Unable to persist options.", s.ToString().c_str());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::PersistOptionsFileForColumnFamilyManipulation(
|
||||
const WriteOptions& write_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& added_column_families,
|
||||
const std::vector<uint32_t>& excluded_cf_ids, uint64_t* options_file_number,
|
||||
uint64_t* options_file_size) {
|
||||
options_mutex_.AssertHeld();
|
||||
assert(options_file_number != nullptr);
|
||||
assert(options_file_size != nullptr);
|
||||
*options_file_number = 0;
|
||||
*options_file_size = 0;
|
||||
|
||||
bool bootstrap_tracking = false;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
bootstrap_tracking =
|
||||
immutable_db_options_.track_options_file_number_in_manifest &&
|
||||
!versions_->has_committed_options_file_number();
|
||||
}
|
||||
if (bootstrap_tracking) {
|
||||
// A DB without a committed number still uses highest-numbered OPTIONS
|
||||
// semantics. Commit the current topology before writing a proposed
|
||||
// topology snapshot, so a failed CF MANIFEST edit cannot make its orphan
|
||||
// candidate authoritative.
|
||||
Status bootstrap_s =
|
||||
WriteOptionsFile(write_options, /*db_mutex_already_held=*/false);
|
||||
if (!bootstrap_s.ok()) {
|
||||
return bootstrap_s;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> cf_names;
|
||||
std::vector<ColumnFamilyOptions> cf_opts;
|
||||
DBOptions db_options;
|
||||
const std::unordered_set<uint32_t> excluded_cf_id_set(excluded_cf_ids.begin(),
|
||||
excluded_cf_ids.end());
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
assert(versions_ != nullptr);
|
||||
if (!immutable_db_options_.track_options_file_number_in_manifest) {
|
||||
return Status::OK();
|
||||
}
|
||||
auto* column_family_set = versions_->GetColumnFamilySet();
|
||||
assert(column_family_set != nullptr);
|
||||
for (auto* cfd : *column_family_set) {
|
||||
assert(cfd != nullptr);
|
||||
if (cfd->IsDropped() || excluded_cf_id_set.count(cfd->GetID()) != 0) {
|
||||
continue;
|
||||
}
|
||||
cf_names.push_back(cfd->GetName());
|
||||
cf_opts.push_back(cfd->GetLatestCFOptions());
|
||||
}
|
||||
for (const auto& column_family : added_column_families) {
|
||||
cf_names.push_back(column_family.name);
|
||||
cf_opts.push_back(column_family.options);
|
||||
}
|
||||
db_options = BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
}
|
||||
|
||||
return PersistOptionsFile(write_options, db_options, cf_names, cf_opts,
|
||||
options_file_number, options_file_size);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void DeleteOptionsFilesHelper(const std::map<uint64_t, std::string>& filenames,
|
||||
const size_t num_files_to_keep,
|
||||
uint64_t protected_file_number,
|
||||
const std::shared_ptr<Logger>& info_log,
|
||||
Env* env) {
|
||||
if (filenames.size() <= num_files_to_keep) {
|
||||
@@ -6713,6 +7014,11 @@ void DeleteOptionsFilesHelper(const std::map<uint64_t, std::string>& filenames,
|
||||
}
|
||||
for (auto iter = std::next(filenames.begin(), num_files_to_keep);
|
||||
iter != filenames.end(); ++iter) {
|
||||
const uint64_t file_number =
|
||||
std::numeric_limits<uint64_t>::max() - iter->first;
|
||||
if (file_number == protected_file_number) {
|
||||
continue;
|
||||
}
|
||||
if (!env->DeleteFile(iter->second).ok()) {
|
||||
ROCKS_LOG_WARN(info_log, "Unable to delete options file %s",
|
||||
iter->second.c_str());
|
||||
@@ -6744,8 +7050,17 @@ Status DBImpl::DeleteObsoleteOptionsFiles(bool schedule_only) {
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps the latest 2 Options file
|
||||
// Keep the latest two snapshots for legacy fallback. The snapshot named by
|
||||
// the committed MANIFEST number is retained separately below and can
|
||||
// increase the total number of retained OPTIONS files beyond two.
|
||||
const size_t kNumOptionsFilesKept = 2;
|
||||
uint64_t protected_file_number = 0;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (versions_->has_committed_options_file_number()) {
|
||||
protected_file_number = versions_->options_file_number();
|
||||
}
|
||||
}
|
||||
if (options_filenames.size() > kNumOptionsFilesKept) {
|
||||
if (schedule_only) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -6754,12 +7069,16 @@ Status DBImpl::DeleteObsoleteOptionsFiles(bool schedule_only) {
|
||||
iter != options_filenames.end(); ++iter) {
|
||||
const uint64_t file_number =
|
||||
std::numeric_limits<uint64_t>::max() - iter->first;
|
||||
if (file_number == protected_file_number) {
|
||||
continue;
|
||||
}
|
||||
SchedulePendingPurge(iter->second, GetName(), kOptionsFile, file_number,
|
||||
/*job_id=*/0);
|
||||
}
|
||||
SchedulePurge();
|
||||
} else {
|
||||
DeleteOptionsFilesHelper(options_filenames, kNumOptionsFilesKept,
|
||||
protected_file_number,
|
||||
immutable_db_options_.info_log, GetEnv());
|
||||
}
|
||||
}
|
||||
@@ -6767,17 +7086,19 @@ Status DBImpl::DeleteObsoleteOptionsFiles(bool schedule_only) {
|
||||
}
|
||||
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled) {
|
||||
uint64_t options_file_number,
|
||||
uint64_t* options_file_size) {
|
||||
Status s;
|
||||
|
||||
uint64_t options_file_number = versions_->NewFileNumber();
|
||||
assert(options_file_size != nullptr);
|
||||
std::string options_file_name =
|
||||
OptionsFileName(GetName(), options_file_number);
|
||||
uint64_t options_file_size = 0;
|
||||
s = GetEnv()->GetFileSize(file_name, &options_file_size);
|
||||
Env* env = GetEnv();
|
||||
assert(env != nullptr);
|
||||
s = env->GetFileSize(file_name, options_file_size);
|
||||
if (s.ok()) {
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
s = env->RenameFile(file_name, options_file_name);
|
||||
std::unique_ptr<FSDirectory> dir_obj;
|
||||
if (s.ok()) {
|
||||
s = fs_->NewDirectory(GetName(), IOOptions(), &dir_obj, nullptr);
|
||||
@@ -6801,47 +7122,33 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
enum class ObsoleteOptionsFileCleanup {
|
||||
kSkip,
|
||||
kDeleteNow,
|
||||
kSchedule,
|
||||
};
|
||||
ObsoleteOptionsFileCleanup obsolete_options_file_cleanup =
|
||||
ObsoleteOptionsFileCleanup::kSkip;
|
||||
return s;
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
if (!disable_delete_obsolete_files_ && !is_remote_compaction_enabled) {
|
||||
if (immutable_db_options_.avoid_unnecessary_blocking_io &&
|
||||
!reject_new_background_jobs_) {
|
||||
// DB::Open() sets `opened_successfully_` after WriteOptionsFile()
|
||||
// returns, then schedules the deferred OPTIONS-file purge.
|
||||
obsolete_options_file_cleanup =
|
||||
opened_successfully_ ? ObsoleteOptionsFileCleanup::kSchedule
|
||||
: ObsoleteOptionsFileCleanup::kSkip;
|
||||
} else {
|
||||
obsolete_options_file_cleanup =
|
||||
ObsoleteOptionsFileCleanup::kDeleteNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obsolete_options_file_cleanup != ObsoleteOptionsFileCleanup::kSkip) {
|
||||
Status obsolete_options_status =
|
||||
DeleteObsoleteOptionsFiles(obsolete_options_file_cleanup ==
|
||||
ObsoleteOptionsFileCleanup::kSchedule);
|
||||
if (!obsolete_options_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete obsolete OPTIONS files: %s",
|
||||
obsolete_options_status.ToString().c_str());
|
||||
void DBImpl::MaybeDeleteObsoleteOptionsFiles() {
|
||||
enum class Cleanup { kSkip, kDeleteNow, kSchedule };
|
||||
Cleanup cleanup = Cleanup::kSkip;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (!disable_delete_obsolete_files_ &&
|
||||
immutable_db_options_.compaction_service == nullptr) {
|
||||
if (immutable_db_options_.avoid_unnecessary_blocking_io &&
|
||||
!reject_new_background_jobs_) {
|
||||
cleanup = opened_successfully_ ? Cleanup::kSchedule : Cleanup::kSkip;
|
||||
} else {
|
||||
cleanup = Cleanup::kDeleteNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
if (cleanup == Cleanup::kSkip) {
|
||||
return;
|
||||
}
|
||||
Status s = DeleteObsoleteOptionsFiles(cleanup == Cleanup::kSchedule);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete obsolete OPTIONS files: %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
@@ -7087,7 +7394,7 @@ class FileIngestionHandleImpl : public FileIngestionHandle {
|
||||
Status Abort() override;
|
||||
|
||||
DBImpl* const db_;
|
||||
std::vector<ExternalSstFileIngestionJob> jobs_;
|
||||
std::vector<std::unique_ptr<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.
|
||||
@@ -7112,7 +7419,7 @@ void DBImpl::RollbackPreparedFileIngestion(FileIngestionHandleImpl* const h) {
|
||||
// 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);
|
||||
job->Cleanup(rollback_status);
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -7226,14 +7533,14 @@ Status DBImpl::PrepareFileIngestion(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExternalSstFileIngestionJob> ingestion_jobs;
|
||||
std::vector<std::unique_ptr<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_,
|
||||
mutable_db_options_, file_options_, &snapshots_,
|
||||
arg.options, &directories_, &event_logger_,
|
||||
io_tracer_);
|
||||
ingestion_jobs.push_back(std::make_unique<ExternalSstFileIngestionJob>(
|
||||
versions_.get(), cfd, immutable_db_options_, mutable_db_options_,
|
||||
file_options_, &snapshots_, arg.options, &directories_, &event_logger_,
|
||||
io_tracer_));
|
||||
}
|
||||
|
||||
// TODO (yanqin) maybe handle the case in which column_families have
|
||||
@@ -7241,8 +7548,8 @@ Status DBImpl::PrepareFileIngestion(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> pending_output_elem;
|
||||
size_t total = 0;
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
total += ingestion_jobs[i].NumFilesToPrepare(args[i].external_files.size(),
|
||||
args[i].atomic_replace_range);
|
||||
total += ingestion_jobs[i]->NumFilesToPrepare(args[i].external_files.size(),
|
||||
args[i].atomic_replace_range);
|
||||
}
|
||||
uint64_t next_file_number = 0;
|
||||
Status status = ReserveFileNumbersBeforeIngestion(
|
||||
@@ -7257,12 +7564,12 @@ Status DBImpl::PrepareFileIngestion(
|
||||
// TODO(yanqin) maybe make jobs run in parallel
|
||||
uint64_t start_file_number = next_file_number;
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
start_file_number += ingestion_jobs[i - 1].NumFilesToPrepare(
|
||||
start_file_number += ingestion_jobs[i - 1]->NumFilesToPrepare(
|
||||
args[i - 1].external_files.size(), args[i - 1].atomic_replace_range);
|
||||
SuperVersion* super_version =
|
||||
ingestion_jobs[i].GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
ingestion_jobs[i]->GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
this);
|
||||
Status es = ingestion_jobs[i].Prepare(
|
||||
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,
|
||||
@@ -7277,9 +7584,9 @@ Status DBImpl::PrepareFileIngestion(
|
||||
TEST_SYNC_POINT("DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1");
|
||||
{
|
||||
SuperVersion* super_version =
|
||||
ingestion_jobs[0].GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
ingestion_jobs[0]->GetColumnFamilyData()->GetReferencedSuperVersion(
|
||||
this);
|
||||
Status es = ingestion_jobs[0].Prepare(
|
||||
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,
|
||||
@@ -7291,7 +7598,7 @@ Status DBImpl::PrepareFileIngestion(
|
||||
}
|
||||
if (!status.ok()) {
|
||||
for (size_t i = 0; i != num_cfs; ++i) {
|
||||
ingestion_jobs[i].Cleanup(status);
|
||||
ingestion_jobs[i]->Cleanup(status);
|
||||
}
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
@@ -7350,13 +7657,13 @@ Status DBImpl::CommitFileIngestionHandles(
|
||||
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);
|
||||
std::max(max_file_opening_threads, job->file_opening_threads());
|
||||
auto [it, inserted] = primary_job_for_cfd.try_emplace(
|
||||
job->GetColumnFamilyData(), job.get());
|
||||
if (inserted) {
|
||||
ingestion_jobs.push_back(&job);
|
||||
ingestion_jobs.push_back(job.get());
|
||||
} else {
|
||||
status = it->second->MergeForSameColumnFamily(&job);
|
||||
status = it->second->MergeForSameColumnFamily(job.get());
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -7554,6 +7861,20 @@ Status DBImpl::CommitFileIngestionHandles(
|
||||
versions_->SetLastSequence(max_assigned_seqno);
|
||||
}
|
||||
}
|
||||
// An iterator whose Version predates the ingestion cannot see the
|
||||
// ingested files, whatever sequence number it reads at: files can get
|
||||
// no new seqno (seqno zero, ingest_behind, DB-generated files), and an
|
||||
// iterator can pair an older SuperVersion with a newer sequence number.
|
||||
// Such iterators must not convert tombstones over the ingested files.
|
||||
// We still hold the per-CF ingest_sst_lock as a ReadLock, so no
|
||||
// conversion runs until the barrier is in place.
|
||||
if (status.ok()) {
|
||||
for (auto* job : ingestion_jobs) {
|
||||
auto* cfd = job->GetColumnFamilyData();
|
||||
cfd->mem()->BumpIngestVersionBarrier(
|
||||
cfd->current()->GetVersionNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* job : ingestion_jobs) {
|
||||
|
||||
+90
-9
@@ -101,6 +101,17 @@ struct JobContext;
|
||||
struct ExternalSstFileInfo;
|
||||
struct MemTableInfo;
|
||||
|
||||
// Captured under the same DB mutex hold as a subset checkpoint's MANIFEST
|
||||
// boundary and live CF set. This prevents the generated OPTIONS membership
|
||||
// from describing a different point in time than the copied MANIFEST.
|
||||
struct SubsetCheckpointOptionsSnapshot {
|
||||
uint64_t options_file_number = 0;
|
||||
bool commit_options_file_number = false;
|
||||
DBOptions db_options;
|
||||
std::vector<std::string> cf_names;
|
||||
std::vector<ColumnFamilyOptions> cf_options;
|
||||
};
|
||||
|
||||
// Class to maintain directories for all database paths other than main one.
|
||||
class Directories {
|
||||
public:
|
||||
@@ -223,7 +234,7 @@ class DBImpl : public DB
|
||||
DBImpl(DBImpl&&) = delete;
|
||||
void operator=(DBImpl&&) = delete;
|
||||
|
||||
virtual ~DBImpl();
|
||||
~DBImpl() override;
|
||||
|
||||
// ---- Implementations of the DB interface ----
|
||||
|
||||
@@ -642,7 +653,8 @@ class DBImpl : public DB
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
const std::vector<uint32_t>& include_cf_ids,
|
||||
std::vector<LiveFileStorageInfo>* files,
|
||||
std::vector<uint32_t>* excluded_cf_ids);
|
||||
std::vector<uint32_t>* excluded_cf_ids,
|
||||
SubsetCheckpointOptionsSnapshot* options_snapshot);
|
||||
|
||||
// Shared body of GetLiveFilesStorageInfo and its subset-checkpoint variant.
|
||||
// include_cf_ids empty -> no filter; otherwise records skipped CF ids in
|
||||
@@ -651,15 +663,23 @@ class DBImpl : public DB
|
||||
const LiveFilesStorageInfoOptions& opts,
|
||||
const std::vector<uint32_t>& include_cf_ids,
|
||||
std::vector<LiveFileStorageInfo>* files,
|
||||
std::vector<uint32_t>* excluded_cf_ids);
|
||||
std::vector<uint32_t>* excluded_cf_ids,
|
||||
SubsetCheckpointOptionsSnapshot* options_snapshot);
|
||||
|
||||
// Appends kColumnFamilyDrop records to the MANIFEST file at manifest_path for
|
||||
// the given column family ids. Used by Checkpoint to make a subset-CF
|
||||
// checkpoint's copied MANIFEST consistent with the reduced file set. Operates
|
||||
// only on the given file; does not mutate this DB's live state.
|
||||
Status AppendColumnFamilyDropsToManifest(const std::string& manifest_path,
|
||||
uint64_t manifest_size,
|
||||
const std::vector<uint32_t>& cf_ids);
|
||||
Status AppendColumnFamilyDropsToManifest(
|
||||
const std::string& manifest_path, uint64_t manifest_size,
|
||||
const std::vector<uint32_t>& cf_ids,
|
||||
uint64_t committed_options_file_number);
|
||||
|
||||
// Writes a previously captured subset-specific OPTIONS snapshot into the
|
||||
// checkpoint. A zero file number is a no-op for a legacy source DB.
|
||||
Status CreateOptionsFileForSubsetCheckpoint(
|
||||
const std::string& checkpoint_dir,
|
||||
const SubsetCheckpointOptionsSnapshot& options_snapshot);
|
||||
|
||||
Status GetPreparedFileInfoForExternalSstIngestion(
|
||||
const std::string& file_path,
|
||||
@@ -1193,6 +1213,12 @@ class DBImpl : public DB
|
||||
batches_[seq] = {log, batch, batch_cnt};
|
||||
}
|
||||
|
||||
// No copying allowed
|
||||
RecoveredTransaction(const RecoveredTransaction&) = delete;
|
||||
RecoveredTransaction& operator=(const RecoveredTransaction&) = delete;
|
||||
RecoveredTransaction(RecoveredTransaction&&) = delete;
|
||||
RecoveredTransaction& operator=(RecoveredTransaction&&) = delete;
|
||||
|
||||
~RecoveredTransaction() {
|
||||
for (auto& it : batches_) {
|
||||
delete it.second.batch_;
|
||||
@@ -1728,6 +1754,13 @@ class DBImpl : public DB
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
class RecoveryContext {
|
||||
public:
|
||||
RecoveryContext() = default;
|
||||
// No copying allowed
|
||||
RecoveryContext(const RecoveryContext&) = delete;
|
||||
RecoveryContext& operator=(const RecoveryContext&) = delete;
|
||||
RecoveryContext(RecoveryContext&&) = delete;
|
||||
RecoveryContext& operator=(RecoveryContext&&) = delete;
|
||||
|
||||
~RecoveryContext() {
|
||||
for (auto& edit_list : edit_lists_) {
|
||||
for (auto* edit : edit_list) {
|
||||
@@ -1760,6 +1793,10 @@ class DBImpl : public DB
|
||||
std::unordered_map<uint32_t, uint32_t> map_; // cf_id to index;
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// Files found in the primary DB directory by MaybeUpdateNextFileNumber().
|
||||
// Reused for OPTIONS selection so writable open performs one directory
|
||||
// scan instead of two.
|
||||
std::vector<std::string> existing_db_files_;
|
||||
// All existing data files (SST files and Blob files) found during DB::Open.
|
||||
std::vector<std::string> existing_data_files_;
|
||||
bool is_new_db_ = false;
|
||||
@@ -1770,6 +1807,24 @@ class DBImpl : public DB
|
||||
Status WriteOptionsFile(const WriteOptions& write_options,
|
||||
bool db_mutex_already_held);
|
||||
|
||||
// Writes, renames, and directory-syncs an OPTIONS snapshot, but does not
|
||||
// publish it in VersionSet. The caller must either commit its file number in
|
||||
// MANIFEST or leave it as an unreferenced orphan.
|
||||
Status PersistOptionsFile(const WriteOptions& write_options,
|
||||
const DBOptions& db_options,
|
||||
const std::vector<std::string>& cf_names,
|
||||
const std::vector<ColumnFamilyOptions>& cf_opts,
|
||||
uint64_t* options_file_number,
|
||||
uint64_t* options_file_size);
|
||||
|
||||
// For tracked DBs, writes the OPTIONS snapshot that is committed with a
|
||||
// column-family add/drop. Returns file number zero in legacy mode.
|
||||
Status PersistOptionsFileForColumnFamilyManipulation(
|
||||
const WriteOptions& write_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& added_column_families,
|
||||
const std::vector<uint32_t>& excluded_cf_ids,
|
||||
uint64_t* options_file_number, uint64_t* options_file_size);
|
||||
|
||||
Status CompactRangeInternal(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
@@ -1779,7 +1834,9 @@ class DBImpl : public DB
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is NOT held
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled);
|
||||
uint64_t options_file_number,
|
||||
uint64_t* options_file_size);
|
||||
void MaybeDeleteObsoleteOptionsFiles();
|
||||
Status DeleteObsoleteOptionsFiles(bool schedule_only);
|
||||
|
||||
void NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
|
||||
@@ -2220,6 +2277,12 @@ class DBImpl : public DB
|
||||
explicit WriteContext(bool create_superversion = false)
|
||||
: superversion_context(create_superversion) {}
|
||||
|
||||
// No copying allowed
|
||||
WriteContext(const WriteContext&) = delete;
|
||||
WriteContext& operator=(const WriteContext&) = delete;
|
||||
WriteContext(WriteContext&&) = delete;
|
||||
WriteContext& operator=(WriteContext&&) = delete;
|
||||
|
||||
~WriteContext() {
|
||||
superversion_context.Clean();
|
||||
for (auto& m : memtables_to_free_) {
|
||||
@@ -2473,6 +2536,15 @@ class DBImpl : public DB
|
||||
const ColumnFamilyOptions& cf_options,
|
||||
const std::string& cf_name,
|
||||
ColumnFamilyHandle** handle);
|
||||
Status CreateColumnFamiliesImpl(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles);
|
||||
Status CreateColumnFamilyWithPreparedOptions(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
const ColumnFamilyOptions& sanitized_cf_options,
|
||||
const std::string& cf_name, uint64_t options_file_number,
|
||||
uint64_t options_file_size, ColumnFamilyHandle** handle);
|
||||
|
||||
// Follow-up work to user creating a column family or (families)
|
||||
Status WrapUpCreateColumnFamilies(
|
||||
@@ -2480,6 +2552,9 @@ class DBImpl : public DB
|
||||
const std::vector<const ColumnFamilyOptions*>& cf_options);
|
||||
|
||||
Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family);
|
||||
Status DropColumnFamilyWithOptionsFile(ColumnFamilyHandle* column_family,
|
||||
uint64_t options_file_number,
|
||||
uint64_t options_file_size);
|
||||
|
||||
// Delete any unneeded files and stale in-memory entries.
|
||||
void DeleteObsoleteFiles();
|
||||
@@ -3232,6 +3307,8 @@ class DBImpl : public DB
|
||||
return *this;
|
||||
}
|
||||
|
||||
~UnpublishedWAL() = default;
|
||||
|
||||
void Reset() {
|
||||
log_number = 0;
|
||||
writer.reset();
|
||||
@@ -4100,8 +4177,12 @@ uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
// Fix user-supplied options to be reasonable
|
||||
template <class T, class V>
|
||||
static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
|
||||
if (static_cast<V>(*ptr) > maxvalue) *ptr = maxvalue;
|
||||
if (static_cast<V>(*ptr) < minvalue) *ptr = minvalue;
|
||||
if (static_cast<V>(*ptr) > maxvalue) {
|
||||
*ptr = maxvalue;
|
||||
}
|
||||
if (static_cast<V>(*ptr) < minvalue) {
|
||||
*ptr = minvalue;
|
||||
}
|
||||
}
|
||||
|
||||
inline Status DBImpl::FailIfCfHasTs(
|
||||
|
||||
@@ -62,6 +62,15 @@ void RecordAtomicFlushRequestReason(Statistics* stats,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> MemTableIds(const autovector<ReadOnlyMemTable*>& mems) {
|
||||
std::vector<uint64_t> ids;
|
||||
ids.reserve(mems.size());
|
||||
for (const ReadOnlyMemTable* mem : mems) {
|
||||
ids.push_back(mem->GetID());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DBImpl::EnoughRoomForCompaction(
|
||||
@@ -311,8 +320,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
flush_reason);
|
||||
|
||||
bool switched_to_mempurge = false;
|
||||
size_t prepared_blob_generations = 0;
|
||||
std::vector<uint64_t> sealed_blob_numbers;
|
||||
std::vector<uint64_t> prepared_blob_memtable_ids;
|
||||
// Within flush_job.Run, rocksdb may call event listener to notify
|
||||
// file creation and deletion.
|
||||
//
|
||||
@@ -323,17 +331,17 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
auto mgr_handle = cfd->blob_partition_manager_handle();
|
||||
auto* mgr = mgr_handle.get();
|
||||
if (mgr != nullptr) {
|
||||
prepared_blob_generations = flush_job.GetMemTables().size();
|
||||
prepared_blob_memtable_ids = MemTableIds(flush_job.GetMemTables());
|
||||
std::vector<BlobFileAddition> write_path_additions;
|
||||
std::vector<BlobFileGarbage> write_path_garbages;
|
||||
std::vector<std::vector<uint64_t>> generation_blob_file_numbers;
|
||||
mutex_.Unlock();
|
||||
s = mgr->PrepareFlushAdditions(
|
||||
write_options, prepared_blob_generations, &write_path_additions,
|
||||
write_options, prepared_blob_memtable_ids, &write_path_additions,
|
||||
&write_path_garbages, &generation_blob_file_numbers);
|
||||
mutex_.Lock();
|
||||
if (!s.ok()) {
|
||||
prepared_blob_generations = 0;
|
||||
prepared_blob_memtable_ids.clear();
|
||||
} else if (!write_path_additions.empty()) {
|
||||
const auto& memtables = flush_job.GetMemTables();
|
||||
assert(generation_blob_file_numbers.size() == memtables.size());
|
||||
@@ -344,9 +352,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
memtables[i]->ProtectSealedBlobFiles(mgr_handle,
|
||||
generation_blob_file_numbers[i]);
|
||||
}
|
||||
for (const auto& addition : write_path_additions) {
|
||||
sealed_blob_numbers.push_back(addition.GetBlobFileNumber());
|
||||
}
|
||||
flush_job.AddExternalBlobFileAdditions(std::move(write_path_additions));
|
||||
flush_job.AddExternalBlobFileGarbages(std::move(write_path_garbages));
|
||||
} else if (!write_path_garbages.empty()) {
|
||||
@@ -390,7 +395,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
});
|
||||
|
||||
if (cfd->blob_partition_manager() != nullptr &&
|
||||
prepared_blob_generations > 0) {
|
||||
!prepared_blob_memtable_ids.empty()) {
|
||||
auto unconsumed_additions = flush_job.TakeExternalBlobFileAdditions();
|
||||
auto unconsumed_garbages = flush_job.TakeExternalBlobFileGarbages();
|
||||
if (!s.ok() || !unconsumed_additions.empty() ||
|
||||
@@ -399,8 +404,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// in pending_generations_. The same immutable memtables still reference
|
||||
// these sealed blob files, so a later flush retry must publish the exact
|
||||
// same additions rather than abandoning them here.
|
||||
prepared_blob_generations = 0;
|
||||
sealed_blob_numbers.clear();
|
||||
prepared_blob_memtable_ids.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,12 +412,9 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
flush_job.Cancel();
|
||||
}
|
||||
|
||||
if (s.ok() && prepared_blob_generations > 0) {
|
||||
auto* mgr = cfd->blob_partition_manager();
|
||||
mgr->CommitPreparedGenerations(prepared_blob_generations);
|
||||
if (!sealed_blob_numbers.empty()) {
|
||||
mgr->RemoveFilePartitionMappings(sealed_blob_numbers);
|
||||
}
|
||||
if (s.ok() && !prepared_blob_memtable_ids.empty()) {
|
||||
cfd->blob_partition_manager()->CommitFlushedGenerations(
|
||||
cfd->imm()->GetEarliestMemTableID());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -656,13 +657,12 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// <bool /* executed */, Status /* status code */>
|
||||
autovector<std::pair<bool, Status>> exec_status;
|
||||
std::vector<bool> pick_status;
|
||||
std::vector<size_t> prepared_blob_generations(num_cfs, 0);
|
||||
std::vector<std::vector<uint64_t>> prepared_blob_memtable_ids(num_cfs);
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
// Initially all jobs are not executed, with status OK.
|
||||
exec_status.emplace_back(false, Status::OK());
|
||||
pick_status.push_back(false);
|
||||
}
|
||||
std::unordered_map<int, std::vector<uint64_t>> sealed_blob_numbers_by_cf;
|
||||
|
||||
bool flush_for_recovery =
|
||||
bg_flush_args[0].flush_reason_ == FlushReason::kErrorRecovery ||
|
||||
@@ -694,17 +694,17 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
continue;
|
||||
}
|
||||
|
||||
prepared_blob_generations[i] = jobs[i]->GetMemTables().size();
|
||||
prepared_blob_memtable_ids[i] = MemTableIds(jobs[i]->GetMemTables());
|
||||
std::vector<BlobFileAddition> write_path_additions;
|
||||
std::vector<BlobFileGarbage> write_path_garbages;
|
||||
std::vector<std::vector<uint64_t>> generation_blob_file_numbers;
|
||||
mutex_.Unlock();
|
||||
s = mgr->PrepareFlushAdditions(
|
||||
write_options, prepared_blob_generations[i], &write_path_additions,
|
||||
write_options, prepared_blob_memtable_ids[i], &write_path_additions,
|
||||
&write_path_garbages, &generation_blob_file_numbers);
|
||||
mutex_.Lock();
|
||||
if (!s.ok()) {
|
||||
prepared_blob_generations[i] = 0;
|
||||
prepared_blob_memtable_ids[i].clear();
|
||||
break;
|
||||
}
|
||||
if (!write_path_additions.empty()) {
|
||||
@@ -714,10 +714,6 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
memtables[j]->ProtectSealedBlobFiles(mgr_handle,
|
||||
generation_blob_file_numbers[j]);
|
||||
}
|
||||
auto& sealed_blob_numbers = sealed_blob_numbers_by_cf[i];
|
||||
for (const auto& addition : write_path_additions) {
|
||||
sealed_blob_numbers.push_back(addition.GetBlobFileNumber());
|
||||
}
|
||||
jobs[i]->AddExternalBlobFileAdditions(std::move(write_path_additions));
|
||||
}
|
||||
if (!write_path_garbages.empty()) {
|
||||
@@ -987,7 +983,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
auto* mgr = cfds[i]->blob_partition_manager();
|
||||
if (mgr == nullptr || prepared_blob_generations[i] == 0) {
|
||||
if (mgr == nullptr || prepared_blob_memtable_ids[i].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -998,15 +994,11 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// Same retry semantics as the single-CF flush path above: do not
|
||||
// consume these prepared generations yet, because the rolled-back
|
||||
// memtables still need to reuse the same sealed blob files on retry.
|
||||
prepared_blob_generations[i] = 0;
|
||||
prepared_blob_memtable_ids[i].clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
mgr->CommitPreparedGenerations(prepared_blob_generations[i]);
|
||||
auto it = sealed_blob_numbers_by_cf.find(i);
|
||||
if (it != sealed_blob_numbers_by_cf.end() && !it->second.empty()) {
|
||||
mgr->RemoveFilePartitionMappings(it->second);
|
||||
}
|
||||
mgr->CommitFlushedGenerations(cfds[i]->imm()->GetEarliestMemTableID());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -1221,7 +1213,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
end_without_ts, "" /*trim_ts*/);
|
||||
}
|
||||
|
||||
std::string begin_str, end_str;
|
||||
std::string begin_str;
|
||||
std::string end_str;
|
||||
auto [begin, end] =
|
||||
MaybeAddTimestampsToRange(OptSlice::CopyFromPtr(begin_without_ts),
|
||||
OptSlice::CopyFromPtr(end_without_ts), ts_sz,
|
||||
@@ -1484,7 +1477,8 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
|
||||
// at L1 (or LBase), if applicable.
|
||||
int level = first_overlapped_level;
|
||||
final_output_level = level;
|
||||
int output_level = 0, base_level = 0;
|
||||
int output_level = 0;
|
||||
int base_level = 0;
|
||||
for (;;) {
|
||||
// Always allow L0 -> L1 compaction
|
||||
if (level > 0) {
|
||||
@@ -2446,7 +2440,8 @@ Status DBImpl::RunManualCompaction(
|
||||
assert(input_level == ColumnFamilyData::kCompactAllLevels ||
|
||||
input_level >= 0);
|
||||
|
||||
InternalKey begin_storage, end_storage;
|
||||
InternalKey begin_storage;
|
||||
InternalKey end_storage;
|
||||
CompactionArg* ca = nullptr;
|
||||
|
||||
bool scheduled = false;
|
||||
|
||||
@@ -24,7 +24,8 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
InternalKey start_key, end_key;
|
||||
InternalKey start_key;
|
||||
InternalKey end_key;
|
||||
if (begin != nullptr) {
|
||||
start_key.SetMinPossibleForUserKey(*begin);
|
||||
}
|
||||
|
||||
@@ -244,9 +244,14 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// Since job_context->min_pending_output is set, until file scan finishes,
|
||||
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
assert(job_context != nullptr);
|
||||
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
|
||||
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
|
||||
job_context->min_options_file_number = MinOptionsFileNumberToKeep();
|
||||
job_context->manifest_options_file_number =
|
||||
versions_->has_committed_options_file_number()
|
||||
? versions_->options_file_number()
|
||||
: 0;
|
||||
job_context->min_blob_file_number_to_keep =
|
||||
versions_->current_next_file_number();
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
@@ -730,7 +735,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
break;
|
||||
case kOptionsFile:
|
||||
keep = (number >= optsfile_num2);
|
||||
keep = (number >= optsfile_num2) ||
|
||||
(number == state.manifest_options_file_number);
|
||||
break;
|
||||
case kCompactionProgressFile:
|
||||
// Keep compaction progress files - they are managed
|
||||
@@ -1202,6 +1208,9 @@ Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (path == NormalizePath(dbname_)) {
|
||||
recovery_ctx->existing_db_files_ = files;
|
||||
}
|
||||
for (const auto& fname : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type;
|
||||
|
||||
@@ -36,7 +36,7 @@ DBImplFollower::DBImplFollower(const DBOptions& db_options,
|
||||
}
|
||||
|
||||
DBImplFollower::~DBImplFollower() {
|
||||
Status s = Close();
|
||||
Status s = DBImplFollower::Close();
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Error closing DB : %s",
|
||||
s.ToString().c_str());
|
||||
|
||||
@@ -19,7 +19,7 @@ class DBImplFollower : public DBImplSecondary {
|
||||
public:
|
||||
DBImplFollower(const DBOptions& db_options, std::unique_ptr<Env>&& env,
|
||||
const std::string& dbname, std::string src_path);
|
||||
~DBImplFollower();
|
||||
~DBImplFollower() override;
|
||||
|
||||
Status Close() override;
|
||||
|
||||
|
||||
+111
-46
@@ -307,7 +307,8 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
}
|
||||
|
||||
if (db_options.daily_offpeak_time_utc != "") {
|
||||
int start_time, end_time;
|
||||
int start_time;
|
||||
int end_time;
|
||||
if (!TryParseTimeRangeString(db_options.daily_offpeak_time_utc, start_time,
|
||||
end_time)) {
|
||||
return Status::InvalidArgument(
|
||||
@@ -603,6 +604,7 @@ Status DBImpl::Recover(
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (s.ok() && !read_only) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
const auto& moptions = cfd->GetLatestMutableCFOptions();
|
||||
@@ -729,6 +731,36 @@ Status DBImpl::Recover(
|
||||
if (s.ok() && !read_only) {
|
||||
s = MaybeUpdateNextFileNumber(recovery_ctx);
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
OptionsFileSelection selection;
|
||||
s = VersionSet::ResolveOptionsFileNumber(
|
||||
GetName(), immutable_db_options_.fs.get(),
|
||||
versions_->options_file_manifest_state(),
|
||||
recovery_ctx->existing_db_files_,
|
||||
immutable_db_options_.track_options_file_number_in_manifest ||
|
||||
versions_->has_committed_options_file_number(),
|
||||
&selection);
|
||||
if (s.ok() && !selection.selected_by_manifest) {
|
||||
versions_->ApplyLegacyOptionsFileNumber(selection.file_number);
|
||||
}
|
||||
if (s.ok() && selection.used_fallback) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"No usable committed OPTIONS file; using tracked "
|
||||
"OPTIONS-%06" PRIu64,
|
||||
selection.file_number);
|
||||
}
|
||||
if (s.ok() && immutable_db_options_.compaction_service == nullptr) {
|
||||
for (uint64_t number : selection.rejected_tracked_file_numbers) {
|
||||
Status delete_s = env_->DeleteFile(OptionsFileName(GetName(), number));
|
||||
if (!delete_s.ok() && !delete_s.IsNotFound()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to delete uncommitted OPTIONS-%06" PRIu64
|
||||
": %s",
|
||||
number, delete_s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && !read_only) {
|
||||
// TODO: share file descriptors (FSDirectory) with SetDirectories above
|
||||
@@ -881,10 +913,7 @@ Status DBImpl::Recover(
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
// If we are opening as read-only, we need to update options_file_number_
|
||||
// to reflect the most recent OPTIONS file. It does not matter for regular
|
||||
// read-write db instance because options_file_number_ will later be
|
||||
// updated to versions_->NewFileNumber() in RenameTempFileToOptionsFile.
|
||||
TEST_SYNC_POINT("DBImpl::Recover:BeforeReadOnlyLegacyOptionsDirectoryScan");
|
||||
std::vector<std::string> filenames;
|
||||
if (s.ok()) {
|
||||
const std::string normalized_dbname = NormalizePath(dbname_);
|
||||
@@ -902,18 +931,28 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
uint64_t number = 0;
|
||||
uint64_t options_file_number = 0;
|
||||
FileType type;
|
||||
for (const auto& fname : filenames) {
|
||||
if (ParseFileName(fname, &number, &type) && type == kOptionsFile) {
|
||||
options_file_number = std::max(number, options_file_number);
|
||||
}
|
||||
OptionsFileSelection selection;
|
||||
s = VersionSet::ResolveOptionsFileNumber(
|
||||
GetName(), immutable_db_options_.fs.get(),
|
||||
versions_->options_file_manifest_state(), filenames,
|
||||
immutable_db_options_.track_options_file_number_in_manifest ||
|
||||
versions_->has_committed_options_file_number(),
|
||||
&selection);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!selection.selected_by_manifest) {
|
||||
versions_->ApplyLegacyOptionsFileNumber(selection.file_number);
|
||||
}
|
||||
if (selection.used_fallback) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"No usable committed OPTIONS file; using tracked "
|
||||
"OPTIONS-%06" PRIu64,
|
||||
selection.file_number);
|
||||
}
|
||||
versions_->options_file_number_ = options_file_number;
|
||||
uint64_t options_file_size = 0;
|
||||
if (options_file_number > 0) {
|
||||
s = env_->GetFileSize(OptionsFileName(GetName(), options_file_number),
|
||||
if (selection.file_number > 0) {
|
||||
s = env_->GetFileSize(OptionsFileName(GetName(), selection.file_number),
|
||||
&options_file_size);
|
||||
}
|
||||
versions_->options_file_size_ = options_file_size;
|
||||
@@ -1985,12 +2024,12 @@ Status DBImpl::MaybeFlushFinalMemtableOrRestoreActiveLogFiles(
|
||||
if (status.ok()) {
|
||||
if (data_seen && !flushed) {
|
||||
status = RestoreAliveLogFiles(wal_numbers);
|
||||
} else if (!wal_numbers.empty()) { // If there's no data in the WAL, or we
|
||||
// flushed all the data, still
|
||||
} else if (!wal_numbers.empty() && !read_only &&
|
||||
fs_->IsPreallocationSupported()) {
|
||||
// If there's no data in the WAL, or we flushed all the data, still
|
||||
// truncate the log file. If the process goes into a crash loop before
|
||||
// the file is deleted, the preallocated space will never get freed.
|
||||
const bool truncate = !read_only;
|
||||
GetLogSizeAndMaybeTruncate(wal_numbers.back(), truncate, nullptr)
|
||||
GetLogSizeAndMaybeTruncate(wal_numbers.back(), /*truncate=*/true, nullptr)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
@@ -2066,6 +2105,7 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
wal_empty_ = false;
|
||||
uint64_t min_wal_with_unflushed_data =
|
||||
versions_->MinLogNumberWithUnflushedData();
|
||||
const bool preallocation_supported = fs_->IsPreallocationSupported();
|
||||
for (auto wal_number : wal_numbers) {
|
||||
if (!allow_2pc() && wal_number < min_wal_with_unflushed_data) {
|
||||
// In non-2pc mode, the WAL files not backing unflushed data are not
|
||||
@@ -2076,8 +2116,10 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
WalFileNumberSize log;
|
||||
s = GetLogSizeAndMaybeTruncate(
|
||||
wal_number, /*truncate=*/(wal_number == wal_numbers.back()), &log);
|
||||
s = GetLogSizeAndMaybeTruncate(wal_number,
|
||||
/*truncate=*/preallocation_supported &&
|
||||
(wal_number == wal_numbers.back()),
|
||||
&log);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -2774,7 +2816,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
if (recovered_seq != kMaxSequenceNumber) {
|
||||
WriteBatch empty_batch;
|
||||
WriteBatchInternal::SetSequence(&empty_batch, recovered_seq);
|
||||
uint64_t wal_used, log_size;
|
||||
uint64_t wal_used;
|
||||
uint64_t log_size;
|
||||
log::Writer* log_writer = impl->logs_.back().writer;
|
||||
WalFileNumberSize& wal_file_number_size = impl->alive_wal_files_.back();
|
||||
|
||||
@@ -2822,37 +2865,59 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
std::vector<ColumnFamilyDescriptor> missing_column_families;
|
||||
for (const auto& cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd != nullptr) {
|
||||
if (impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name) ==
|
||||
nullptr) {
|
||||
if (!db_options.create_missing_column_families) {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
missing_column_families.push_back(cf);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyHandle*> newly_created_handles;
|
||||
if (s.ok() && !missing_column_families.empty()) {
|
||||
impl->mutex_.Unlock();
|
||||
// NOTE: the work normally done in WrapUpCreateColumnFamilies is done
|
||||
// separately below. Publish one proposed OPTIONS snapshot for all
|
||||
// missing CFs so tracked open does not reintroduce quadratic I/O.
|
||||
s = impl->CreateColumnFamiliesImpl(read_options, write_options,
|
||||
missing_column_families,
|
||||
&newly_created_handles);
|
||||
impl->mutex_.Lock();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
handles->insert(handles->end(), newly_created_handles.begin(),
|
||||
newly_created_handles.end());
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ColumnFamilyHandle*>
|
||||
newly_created_handles_by_name;
|
||||
for (auto* handle : newly_created_handles) {
|
||||
auto* cfd = static_cast<ColumnFamilyHandleImpl*>(handle)->cfd();
|
||||
newly_created_handles_by_name.emplace(cfd->GetName(), handle);
|
||||
}
|
||||
|
||||
// Set column-family handles in the caller's descriptor order.
|
||||
for (const auto& cf : column_families) {
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
auto created = newly_created_handles_by_name.find(cf.name);
|
||||
if (created != newly_created_handles_by_name.end()) {
|
||||
handles->push_back(created->second);
|
||||
} else {
|
||||
auto* cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
assert(cfd != nullptr);
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl.get(), &impl->mutex_));
|
||||
impl->NewThreadStatusCfInfo(cfd);
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
impl->InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
sv_context.Clean();
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
impl->mutex_.Unlock();
|
||||
// NOTE: the work normally done in WrapUpCreateColumnFamilies will
|
||||
// be done separately below.
|
||||
// This includes InstallSuperVersionForConfigChange.
|
||||
s = impl->CreateColumnFamilyImpl(read_options, write_options,
|
||||
cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ class DBImplReadOnly : public DBImpl {
|
||||
// No copying allowed
|
||||
DBImplReadOnly(const DBImplReadOnly&) = delete;
|
||||
void operator=(const DBImplReadOnly&) = delete;
|
||||
DBImplReadOnly(DBImplReadOnly&&) = delete;
|
||||
void operator=(DBImplReadOnly&&) = delete;
|
||||
|
||||
virtual ~DBImplReadOnly();
|
||||
~DBImplReadOnly() override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DBImpl::GetImpl;
|
||||
|
||||
@@ -45,6 +45,11 @@ class LogReaderContainer {
|
||||
reporter, true /*checksum*/,
|
||||
log_number);
|
||||
}
|
||||
// No copying allowed
|
||||
LogReaderContainer(const LogReaderContainer&) = delete;
|
||||
LogReaderContainer& operator=(const LogReaderContainer&) = delete;
|
||||
LogReaderContainer(LogReaderContainer&&) = delete;
|
||||
LogReaderContainer& operator=(LogReaderContainer&&) = delete;
|
||||
log::FragmentBufferedReader* reader_;
|
||||
log::Reader::Reporter* reporter_;
|
||||
Status* status_;
|
||||
@@ -379,8 +384,10 @@ class DBImplSecondary : public DBImpl {
|
||||
std::vector<Iterator*>* iterators);
|
||||
|
||||
// No copying allowed
|
||||
DBImplSecondary(const DBImplSecondary&);
|
||||
void operator=(const DBImplSecondary&);
|
||||
DBImplSecondary(const DBImplSecondary&) = delete;
|
||||
void operator=(const DBImplSecondary&) = delete;
|
||||
DBImplSecondary(DBImplSecondary&&) = delete;
|
||||
void operator=(DBImplSecondary&&) = delete;
|
||||
|
||||
using DBImpl::Recover;
|
||||
|
||||
|
||||
@@ -3567,7 +3567,7 @@ Status DBImpl::SwitchMemtableImpl(ColumnFamilyData* cfd, WriteContext* context,
|
||||
}
|
||||
|
||||
if (cfd->blob_partition_manager() != nullptr) {
|
||||
cfd->blob_partition_manager()->RotateCurrentGeneration();
|
||||
cfd->blob_partition_manager()->RotateCurrentGeneration(cfd->mem()->GetID());
|
||||
}
|
||||
|
||||
cfd->mem()->SetNextLogNumber(cur_wal_number_);
|
||||
|
||||
@@ -30,7 +30,8 @@ void DumpDBFileSummary(const ImmutableDBOptions& options,
|
||||
std::vector<std::string> files;
|
||||
uint64_t file_num = 0;
|
||||
uint64_t file_size;
|
||||
std::string file_info, wal_info;
|
||||
std::string file_info;
|
||||
std::string wal_info;
|
||||
|
||||
Header(options.info_log, "DB SUMMARY\n");
|
||||
{
|
||||
|
||||
@@ -832,7 +832,7 @@ class DBIOCorruptionTest
|
||||
Reopen(options_);
|
||||
}
|
||||
|
||||
~DBIOCorruptionTest() {
|
||||
~DBIOCorruptionTest() override {
|
||||
Close();
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
+64
-13
@@ -96,6 +96,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
trace_cf_id_(cfd != nullptr ? cfd->GetID() : 0),
|
||||
has_trace_state_(db_impl != nullptr && cfd != nullptr),
|
||||
ingest_sst_lock_(cfd != nullptr ? &cfd->GetIngestSstLock() : nullptr),
|
||||
version_number_(version != nullptr ? version->GetVersionNumber() : 0),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
|
||||
@@ -117,13 +118,15 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
read_options.total_order_seek ||
|
||||
read_options.auto_prefix_mode),
|
||||
// Read-path range conversion assumes the scan can observe all interior
|
||||
// live keys. table_filter can hide whole SSTs, and timestamp filtering
|
||||
// live keys. table_filter can hide whole SSTs, read tiers other than
|
||||
// kReadAllTier can skip SSTs or uncached blocks, and timestamp filtering
|
||||
// can hide newer UDT versions unless the read is at max timestamp with no
|
||||
// lower timestamp bound. Legacy prefix iterators without
|
||||
// prefix_same_as_start do not guarantee complete scans, so conversion
|
||||
// must stay disabled for the iterator lifetime.
|
||||
min_tombstones_for_range_conversion_(
|
||||
active_mem != nullptr && !HasTableFilter(read_options) &&
|
||||
read_options.read_tier == kReadAllTier &&
|
||||
(expect_total_order_inner_iter_ || prefix_same_as_start_) &&
|
||||
HasFullTimestampVisibility(read_options)
|
||||
? mutable_cf_options.min_tombstones_for_range_conversion
|
||||
@@ -690,6 +693,14 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key) {
|
||||
// TODO(lth): If we reseek to sequence number greater than ikey_.sequence,
|
||||
// then it does not make sense to reseek as we would actually land further
|
||||
// away from the desired key. There is opportunity for optimization here.
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_skipped > max_skip_ && !reseek_done) {
|
||||
is_key_seqnum_zero_ = false;
|
||||
num_skipped = 0;
|
||||
@@ -735,14 +746,6 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key) {
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (aborted) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
} while (iter_.Valid());
|
||||
|
||||
// If we accumulated tombstones, use the last tracked tombstone as the
|
||||
@@ -790,7 +793,20 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
for (iter_.Next(); iter_.Valid(); iter_.Next()) {
|
||||
while (true) {
|
||||
// This could be a long-running operation due to merge operands, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_.Next();
|
||||
if (!iter_.Valid()) {
|
||||
break;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
@@ -1247,6 +1263,15 @@ bool DBIter::FindValueForCurrentKey(bool& found_visible) {
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_.Prev();
|
||||
++num_skipped;
|
||||
|
||||
@@ -1399,6 +1424,14 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
break;
|
||||
}
|
||||
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
@@ -1468,6 +1501,14 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
|
||||
while (true) {
|
||||
// This could be a long-running operation due to merge operands, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_.Next();
|
||||
|
||||
if (!iter_.Valid()) {
|
||||
@@ -1663,6 +1704,14 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
bool aborted = ROCKSDB_THREAD_YIELD_CHECK_ABORT();
|
||||
if (UNLIKELY(aborted)) {
|
||||
valid_ = false;
|
||||
status_ = Status::Aborted("Query abort.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (num_skipped >= max_skip_) {
|
||||
num_skipped = 0;
|
||||
std::string last_key;
|
||||
@@ -1785,7 +1834,7 @@ void DBIter::MaybeInsertRangeTombstone(const Slice& end_key) {
|
||||
assert(ingest_sst_lock_ != nullptr);
|
||||
if (active_mem_->AddLogicallyRedundantRangeTombstone(
|
||||
insert_seq, range_tomb_first_key_.GetUserKey(), end_key,
|
||||
*ingest_sst_lock_)) {
|
||||
version_number_, *ingest_sst_lock_)) {
|
||||
RecordTick(statistics_, READ_PATH_RANGE_TOMBSTONES_INSERTED);
|
||||
ROCKS_LOG_DEBUG(logger_,
|
||||
"Inserted range tombstone [%s, %s) @ seq %" PRIu64
|
||||
@@ -2019,7 +2068,8 @@ void DBIter::Seek(const Slice& target) {
|
||||
|
||||
if (has_trace_state_) {
|
||||
// TODO: What do we do if this returns an error?
|
||||
Slice lower_bound, upper_bound;
|
||||
Slice lower_bound;
|
||||
Slice upper_bound;
|
||||
if (iterate_lower_bound_ != nullptr) {
|
||||
lower_bound = *iterate_lower_bound_;
|
||||
} else {
|
||||
@@ -2140,7 +2190,8 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
|
||||
if (has_trace_state_) {
|
||||
// TODO: What do we do if this returns an error?
|
||||
Slice lower_bound, upper_bound;
|
||||
Slice lower_bound;
|
||||
Slice upper_bound;
|
||||
if (iterate_lower_bound_ != nullptr) {
|
||||
lower_bound = *iterate_lower_bound_;
|
||||
} else {
|
||||
|
||||
+10
-6
@@ -161,6 +161,8 @@ class DBIter final : public Iterator {
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&) = delete;
|
||||
void operator=(const DBIter&) = delete;
|
||||
DBIter(DBIter&&) = delete;
|
||||
void operator=(DBIter&&) = delete;
|
||||
|
||||
~DBIter() override {
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
@@ -239,14 +241,14 @@ class DBIter final : public Iterator {
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
|
||||
void Next() final override;
|
||||
void Prev() final override;
|
||||
void Next() final;
|
||||
void Prev() final;
|
||||
// 'target' does not contain timestamp, even if user timestamp feature is
|
||||
// enabled.
|
||||
void Seek(const Slice& target) final override;
|
||||
void SeekForPrev(const Slice& target) final override;
|
||||
void SeekToFirst() final override;
|
||||
void SeekToLast() final override;
|
||||
void Seek(const Slice& target) final;
|
||||
void SeekForPrev(const Slice& target) final;
|
||||
void SeekToFirst() final;
|
||||
void SeekToLast() final;
|
||||
Env* env() const { return env_; }
|
||||
void set_sequence(uint64_t s) {
|
||||
sequence_ = s;
|
||||
@@ -711,6 +713,8 @@ class DBIter final : public Iterator {
|
||||
uint32_t trace_cf_id_;
|
||||
bool has_trace_state_;
|
||||
port::RWMutex* ingest_sst_lock_;
|
||||
// Number of the Version this iterator reads, or 0 if unknown.
|
||||
const uint64_t version_number_;
|
||||
const Slice* const timestamp_ub_;
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
|
||||
+707
-4
@@ -29,6 +29,43 @@
|
||||
#include "util/random.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
|
||||
#ifdef OS_LINUX
|
||||
namespace {
|
||||
struct QueryAbortState {
|
||||
int check_count = 0;
|
||||
int abort_after_check_count = -1;
|
||||
};
|
||||
|
||||
QueryAbortState& GetQueryAbortState() {
|
||||
static thread_local QueryAbortState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
class ScopedQueryAbort {
|
||||
public:
|
||||
explicit ScopedQueryAbort(int abort_after_check_count) {
|
||||
QueryAbortState& state = GetQueryAbortState();
|
||||
state.check_count = 0;
|
||||
state.abort_after_check_count = abort_after_check_count;
|
||||
}
|
||||
|
||||
~ScopedQueryAbort() { GetQueryAbortState().abort_after_check_count = -1; }
|
||||
|
||||
int check_count() const { return GetQueryAbortState().check_count; }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" bool RocksDbThreadYieldAndCheckAbort() {
|
||||
QueryAbortState& state = GetQueryAbortState();
|
||||
if (state.abort_after_check_count < 0) {
|
||||
return false;
|
||||
}
|
||||
++state.check_count;
|
||||
return state.abort_after_check_count > 0 &&
|
||||
state.check_count >= state.abort_after_check_count;
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A dumb ReadCallback which saying every key is committed.
|
||||
@@ -45,6 +82,100 @@ class DBIteratorBaseTest : public DBTestBase {
|
||||
: DBTestBase("db_iterator_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
#ifdef OS_LINUX
|
||||
TEST_F(DBIteratorBaseTest, PrevChecksQueryAbort) {
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
ASSERT_OK(Put("b", "vb"));
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
|
||||
for (int abort_after_check_count = 1; abort_after_check_count <= 2;
|
||||
++abort_after_check_count) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("c");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ScopedQueryAbort abort(abort_after_check_count);
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsAborted());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorBaseTest, ForwardMergeChecksQueryAbort) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("b", "base"));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "b", "operand"));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/0);
|
||||
iter->Seek("b");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), "b");
|
||||
ASSERT_EQ(iter->value(), "base,operand,operand,operand,operand");
|
||||
ASSERT_EQ(abort.check_count(), 4);
|
||||
}
|
||||
|
||||
iter.reset(db_->NewIterator(ReadOptions()));
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/3);
|
||||
iter->Seek("b");
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsAborted());
|
||||
ASSERT_EQ(abort.check_count(), 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorBaseTest, PrevReseekMergeChecksQueryAbort) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 2;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
ASSERT_OK(Put("b", "base"));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "b", "operand"));
|
||||
}
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("c");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/0);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), "b");
|
||||
// One check reaches "b", two trigger the reseek, four scan merge
|
||||
// operands, and three finish positioning before "b".
|
||||
ASSERT_EQ(abort.check_count(), 10);
|
||||
}
|
||||
|
||||
iter.reset(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("c");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/4);
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsAborted());
|
||||
ASSERT_EQ(abort.check_count(), 4);
|
||||
}
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
TEST_F(DBIteratorBaseTest, APICallsWithPerfContext) {
|
||||
// Set up the DB
|
||||
Options options = CurrentOptions();
|
||||
@@ -572,7 +703,8 @@ TEST_P(DBIteratorTest, PersistedTierOnIterator) {
|
||||
|
||||
TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
do {
|
||||
ReadOptions non_blocking_opts, regular_opts;
|
||||
ReadOptions non_blocking_opts;
|
||||
ReadOptions regular_opts;
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.full_block_cache = true;
|
||||
Options options = CurrentOptions(options_override);
|
||||
@@ -3828,6 +3960,71 @@ INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest,
|
||||
// Tests how DBIter work with ReadCallback
|
||||
class DBIteratorWithReadCallbackTest : public DBIteratorTest {};
|
||||
|
||||
#ifdef OS_LINUX
|
||||
TEST_F(DBIteratorWithReadCallbackTest,
|
||||
PrevReseekVisibilityScanChecksQueryAbort) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber max_visible_seq)
|
||||
: ReadCallback(max_visible_seq) {}
|
||||
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return seq <= max_visible_seq_;
|
||||
}
|
||||
};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 2;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(Put("b", "visible"));
|
||||
}
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
TestReadCallback callback(db_->GetLatestSequenceNumber());
|
||||
ASSERT_OK(Put("b", "hidden"));
|
||||
|
||||
auto* cfh = static_cast_with_check<ColumnFamilyHandleImpl>(
|
||||
db_->DefaultColumnFamily());
|
||||
auto* cfd = cfh->cfd();
|
||||
DBImpl* db_impl = dbfull();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(db_impl);
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_impl->NewIteratorImpl(ReadOptions(), cfh, super_version,
|
||||
db_->GetLatestSequenceNumber(), &callback));
|
||||
iter->Seek("c");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/0);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), "b");
|
||||
// One check reaches "b", two trigger the reseek, one skips the invisible
|
||||
// value, and two finish positioning before "b".
|
||||
ASSERT_EQ(abort.check_count(), 6);
|
||||
}
|
||||
|
||||
super_version = cfd->GetReferencedSuperVersion(db_impl);
|
||||
iter.reset(db_impl->NewIteratorImpl(ReadOptions(), cfh, super_version,
|
||||
db_->GetLatestSequenceNumber(),
|
||||
&callback));
|
||||
iter->Seek("c");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
{
|
||||
ScopedQueryAbort abort(/*abort_after_check_count=*/4);
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsAborted());
|
||||
ASSERT_EQ(abort.check_count(), 4);
|
||||
}
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
@@ -4073,7 +4270,9 @@ TEST_F(DBIteratorTest, ErrorWhenReadFile) {
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
// Get file names for F1, F2 and F3.
|
||||
// These are file names, not full paths.
|
||||
std::string f1, f2, f3;
|
||||
std::string f1;
|
||||
std::string f2;
|
||||
std::string f3;
|
||||
for (auto& file_meta : files) {
|
||||
if (file_meta.level == 6) {
|
||||
f3 = file_meta.name;
|
||||
@@ -7356,8 +7555,9 @@ TEST_P(ReadPathRangeTombstoneTest, NoInsertionOnBlockCacheTierIncomplete) {
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
// No range tombstone should have been inserted despite meeting threshold,
|
||||
// because the iterator terminated with Incomplete (cache miss).
|
||||
// No range tombstone should have been inserted despite meeting threshold:
|
||||
// conversion is disabled for kBlockCacheTier, and the iterator terminated
|
||||
// with Incomplete (cache miss).
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 0);
|
||||
ASSERT_EQ(
|
||||
options.statistics->getTickerCount(READ_PATH_RANGE_TOMBSTONES_INSERTED),
|
||||
@@ -7371,6 +7571,154 @@ TEST_P(ReadPathRangeTombstoneTest, NoInsertionOnBlockCacheTierIncomplete) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ReadPathRangeTombstoneTest, NoInsertionFromMemtableTierIterator) {
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// "c" is live and only exists in an SST file, which a kMemtableTier
|
||||
// iterator does not read.
|
||||
ASSERT_OK(Put("c", "vc"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("a", "va"));
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
ASSERT_OK(Put("e", "ve"));
|
||||
|
||||
{
|
||||
ReadOptions ro;
|
||||
ro.read_tier = kMemtableTier;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
std::vector<std::string> seen;
|
||||
if (Forward()) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
seen.push_back(iter->key().ToString());
|
||||
}
|
||||
} else {
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
seen.insert(seen.begin(), iter->key().ToString());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(seen, std::vector<std::string>({"a", "e"}));
|
||||
}
|
||||
|
||||
ASSERT_EQ(Get("c"), "vc");
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 0);
|
||||
ASSERT_EQ(
|
||||
options.statistics->getTickerCount(READ_PATH_RANGE_TOMBSTONES_INSERTED),
|
||||
0);
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(Get("c"), "vc");
|
||||
}
|
||||
|
||||
TEST_P(ReadPathRangeTombstoneTest, PartialTierReadsAfterConversion) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
std::shared_ptr<Cache> block_cache = NewLRUCache(1 << 20);
|
||||
table_options.block_cache = block_cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
// Conversion starts disabled so the reads recorded before it do not insert.
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (char c = 'a'; c <= 'h'; c++) {
|
||||
ASSERT_OK(Put(std::string(1, c), std::string("v") + c));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("c"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "va2"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
ASSERT_OK(Put("e", "ve2"));
|
||||
ASSERT_OK(Delete("e"));
|
||||
|
||||
auto scan = [&](ReadTier tier) {
|
||||
ReadOptions ro;
|
||||
ro.read_tier = tier;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
std::vector<std::string> out;
|
||||
if (Forward()) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
out.push_back(iter->key().ToString() + "=" + iter->value().ToString());
|
||||
}
|
||||
} else {
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
out.insert(out.begin(),
|
||||
iter->key().ToString() + "=" + iter->value().ToString());
|
||||
}
|
||||
}
|
||||
out.push_back(iter->status().ToString());
|
||||
return out;
|
||||
};
|
||||
// kMemtableTier only affects iterators, so point reads compare
|
||||
// kBlockCacheTier with kReadAllTier.
|
||||
auto gets = [&](ReadTier tier) {
|
||||
ReadOptions ro;
|
||||
ro.read_tier = tier;
|
||||
std::vector<std::string> out;
|
||||
for (char c = 'a'; c <= 'h'; c++) {
|
||||
std::string value;
|
||||
Status s = db_->Get(ro, std::string(1, c), &value);
|
||||
out.push_back(std::string(1, c) + ":" + (s.ok() ? value : s.ToString()));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const auto cold_cache_scan_before = scan(kBlockCacheTier);
|
||||
const auto cold_cache_gets_before = gets(kBlockCacheTier);
|
||||
const std::string incomplete =
|
||||
Status::Incomplete("no blocking io").ToString();
|
||||
ASSERT_EQ(cold_cache_scan_before.back(), incomplete);
|
||||
ASSERT_EQ(cold_cache_gets_before[1], "b:" + incomplete);
|
||||
ASSERT_EQ(cold_cache_gets_before[2], "c:" + incomplete);
|
||||
|
||||
// A full scan with conversion disabled reads every block into the cache.
|
||||
const std::vector<std::string> full_view = {"a=va2", "f=vf", "g=vg", "h=vh",
|
||||
"OK"};
|
||||
ASSERT_EQ(scan(kReadAllTier), full_view);
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 0);
|
||||
|
||||
const auto mem_scan_before = scan(kMemtableTier);
|
||||
ASSERT_EQ(mem_scan_before, std::vector<std::string>({"a=va2", "OK"}));
|
||||
const auto warm_cache_scan_before = scan(kBlockCacheTier);
|
||||
ASSERT_EQ(warm_cache_scan_before, full_view);
|
||||
const auto warm_cache_gets_before = gets(kBlockCacheTier);
|
||||
const auto full_gets = gets(kReadAllTier);
|
||||
ASSERT_EQ(warm_cache_gets_before, full_gets);
|
||||
|
||||
ASSERT_OK(db_->SetOptions({{"min_tombstones_for_range_conversion", "2"}}));
|
||||
// A complete kBlockCacheTier scan does not convert. Only kReadAllTier does.
|
||||
ASSERT_EQ(scan(kBlockCacheTier), warm_cache_scan_before);
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 0);
|
||||
ASSERT_EQ(scan(kReadAllTier), full_view);
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 1);
|
||||
AssertRange(0, "b", "f");
|
||||
ASSERT_EQ(
|
||||
options.statistics->getTickerCount(READ_PATH_RANGE_TOMBSTONES_INSERTED),
|
||||
1);
|
||||
|
||||
ASSERT_EQ(scan(kMemtableTier), mem_scan_before);
|
||||
ASSERT_EQ(scan(kBlockCacheTier), warm_cache_scan_before);
|
||||
ASSERT_EQ(gets(kBlockCacheTier), warm_cache_gets_before);
|
||||
|
||||
// With a cold cache, Gets of the covered keys "b" and "c" now stop at the
|
||||
// memtable range tombstone and return the full-tier NotFound instead of
|
||||
// Incomplete. No read returns a different value.
|
||||
block_cache->EraseUnRefEntries();
|
||||
ASSERT_EQ(scan(kBlockCacheTier), cold_cache_scan_before);
|
||||
auto expected_cold_cache_gets = cold_cache_gets_before;
|
||||
expected_cold_cache_gets[1] = full_gets[1];
|
||||
expected_cold_cache_gets[2] = full_gets[2];
|
||||
ASSERT_EQ(gets(kBlockCacheTier), expected_cold_cache_gets);
|
||||
}
|
||||
|
||||
TEST_P(ReadPathRangeTombstoneTest, SkipInsertionWhenCoveredByExistingRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 4;
|
||||
@@ -8066,6 +8414,361 @@ TEST_P(ReadPathRangeTombstoneTest,
|
||||
db_->ReleaseSnapshot(snap);
|
||||
}
|
||||
|
||||
// An ingested file that gets no new sequence number is invisible to an
|
||||
// iterator created before the ingestion. That iterator must not convert the
|
||||
// deleted keys around the file into a range tombstone that covers it.
|
||||
TEST_P(ReadPathRangeTombstoneTest, IngestedFileSurvivesOlderIterator) {
|
||||
enum class IngestMode { kSeqnoZero, kIngestBehind, kDbGenerated };
|
||||
for (IngestMode mode : {IngestMode::kSeqnoZero, IngestMode::kIngestBehind,
|
||||
IngestMode::kDbGenerated}) {
|
||||
SCOPED_TRACE("ingest mode " + std::to_string(static_cast<int>(mode)));
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
options.cf_allow_ingest_behind = mode == IngestMode::kIngestBehind;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string file = dbname_ + "/ingest_c.sst";
|
||||
SequenceNumber expected_file_seqno = 0;
|
||||
if (mode == IngestMode::kDbGenerated) {
|
||||
const std::string src_path = dbname_ + "_src";
|
||||
ASSERT_OK(DestroyDB(src_path, options));
|
||||
std::unique_ptr<DB> src;
|
||||
ASSERT_OK(DB::Open(options, src_path, &src));
|
||||
ASSERT_OK(src->Put(WriteOptions(), "c", "vc"));
|
||||
ASSERT_OK(src->Flush(FlushOptions()));
|
||||
std::vector<LiveFileMetaData> src_files;
|
||||
src->GetLiveFilesMetaData(&src_files);
|
||||
ASSERT_EQ(src_files.size(), 1);
|
||||
expected_file_seqno = src_files[0].largest_seqno;
|
||||
ASSERT_GT(expected_file_seqno, 0);
|
||||
file = src_files[0].directory + "/" + src_files[0].relative_filename;
|
||||
ASSERT_OK(src->Close());
|
||||
} else {
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file));
|
||||
ASSERT_OK(writer.Put("c", "vc"));
|
||||
ASSERT_OK(writer.Finish());
|
||||
}
|
||||
|
||||
for (const char* key : {"a", "b", "d", "e", "f", "g"}) {
|
||||
ASSERT_OK(Put(key, std::string("v") + key));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
// The iterator sequence number must be above the DB-generated file's.
|
||||
ASSERT_OK(Delete("f"));
|
||||
ASSERT_GT(db_->GetLatestSequenceNumber(), expected_file_seqno);
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
|
||||
// "c" falls between the deleted keys and overlaps no key in the DB.
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.ingest_behind = mode == IngestMode::kIngestBehind;
|
||||
ifo.allow_db_generated_files = mode == IngestMode::kDbGenerated;
|
||||
ASSERT_OK(db_->IngestExternalFile({file}, ifo));
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
bool found = false;
|
||||
for (const auto& f : files) {
|
||||
if (f.smallestkey == "c") {
|
||||
found = true;
|
||||
ASSERT_EQ(f.level, options.num_levels - 1);
|
||||
ASSERT_EQ(f.largest_seqno, expected_file_seqno);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ(Get("c"), "vc");
|
||||
|
||||
// The iterator predates the ingestion, so it sees b and d as one run of
|
||||
// deleted keys.
|
||||
attempted_insert_ranges_.clear();
|
||||
if (Forward()) {
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "a");
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "e");
|
||||
} else {
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "g");
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "e");
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "a");
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
iter.reset();
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 1);
|
||||
AssertRange(0, "b", "e");
|
||||
|
||||
EXPECT_EQ(Get("c"), "vc");
|
||||
VerifyIteration({"a", "c", "e", "g"});
|
||||
ASSERT_OK(Flush());
|
||||
Reopen(options);
|
||||
EXPECT_EQ(Get("c"), "vc");
|
||||
}
|
||||
}
|
||||
|
||||
// NewIterator() references the SuperVersion before it reads the sequence
|
||||
// number. An ingestion plus a write in between gives the iterator a sequence
|
||||
// number above the ingested files' while its SuperVersion lacks them. It must
|
||||
// not convert the deleted keys around those files into a range tombstone.
|
||||
TEST_P(ReadPathRangeTombstoneTest,
|
||||
IngestedFileSurvivesIteratorWithOlderSuperVersion) {
|
||||
for (bool new_seqno : {false, true}) {
|
||||
SCOPED_TRACE(new_seqno ? "ingested with a new seqno"
|
||||
: "ingested with seqno zero");
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (const char* key : {"a", "b", "d", "e"}) {
|
||||
ASSERT_OK(Put(key, std::string("v") + key));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
// The memtable stays empty, so the ingestion does not switch it.
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// "c" alone overlaps no key, so it gets seqno zero. Adding "b" overlaps
|
||||
// the deleted "b" in L0, so the file gets a new seqno.
|
||||
const std::string file = dbname_ + "/ingest.sst";
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file));
|
||||
if (new_seqno) {
|
||||
ASSERT_OK(writer.Put("b", "vb2"));
|
||||
}
|
||||
ASSERT_OK(writer.Put("c", "vc"));
|
||||
ASSERT_OK(writer.Finish());
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::NewIterator:1",
|
||||
"ReadPathRangeTombstoneTest::OlderSuperVersion:Ingest"},
|
||||
{"ReadPathRangeTombstoneTest::OlderSuperVersion:Written",
|
||||
"DBImpl::NewIterator:2"}});
|
||||
std::unique_ptr<Iterator> iter;
|
||||
port::Thread reader([&]() { iter.reset(db_->NewIterator(ReadOptions())); });
|
||||
TEST_SYNC_POINT("ReadPathRangeTombstoneTest::OlderSuperVersion:Ingest");
|
||||
ASSERT_OK(db_->IngestExternalFile({file}, IngestExternalFileOptions()));
|
||||
ASSERT_OK(Put("z", "vz"));
|
||||
TEST_SYNC_POINT("ReadPathRangeTombstoneTest::OlderSuperVersion:Written");
|
||||
reader.join();
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
SyncPoint::GetInstance()->LoadDependency({});
|
||||
|
||||
std::vector<LiveFileMetaData> files;
|
||||
db_->GetLiveFilesMetaData(&files);
|
||||
bool found = false;
|
||||
for (const auto& f : files) {
|
||||
if (f.largestkey == "c") {
|
||||
found = true;
|
||||
ASSERT_EQ(f.largest_seqno > 0, new_seqno);
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found);
|
||||
|
||||
// The iterator reads past the write of "z" but not the ingested file.
|
||||
attempted_insert_ranges_.clear();
|
||||
std::vector<std::string> seen;
|
||||
if (Forward()) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
seen.push_back(iter->key().ToString());
|
||||
}
|
||||
} else {
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
seen.insert(seen.begin(), iter->key().ToString());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(seen, std::vector<std::string>({"a", "e", "z"}));
|
||||
iter.reset();
|
||||
ASSERT_EQ(attempted_insert_ranges_.size(), 1);
|
||||
AssertRange(0, "b", "e");
|
||||
|
||||
EXPECT_EQ(Get("c"), "vc");
|
||||
EXPECT_EQ(Get("b"), new_seqno ? "vb2" : "NOT_FOUND");
|
||||
ASSERT_OK(Flush());
|
||||
Reopen(options);
|
||||
EXPECT_EQ(Get("c"), "vc");
|
||||
}
|
||||
}
|
||||
|
||||
// One IngestExternalFiles() call installs files in two column families. Each
|
||||
// column family's older iterator must refuse its conversion.
|
||||
TEST_P(ReadPathRangeTombstoneTest, AtomicIngestionSurvivesOlderIterators) {
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<std::string> files;
|
||||
for (int cf = 0; cf < 2; cf++) {
|
||||
files.push_back(dbname_ + "/ingest_c" + std::to_string(cf) + ".sst");
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(files.back()));
|
||||
ASSERT_OK(writer.Put("c", "vc"));
|
||||
ASSERT_OK(writer.Finish());
|
||||
for (const char* key : {"a", "b", "d", "e"}) {
|
||||
ASSERT_OK(Put(cf, key, std::string("v") + key));
|
||||
}
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(Delete(cf, "b"));
|
||||
ASSERT_OK(Delete(cf, "d"));
|
||||
}
|
||||
std::unique_ptr<Iterator> it0(db_->NewIterator(ReadOptions(), handles_[0]));
|
||||
std::unique_ptr<Iterator> it1(db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
std::vector<IngestExternalFileArg> args(2);
|
||||
for (int cf = 0; cf < 2; cf++) {
|
||||
args[cf].column_family = handles_[cf];
|
||||
args[cf].external_files = {files[cf]};
|
||||
}
|
||||
ASSERT_OK(db_->IngestExternalFiles(args));
|
||||
|
||||
attempted_insert_ranges_.clear();
|
||||
for (auto* it : {it0.get(), it1.get()}) {
|
||||
if (Forward()) {
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
}
|
||||
} else {
|
||||
for (it->SeekToLast(); it->Valid(); it->Prev()) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
}
|
||||
it0.reset();
|
||||
it1.reset();
|
||||
EXPECT_EQ(attempted_insert_ranges_.size(), 2);
|
||||
EXPECT_EQ(Get(0, "c"), "vc");
|
||||
EXPECT_EQ(Get(1, "c"), "vc");
|
||||
}
|
||||
|
||||
// An iterator created after an ingestion sees the ingested files, so the
|
||||
// barrier must still let it convert.
|
||||
TEST_P(ReadPathRangeTombstoneTest, IteratorCreatedAfterIngestionConverts) {
|
||||
for (bool seqno_zero : {true, false}) {
|
||||
SCOPED_TRACE(seqno_zero ? "ingested with seqno zero"
|
||||
: "ingested with a new seqno");
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// "x" alone overlaps no key, so it gets seqno zero. Adding "e" overlaps
|
||||
// the flushed "e", so the file gets a new seqno.
|
||||
const std::string file = dbname_ + "/ingest_x.sst";
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file));
|
||||
if (!seqno_zero) {
|
||||
ASSERT_OK(writer.Put("e", "ve2"));
|
||||
}
|
||||
ASSERT_OK(writer.Put("x", "vx"));
|
||||
ASSERT_OK(writer.Finish());
|
||||
for (const char* key : {"a", "b", "c", "d", "e"}) {
|
||||
ASSERT_OK(Put(key, std::string("v") + key));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("c"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_blocking_flush = false;
|
||||
ASSERT_OK(db_->IngestExternalFile({file}, ifo));
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
}
|
||||
EXPECT_EQ(TestGetTickerCount(options, READ_PATH_RANGE_TOMBSTONES_INSERTED),
|
||||
1);
|
||||
VerifyIteration({"a", "e", "x"});
|
||||
EXPECT_EQ(Get("e"), seqno_zero ? "ve" : "ve2");
|
||||
}
|
||||
}
|
||||
|
||||
// A conversion that runs before an ingestion leaves a range tombstone in the
|
||||
// memtable over the gap where the file lands. The ingestion must either fail
|
||||
// or make the file visible, whatever the ingestion options.
|
||||
TEST_P(ReadPathRangeTombstoneTest, IngestionAfterConversionIsNeverHidden) {
|
||||
enum class IngestMode {
|
||||
kNoGlobalSeqnoNoFlush,
|
||||
kNoGlobalSeqno,
|
||||
kGlobalSeqno,
|
||||
kDbGenerated
|
||||
};
|
||||
for (IngestMode mode :
|
||||
{IngestMode::kNoGlobalSeqnoNoFlush, IngestMode::kNoGlobalSeqno,
|
||||
IngestMode::kGlobalSeqno, IngestMode::kDbGenerated}) {
|
||||
SCOPED_TRACE("ingest mode " + std::to_string(static_cast<int>(mode)));
|
||||
Options options = CurrentOptions();
|
||||
options.min_tombstones_for_range_conversion = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string file = dbname_ + "/ingest_c.sst";
|
||||
if (mode == IngestMode::kDbGenerated) {
|
||||
const std::string src_path = dbname_ + "_src";
|
||||
ASSERT_OK(DestroyDB(src_path, options));
|
||||
std::unique_ptr<DB> src;
|
||||
ASSERT_OK(DB::Open(options, src_path, &src));
|
||||
ASSERT_OK(src->Put(WriteOptions(), "c", "vc"));
|
||||
ASSERT_OK(src->Flush(FlushOptions()));
|
||||
std::vector<LiveFileMetaData> src_files;
|
||||
src->GetLiveFilesMetaData(&src_files);
|
||||
ASSERT_EQ(src_files.size(), 1);
|
||||
file = src_files[0].directory + "/" + src_files[0].relative_filename;
|
||||
ASSERT_OK(src->Close());
|
||||
} else {
|
||||
SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(file));
|
||||
ASSERT_OK(writer.Put("c", "vc"));
|
||||
ASSERT_OK(writer.Finish());
|
||||
}
|
||||
|
||||
for (const char* key : {"a", "b", "d", "e"}) {
|
||||
ASSERT_OK(Put(key, std::string("v") + key));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Delete("d"));
|
||||
attempted_insert_ranges_.clear();
|
||||
VerifyIteration({"a", "e"});
|
||||
ASSERT_EQ(TestGetTickerCount(options, READ_PATH_RANGE_TOMBSTONES_INSERTED),
|
||||
1);
|
||||
AssertRange(0, "b", "e");
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = mode == IngestMode::kGlobalSeqno;
|
||||
ifo.allow_blocking_flush = mode != IngestMode::kNoGlobalSeqnoNoFlush;
|
||||
ifo.allow_db_generated_files = mode == IngestMode::kDbGenerated;
|
||||
Status s = db_->IngestExternalFile({file}, ifo);
|
||||
if (mode == IngestMode::kGlobalSeqno) {
|
||||
ASSERT_OK(s);
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsInvalidArgument()) << s.ToString();
|
||||
}
|
||||
|
||||
const std::string expected_c = s.ok() ? "vc" : "NOT_FOUND";
|
||||
EXPECT_EQ(Get("c"), expected_c);
|
||||
VerifyIteration(s.ok() ? std::vector<std::string>{"a", "c", "e"}
|
||||
: std::vector<std::string>{"a", "e"});
|
||||
ASSERT_OK(Flush());
|
||||
Reopen(options);
|
||||
EXPECT_EQ(Get("c"), expected_c);
|
||||
}
|
||||
}
|
||||
|
||||
// SeekForPrev uses the reverse path: PrevInternal -> FindValueForCurrentKey.
|
||||
// When a key has more versions than max_sequential_skip_in_iterations,
|
||||
// FindValueForCurrentKey delegates to FindValueForCurrentKeyUsingSeek which
|
||||
|
||||
@@ -582,7 +582,8 @@ TEST_F(DBMergeOperandTest, GetMergeOperandsBaseDeletionInImmMem) {
|
||||
merge_operands_info.expected_max_number_of_operands =
|
||||
static_cast<int>(values.size());
|
||||
|
||||
std::string key = "k1", from_db;
|
||||
std::string key = "k1";
|
||||
std::string from_db;
|
||||
int number_of_operands = 0;
|
||||
ASSERT_OK(db_->GetMergeOperands(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
key, values.data(), &merge_operands_info,
|
||||
|
||||
@@ -125,7 +125,9 @@ TEST_F(DBPropertiesTest, Empty) {
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, CurrentVersionNumber) {
|
||||
uint64_t v1, v2, v3;
|
||||
uint64_t v1;
|
||||
uint64_t v2;
|
||||
uint64_t v3;
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.current-super-version-number", &v1));
|
||||
ASSERT_OK(Put("12345678", ""));
|
||||
@@ -587,7 +589,9 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
std::string level_tp_strings[kMaxLevel];
|
||||
std::string tp_string;
|
||||
TableProperties level_tps[kMaxLevel];
|
||||
TableProperties tp, sum_tp, expected_tp;
|
||||
TableProperties tp;
|
||||
TableProperties sum_tp;
|
||||
TableProperties expected_tp;
|
||||
for (int table = 1; table <= kTableCount; ++table) {
|
||||
for (int i = 0; i < kPutsPerTable; ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), rnd.RandomString(kKeySize),
|
||||
|
||||
+55
-22
@@ -113,7 +113,9 @@ TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
// regression test for exactly filled compaction output files. Previously
|
||||
// another file would be generated containing all range deletions, which
|
||||
// could invalidate the non-overlapping file boundary invariant.
|
||||
const int kNumPerFile = 4, kNumFiles = 2, kFileBytes = 9 << 10;
|
||||
const int kNumPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
const int kFileBytes = 9 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
@@ -159,7 +161,9 @@ TEST_F(DBRangeDelTest, MaxCompactionBytesCutsOutputFiles) {
|
||||
// Ensures range deletion spanning multiple compaction output files that are
|
||||
// cut by max_compaction_bytes will have non-overlapping key-ranges.
|
||||
// https://github.com/facebook/rocksdb/issues/1778
|
||||
const int kNumFiles = 2, kNumPerFile = 1 << 8, kBytesPerVal = 1 << 12;
|
||||
const int kNumFiles = 2;
|
||||
const int kNumPerFile = 1 << 8;
|
||||
const int kBytesPerVal = 1 << 12;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.disable_auto_compactions = true;
|
||||
@@ -297,7 +301,9 @@ TEST_F(DBRangeDelTest, CompactRangeDelsSameStartKey) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) {
|
||||
const int kNum = 300, kRangeBegin = 50, kRangeEnd = 250;
|
||||
const int kNum = 300;
|
||||
const int kRangeBegin = 50;
|
||||
const int kRangeEnd = 250;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
DestroyAndReopen(opts);
|
||||
@@ -332,7 +338,8 @@ TEST_F(DBRangeDelTest, FlushRemovesCoveredKeys) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) {
|
||||
const int kNumPerFile = 100, kNumFiles = 4;
|
||||
const int kNumPerFile = 100;
|
||||
const int kNumFiles = 4;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.disable_auto_compactions = true;
|
||||
@@ -388,7 +395,9 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredKeys) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, ValidLevelSubcompactionBoundaries) {
|
||||
const int kNumPerFile = 100, kNumFiles = 4, kFileBytes = 100 << 10;
|
||||
const int kNumPerFile = 100;
|
||||
const int kNumFiles = 4;
|
||||
const int kFileBytes = 100 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumFiles;
|
||||
@@ -446,7 +455,9 @@ TEST_F(DBRangeDelTest, ValidLevelSubcompactionBoundaries) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
const int kNumPerFile = 100, kFilesPerLevel = 4, kNumLevels = 4;
|
||||
const int kNumPerFile = 100;
|
||||
const int kFilesPerLevel = 4;
|
||||
const int kNumLevels = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_options_universal.min_merge_width = kFilesPerLevel;
|
||||
options.compaction_options_universal.max_merge_width = kFilesPerLevel;
|
||||
@@ -513,7 +524,8 @@ TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
const int kNumPerFile = 3, kNumFiles = 3;
|
||||
const int kNumPerFile = 3;
|
||||
const int kNumFiles = 3;
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(2 * kNumPerFile));
|
||||
@@ -542,7 +554,8 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
|
||||
ReadOptions read_opts;
|
||||
read_opts.ignore_range_deletions = true;
|
||||
std::string expected, actual;
|
||||
std::string expected;
|
||||
std::string actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 45); // 1+2+...+9
|
||||
ASSERT_EQ(expected, actual);
|
||||
@@ -576,7 +589,8 @@ TEST_F(DBRangeDelTest, PutDeleteRangeMergeFlush) {
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string expected, actual;
|
||||
std::string expected;
|
||||
std::string actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 1);
|
||||
ASSERT_EQ(expected, actual);
|
||||
@@ -633,7 +647,10 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) {
|
||||
// aggregator. So, subsequent calls to Next() must be able to use these
|
||||
// deletions to decide whether a key is covered. This will work as long as
|
||||
// the aggregator properly references the range deletion block.
|
||||
const int kNum = 25, kRangeBegin = 0, kRangeEnd = 7, kNumRanges = 5;
|
||||
const int kNum = 25;
|
||||
const int kRangeBegin = 0;
|
||||
const int kRangeEnd = 7;
|
||||
const int kNumRanges = 5;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.level0_file_num_compaction_trigger = 4;
|
||||
@@ -769,7 +786,8 @@ TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) {
|
||||
}
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string expected, actual;
|
||||
std::string expected;
|
||||
std::string actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 30); // 6+7+8+9
|
||||
ASSERT_EQ(expected, actual);
|
||||
@@ -811,7 +829,10 @@ TEST_F(DBRangeDelTest, GetIgnoresRangeDeletions) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
const int kNum = 200;
|
||||
const int kRangeBegin = 50;
|
||||
const int kRangeEnd = 150;
|
||||
const int kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
@@ -847,7 +868,10 @@ TEST_F(DBRangeDelTest, IteratorRemovesCoveredKeys) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, IteratorOverUserSnapshot) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
const int kNum = 200;
|
||||
const int kRangeBegin = 50;
|
||||
const int kRangeEnd = 150;
|
||||
const int kNumPerFile = 25;
|
||||
Options opts = CurrentOptions();
|
||||
opts.comparator = test::Uint64Comparator();
|
||||
opts.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
@@ -947,7 +971,8 @@ TEST_F(DBRangeDelTest, TailingIteratorRangeTombstoneUnsupported) {
|
||||
#endif // !ROCKSDB_UBSAN_RUN
|
||||
|
||||
TEST_F(DBRangeDelTest, SubcompactionHasEmptyDedicatedRangeDelFile) {
|
||||
const int kNumFiles = 2, kNumKeysPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
const int kNumKeysPerFile = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1028,7 +1053,8 @@ TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
// tombstones are now truncated to sstable boundaries which removed the need
|
||||
// for that behavior (which could lead to excessively large
|
||||
// compactions).
|
||||
const int kNumFilesPerLevel = 4, kValueBytes = 4 << 10;
|
||||
const int kNumFilesPerLevel = 4;
|
||||
const int kValueBytes = 4 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
@@ -1078,8 +1104,10 @@ TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
CompactionOptions(), {meta.levels[1].files[0].name}, 2 /* level */));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
} else if (i == 1) {
|
||||
auto begin_str = Key(0), end_str = Key(1);
|
||||
Slice begin = begin_str, end = end_str;
|
||||
auto begin_str = Key(0);
|
||||
auto end_str = Key(1);
|
||||
Slice begin = begin_str;
|
||||
Slice end = end_str;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin, &end));
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(1));
|
||||
} else if (i == 2) {
|
||||
@@ -1098,7 +1126,8 @@ TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) {
|
||||
// Test the handling of the range-tombstone end-key as the
|
||||
// upper-bound for an sstable.
|
||||
|
||||
const int kNumFilesPerLevel = 2, kValueBytes = 4 << 10;
|
||||
const int kNumFilesPerLevel = 2;
|
||||
const int kValueBytes = 4 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
@@ -1798,7 +1827,8 @@ TEST_F(DBRangeDelTest, OversizeCompactionGapBetweenPointKeyAndTombstone) {
|
||||
// When compacting L0 into L1, it should split into 3 files:
|
||||
// compaction output should cut before key 5 and key 8 to
|
||||
// limit future compaction size.
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
const int kNumPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
@@ -1835,7 +1865,8 @@ TEST_F(DBRangeDelTest, OversizeCompactionGapBetweenTombstone) {
|
||||
// max_compaction_bytes is less than the size of L2_0.
|
||||
// When compacting L0 into L1, the two range tombstones should be
|
||||
// split into two files.
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
const int kNumPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
@@ -1902,7 +1933,8 @@ TEST_F(DBRangeDelTest, OversizeCompactionPointKeyWithinRangetombstone) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, OverlappedTombstones) {
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
const int kNumPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
@@ -1942,7 +1974,8 @@ TEST_F(DBRangeDelTest, OverlappedTombstones) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, OverlappedKeys) {
|
||||
const int kNumPerFile = 4, kNumFiles = 2;
|
||||
const int kNumPerFile = 4;
|
||||
const int kNumFiles = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.target_file_size_base = 9 * 1024;
|
||||
|
||||
+62
-2
@@ -268,7 +268,9 @@ void DBSecondaryTestBase::CheckFileTypeCounts(const std::string& dir,
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dir, &filenames));
|
||||
|
||||
int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0;
|
||||
int log_cnt = 0;
|
||||
int sst_cnt = 0;
|
||||
int manifest_cnt = 0;
|
||||
for (const auto& file : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
@@ -417,7 +419,8 @@ TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
OpenAndCompactOptions(), cfh, input, &result));
|
||||
|
||||
ASSERT_EQ(result.output_files.size(), 1);
|
||||
InternalKey smallest, largest;
|
||||
InternalKey smallest;
|
||||
InternalKey largest;
|
||||
smallest.DecodeFrom(result.output_files[0].smallest_internal_key);
|
||||
largest.DecodeFrom(result.output_files[0].largest_internal_key);
|
||||
ASSERT_EQ(smallest.user_key().ToString(), "bar");
|
||||
@@ -930,6 +933,63 @@ TEST_F(DBSecondaryTest, OpenFilesAsync) {
|
||||
VerifySecondaryValue("key", "value3");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncNonDefaultCfCatchUp) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
// One open-time table in the non-default column family.
|
||||
ASSERT_OK(Put(1, "key", "v1"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
Options secondary_options = options;
|
||||
secondary_options.max_open_files = -1;
|
||||
secondary_options.open_files_async = true;
|
||||
secondary_options.skip_stats_update_on_db_open = true;
|
||||
|
||||
// Give the HIGH pool enough threads that holding the async opener back does
|
||||
// not block the primary's own flushes.
|
||||
const int original_high_threads =
|
||||
env_->GetBackgroundThreads(Env::Priority::HIGH);
|
||||
Defer restore_high_threads([&] {
|
||||
env_->SetBackgroundThreads(original_high_threads, Env::Priority::HIGH);
|
||||
});
|
||||
env_->SetBackgroundThreads(4, Env::Priority::HIGH);
|
||||
|
||||
// Hold the background opener back so cf1's open-time table stays unpinned
|
||||
// through catch-up.
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBSecondaryTest::OpenFilesAsyncNonDefaultCfCatchUp:Release",
|
||||
"DBImpl::BGWorkAsyncFileOpen::Start"},
|
||||
{"DBImpl::BGWorkAsyncFileOpen:Done",
|
||||
"DBSecondaryTest::OpenFilesAsyncNonDefaultCfCatchUp:Done"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Defer cleanup_sync_point([] {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
});
|
||||
|
||||
OpenSecondaryWithColumnFamilies({"cf1"}, secondary_options);
|
||||
ASSERT_EQ(2, handles_secondary_.size());
|
||||
|
||||
// The primary replaces cf1's open-time table and deletes it.
|
||||
ASSERT_OK(Put(1, "key", "v2"));
|
||||
ASSERT_OK(Flush(1));
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
ASSERT_OK(db_->FlushWAL(/*sync=*/true));
|
||||
|
||||
// Before the fix, catch-up re-opened cf1's compacted-away open-time table and
|
||||
// failed with PathNotFound, latching the tailer.
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
VerifySecondaryValue(handles_secondary_[1], "key", "v2");
|
||||
|
||||
// Let the held-back opener drain before teardown.
|
||||
TEST_SYNC_POINT("DBSecondaryTest::OpenFilesAsyncNonDefaultCfCatchUp:Release");
|
||||
TEST_SYNC_POINT("DBSecondaryTest::OpenFilesAsyncNonDefaultCfCatchUp:Done");
|
||||
CloseSecondary();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenFilesAsyncRequiresSkipStatsUpdate) {
|
||||
Options options = CurrentOptions();
|
||||
options.open_files_async = true;
|
||||
|
||||
@@ -451,7 +451,8 @@ TEST_P(DBTablePropertiesInRangeTest, GetPropertiesOfTablesInRange) {
|
||||
ASSERT_GT(NumTableFilesAtLevel(2), 0);
|
||||
|
||||
// Query the largest range
|
||||
std::size_t num_properties, num_files;
|
||||
std::size_t num_properties;
|
||||
std::size_t num_files;
|
||||
TestGetPropertiesOfTablesInRange(
|
||||
{Range(test::RandomKey(&rnd, 5, test::RandomKeyType::SMALLEST),
|
||||
test::RandomKey(&rnd, 5, test::RandomKeyType::LARGEST))},
|
||||
@@ -547,7 +548,8 @@ TEST_F(DBTablePropertiesTest, GetDbIdentifiersProperty) {
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
|
||||
ASSERT_EQ(1U, fname_to_props.size());
|
||||
|
||||
std::string id, sid;
|
||||
std::string id;
|
||||
std::string sid;
|
||||
ASSERT_OK(db_->GetDbIdentity(id));
|
||||
ASSERT_OK(db_->GetDbSessionId(sid));
|
||||
ASSERT_EQ(id, fname_to_props.begin()->second->db_id);
|
||||
|
||||
@@ -185,7 +185,8 @@ TEST_P(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
if (GetParam()) {
|
||||
read_options.async_io = true;
|
||||
}
|
||||
int num_iters, deleted_iters;
|
||||
int num_iters;
|
||||
int deleted_iters;
|
||||
|
||||
char bufe[32];
|
||||
snprintf(bufe, sizeof(bufe), "00b0%016d", 0);
|
||||
|
||||
+18
-7
@@ -2043,7 +2043,8 @@ TEST_F(DBTest, ApproximateSizesMemTable) {
|
||||
start = Key(1050);
|
||||
end = Key(1080);
|
||||
r = Range(start, end);
|
||||
uint64_t size_with_mt, size_without_mt;
|
||||
uint64_t size_with_mt;
|
||||
uint64_t size_without_mt;
|
||||
ASSERT_OK(db_->GetApproximateSizes(size_approx_options, default_cf, &r, 1,
|
||||
&size_with_mt));
|
||||
ASSERT_GT(size_with_mt, 6000);
|
||||
@@ -2647,7 +2648,8 @@ TEST_F(DBTest, ComparatorCheck) {
|
||||
BytewiseComparator()->FindShortSuccessor(key);
|
||||
}
|
||||
};
|
||||
Options new_options, options;
|
||||
Options new_options;
|
||||
Options options;
|
||||
NewComparator cmp;
|
||||
do {
|
||||
options = CurrentOptions();
|
||||
@@ -3213,7 +3215,11 @@ static void MTThreadBody(void* arg) {
|
||||
ASSERT_OK(s);
|
||||
int unique_id = -1;
|
||||
for (int i = 0; i < kColumnFamilies; ++i) {
|
||||
int k, w, c, cf, u;
|
||||
int k;
|
||||
int w;
|
||||
int c;
|
||||
int cf;
|
||||
int u;
|
||||
ASSERT_EQ(5, sscanf(values[i].c_str(), "%d.%d.%d.%d.%d", &k, &w, &c,
|
||||
&cf, &u))
|
||||
<< values[i];
|
||||
@@ -3992,7 +3998,8 @@ TEST_P(DBTestRandomized, Randomized) {
|
||||
const int N = 10000;
|
||||
const Snapshot* model_snap = nullptr;
|
||||
const Snapshot* db_snap = nullptr;
|
||||
std::string k, v;
|
||||
std::string k;
|
||||
std::string v;
|
||||
for (int step = 0; step < N; step++) {
|
||||
// TODO(sanjay): Test Get() works
|
||||
int p = rnd.Uniform(100);
|
||||
@@ -6312,7 +6319,9 @@ class DelayedMergeOperator : public MergeOperator {
|
||||
};
|
||||
|
||||
TEST_F(DBTest, MergeTestTime) {
|
||||
std::string one, two, three;
|
||||
std::string one;
|
||||
std::string two;
|
||||
std::string three;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
PutFixed64(&three, 3);
|
||||
@@ -6571,7 +6580,8 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
// nonoverlapping with the file on level 0
|
||||
Slice start("a"), end("b");
|
||||
Slice start("a");
|
||||
Slice end("b");
|
||||
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
@@ -6630,7 +6640,8 @@ TEST_F(DBTest, SuggestCompactRangeUniversal) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
// nonoverlapping with the file on level 0
|
||||
Slice start("a"), end("b");
|
||||
Slice start("a");
|
||||
Slice end("b");
|
||||
ASSERT_OK(experimental::SuggestCompactRange(db_.get(), &start, &end));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
|
||||
+2
-1
@@ -535,7 +535,8 @@ Options DBTestBase::GetOptions(
|
||||
}
|
||||
case kBlockBasedTableWithLatestFormat: {
|
||||
// In case different from default
|
||||
table_options.format_version = kLatestBbtFormatVersion;
|
||||
table_options.format_version =
|
||||
BlockBasedTableOptions::kLatestSupportedFormatVersion;
|
||||
break;
|
||||
}
|
||||
case kOptimizeFiltersForHits: {
|
||||
|
||||
+6
-5
@@ -282,7 +282,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
: env_(env), base_(std::move(b)) {
|
||||
env_->num_open_wal_file_.fetch_add(1);
|
||||
}
|
||||
virtual ~SpecialWalFile() { env_->num_open_wal_file_.fetch_add(-1); }
|
||||
~SpecialWalFile() override { env_->num_open_wal_file_.fetch_add(-1); }
|
||||
Status Append(const Slice& data) override {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
TEST_SYNC_POINT("SpecialEnv::SpecialWalFile::Append:1");
|
||||
@@ -319,7 +319,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
// SyncPoint is not supported in Released Windows Mode.
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
// Check preallocation size
|
||||
size_t block_size, last_allocated_block;
|
||||
size_t block_size;
|
||||
size_t last_allocated_block;
|
||||
base_->GetPreallocationStatus(&block_size, &last_allocated_block);
|
||||
TEST_SYNC_POINT_CALLBACK("DBTestWalFile.GetPreallocationStatus",
|
||||
&block_size);
|
||||
@@ -602,7 +603,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
class NoopDirectory : public Directory {
|
||||
public:
|
||||
NoopDirectory() {}
|
||||
~NoopDirectory() {}
|
||||
~NoopDirectory() override {}
|
||||
|
||||
Status Fsync() override { return Status::OK(); }
|
||||
Status Close() override { return Status::OK(); }
|
||||
@@ -891,7 +892,7 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
|
||||
class OnFileDeletionListener : public EventListener {
|
||||
public:
|
||||
OnFileDeletionListener() : matched_count_(0), expected_file_name_("") {}
|
||||
OnFileDeletionListener() : matched_count_(0) {}
|
||||
const char* Name() const override { return kClassName(); }
|
||||
static const char* kClassName() { return "OnFileDeletionListener"; }
|
||||
|
||||
@@ -1107,7 +1108,7 @@ class DBTestBase : public testing::Test {
|
||||
// tests, but won't cover the exact fsync logic.
|
||||
DBTestBase(const std::string path, bool env_do_fsync);
|
||||
|
||||
~DBTestBase();
|
||||
~DBTestBase() override;
|
||||
|
||||
static std::string Key(int i) {
|
||||
char buf[100];
|
||||
|
||||
@@ -1340,7 +1340,8 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCFPathUse) {
|
||||
|
||||
std::vector<Options> option_vector;
|
||||
option_vector.emplace_back(options);
|
||||
ColumnFamilyOptions cf_opt1(options), cf_opt2(options);
|
||||
ColumnFamilyOptions cf_opt1(options);
|
||||
ColumnFamilyOptions cf_opt2(options);
|
||||
// Configure CF1 specific paths.
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1", 300 * 1024);
|
||||
cf_opt1.cf_paths.emplace_back(dbname_ + "cf1_2", 300 * 1024);
|
||||
|
||||
+102
-2
@@ -1043,7 +1043,8 @@ TEST_F(DBWALTest, IgnoreRecoveredLog) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// fill up the DB
|
||||
std::string one, two;
|
||||
std::string one;
|
||||
std::string two;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
||||
@@ -1769,7 +1770,8 @@ TEST_F(DBWALTest, PartOfWritesWithWALDisabled) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_env.get();
|
||||
options.disable_auto_compactions = true;
|
||||
WriteOptions wal_on, wal_off;
|
||||
WriteOptions wal_on;
|
||||
WriteOptions wal_off;
|
||||
wal_on.sync = true;
|
||||
wal_on.disableWAL = false;
|
||||
wal_off.disableWAL = true;
|
||||
@@ -3088,6 +3090,104 @@ TEST_F(DBWALTest, ReadOnlyRecoveryNoTruncate) {
|
||||
#endif // ROCKSDB_FALLOCATE_PRESENT
|
||||
#endif // ROCKSDB_PLATFORM_POSIX
|
||||
|
||||
// Reports a configurable preallocation capability and counts
|
||||
// ReopenWritableFile() calls on WAL files.
|
||||
class PreallocationTestFs : public FileSystemWrapper {
|
||||
public:
|
||||
PreallocationTestFs(const std::shared_ptr<FileSystem>& target,
|
||||
bool preallocation_supported)
|
||||
: FileSystemWrapper(target),
|
||||
preallocation_supported_(preallocation_supported) {}
|
||||
|
||||
const char* Name() const override { return "PreallocationTestFs"; }
|
||||
|
||||
bool IsPreallocationSupported() override { return preallocation_supported_; }
|
||||
|
||||
IOStatus ReopenWritableFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(fname.substr(fname.find_last_of('/') + 1), &number,
|
||||
&type) &&
|
||||
type == kWalFile) {
|
||||
wal_reopen_count_.FetchAddRelaxed(1);
|
||||
}
|
||||
return target()->ReopenWritableFile(fname, file_opts, result, dbg);
|
||||
}
|
||||
|
||||
void SetPreallocationSupported(bool supported) {
|
||||
preallocation_supported_ = supported;
|
||||
}
|
||||
|
||||
int GetWalReopenCount() const { return wal_reopen_count_.LoadRelaxed(); }
|
||||
|
||||
void ResetWalReopenCount() { wal_reopen_count_.StoreRelaxed(0); }
|
||||
|
||||
private:
|
||||
bool preallocation_supported_;
|
||||
RelaxedAtomic<int> wal_reopen_count_{0};
|
||||
};
|
||||
|
||||
// Tests that the last WAL is reopened for truncation during recovery only if
|
||||
// the FileSystem reports that it supports preallocation.
|
||||
TEST_F(DBWALTest, SkipWalTruncateIfPreallocationNotSupported) {
|
||||
// The Env must outlive the loop since DestroyAndReopen() destroys the
|
||||
// previous DB using the previous iteration's options.
|
||||
auto test_fs = std::make_shared<PreallocationTestFs>(
|
||||
env_->GetFileSystem(), /*preallocation_supported=*/true);
|
||||
std::unique_ptr<Env> test_env(NewCompositeEnv(test_fs));
|
||||
for (bool preallocation_supported : {true, false}) {
|
||||
// avoid_flush_during_recovery = true restores the WAL as alive, while
|
||||
// false flushes the recovered data and leaves the WAL obsolete.
|
||||
for (bool avoid_flush_during_recovery : {true, false}) {
|
||||
SCOPED_TRACE(
|
||||
"preallocation_supported=" + std::to_string(preallocation_supported) +
|
||||
" avoid_flush_during_recovery=" +
|
||||
std::to_string(avoid_flush_during_recovery));
|
||||
Options options = CurrentOptions();
|
||||
options.env = test_env.get();
|
||||
options.avoid_flush_during_recovery = avoid_flush_during_recovery;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
test_fs->SetPreallocationSupported(preallocation_supported);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
Close();
|
||||
|
||||
test_fs->ResetWalReopenCount();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(preallocation_supported ? 1 : 0, test_fs->GetWalReopenCount());
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that a read-only open never reopens a WAL for truncation, even if the
|
||||
// FileSystem supports preallocation.
|
||||
TEST_F(DBWALTest, ReadOnlyOpenSkipsWalTruncate) {
|
||||
Options options = CurrentOptions();
|
||||
auto test_fs = std::make_shared<PreallocationTestFs>(
|
||||
env_->GetFileSystem(), /*preallocation_supported=*/true);
|
||||
std::unique_ptr<Env> test_env(NewCompositeEnv(test_fs));
|
||||
options.env = test_env.get();
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
Close();
|
||||
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Close();
|
||||
ASSERT_EQ(0, test_fs->GetWalReopenCount());
|
||||
|
||||
// The same WAL is truncated by a read-write open.
|
||||
Reopen(options);
|
||||
ASSERT_EQ(1, test_fs->GetWalReopenCount());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, WalInManifestButNotInSortedWals) {
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
|
||||
@@ -303,7 +303,8 @@ TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) {
|
||||
|
||||
std::string start_str = "foo";
|
||||
std::string end_str = "foo2";
|
||||
Slice start(start_str), end(end_str);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
|
||||
Close();
|
||||
@@ -442,7 +443,8 @@ TEST_F(DBBasicTestWithTimestamp, UpdateFullHistoryTsLow) {
|
||||
}
|
||||
|
||||
// Test invalid compaction with range
|
||||
Slice start(kKey), end(kKey);
|
||||
Slice start(kKey);
|
||||
Slice end(kKey);
|
||||
Status s = db_->CompactRange(comp_opts, &start, &end);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
s = db_->CompactRange(comp_opts, &start, nullptr);
|
||||
@@ -4253,7 +4255,8 @@ TEST_F(DBBasicTestWithTimestamp,
|
||||
ropts.timestamp = &ts;
|
||||
CompactRangeOptions cro;
|
||||
cro.full_history_ts_low = nullptr;
|
||||
std::string value, key_ts;
|
||||
std::string value;
|
||||
std::string key_ts;
|
||||
Status s;
|
||||
auto verify = [&] {
|
||||
s = db_->Get(ropts, "k1", &value);
|
||||
@@ -4345,7 +4348,8 @@ TEST_F(DBBasicTestWithTimestamp,
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ropts.timestamp = &compaction_ts;
|
||||
std::string value, ts;
|
||||
std::string value;
|
||||
std::string ts;
|
||||
ASSERT_OK(db_->Get(ropts, "k1", &value, &ts));
|
||||
ASSERT_EQ(value, "v1");
|
||||
// timestamp is below full_history_ts_low, zeroed out as the key goes into
|
||||
@@ -4399,7 +4403,10 @@ INSTANTIATE_TEST_CASE_P(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp)));
|
||||
|
||||
TEST_P(DeleteRangeWithTimestampTableOptions, BasicReadAndIterate) {
|
||||
const int kNum = 200, kRangeBegin = 50, kRangeEnd = 150, kNumPerFile = 25;
|
||||
const int kNum = 200;
|
||||
const int kRangeBegin = 50;
|
||||
const int kRangeEnd = 150;
|
||||
const int kNumPerFile = 25;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
@@ -4503,7 +4510,8 @@ TEST_P(DeleteRangeWithTimestampTableOptions, BasicReadAndIterate) {
|
||||
PutFixed64(&read_ts, kNum);
|
||||
read_ts_slice = read_ts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
std::string value, timestamp;
|
||||
std::string value;
|
||||
std::string timestamp;
|
||||
Status s;
|
||||
std::string expected_ts;
|
||||
int int_expected_ts;
|
||||
@@ -4589,7 +4597,10 @@ TEST_F(DBBasicTestWithTimestamp, DeleteRangeGetIteratorWithSnapshot) {
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
std::string put_ts = Timestamp(0, 0);
|
||||
const int kNum = 4, kNumPerFile = 1, kRangeBegin = 1, kRangeEnd = 3;
|
||||
const int kNum = 4;
|
||||
const int kNumPerFile = 1;
|
||||
const int kRangeBegin = 1;
|
||||
const int kRangeEnd = 3;
|
||||
options.memtable_factory.reset(test::NewSpecialSkipListFactory(kNumPerFile));
|
||||
const Snapshot* before_tombstone = nullptr;
|
||||
const Snapshot* after_tombstone = nullptr;
|
||||
@@ -4636,7 +4647,8 @@ TEST_F(DBBasicTestWithTimestamp, DeleteRangeGetIteratorWithSnapshot) {
|
||||
db_->MultiGet(read_opts, db_->DefaultColumnFamily(), batch_size,
|
||||
keys.data(), values.data(), timestamps.data(),
|
||||
statuses.data(), true /* sorted_input */);
|
||||
std::string value, timestamp;
|
||||
std::string value;
|
||||
std::string timestamp;
|
||||
Status s;
|
||||
for (int i = 0; i < kNum; ++i) {
|
||||
s = db_->Get(read_opts, Key1(i), &value, ×tamp);
|
||||
|
||||
@@ -388,7 +388,8 @@ TEST_P(DBWriteBufferManagerTest, SharedWriteBufferLimitAcrossDB1) {
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
bool s1 = true;
|
||||
bool s2 = true;
|
||||
// Write to multiple columns of db_.
|
||||
std::function<void(int)> write_cf = [&](int cf) {
|
||||
Status tmp = Put(cf, Key(3), DummyString(1), wo);
|
||||
@@ -530,7 +531,8 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsSingleDB) {
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
bool s1 = true;
|
||||
bool s2 = true;
|
||||
|
||||
std::function<void(int)> write_slow_down = [&](int cf) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
@@ -694,7 +696,8 @@ TEST_P(DBWriteBufferManagerTest, MixedSlowDownOptionsMultipleDB) {
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
bool s1 = true, s2 = true;
|
||||
bool s1 = true;
|
||||
bool s2 = true;
|
||||
std::function<void(DB*)> write_slow_down = [&](DB* db) {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
@@ -983,6 +986,8 @@ TEST_P(DBWriteBufferManagerTest, FlushLargestAcrossDBsPolicy) {
|
||||
options.write_buffer_manager->SetBufferSize(768 << 10);
|
||||
|
||||
recorder->WaitForFlush(other_db.get());
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(other_db.get())
|
||||
->TEST_WaitForBackgroundWork());
|
||||
EXPECT_FALSE(recorder->HasFlushed(db_.get()));
|
||||
EXPECT_EQ(0, recorder->ManualFlushes());
|
||||
|
||||
|
||||
+4
-2
@@ -434,7 +434,6 @@ class InternalKeyComparator
|
||||
// overhead, set `named` to false. In that case, `Name()` will return a
|
||||
// generic name that is non-specific to the underlying comparator.
|
||||
explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c) {}
|
||||
virtual ~InternalKeyComparator() {}
|
||||
|
||||
int Compare(const Slice& a, const Slice& b) const override;
|
||||
|
||||
@@ -621,6 +620,8 @@ class IterKey {
|
||||
// No copying allowed
|
||||
IterKey(const IterKey&) = delete;
|
||||
void operator=(const IterKey&) = delete;
|
||||
IterKey(IterKey&&) = delete;
|
||||
void operator=(IterKey&&) = delete;
|
||||
|
||||
~IterKey() {
|
||||
ResetBuffer();
|
||||
@@ -1274,7 +1275,8 @@ inline int InternalKeyComparator::Compare(const Slice& a,
|
||||
SequenceNumber b_global_seqno) const {
|
||||
int r = user_comparator_.Compare(ExtractUserKey(a), ExtractUserKey(b));
|
||||
if (r == 0) {
|
||||
uint64_t a_footer, b_footer;
|
||||
uint64_t a_footer;
|
||||
uint64_t b_footer;
|
||||
if (a_global_seqno == kDisableGlobalSequenceNumber) {
|
||||
a_footer = ExtractInternalKeyFooter(a);
|
||||
} else {
|
||||
|
||||
+4
-2
@@ -217,9 +217,11 @@ TEST_P(IterKeySwapTest, SwapAndDestroy) {
|
||||
auto [a_key_len, a_copy, a_use_secondary, b_key_len, b_copy,
|
||||
b_use_secondary] = GetParam();
|
||||
|
||||
std::string expected_a, expected_b;
|
||||
std::string expected_a;
|
||||
std::string expected_b;
|
||||
// Storage for pinned keys -- must outlive the IterKeys.
|
||||
std::string a_storage, b_storage;
|
||||
std::string a_storage;
|
||||
std::string b_storage;
|
||||
IterKey a;
|
||||
expected_a =
|
||||
PopulateKey(a, a_key_len, 'a', a_copy, a_use_secondary, a_storage);
|
||||
|
||||
+23
-11
@@ -104,7 +104,9 @@ class DeleteFileTest : public DBTestBase {
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dir, &filenames));
|
||||
|
||||
int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0;
|
||||
int log_cnt = 0;
|
||||
int sst_cnt = 0;
|
||||
int manifest_cnt = 0;
|
||||
for (const auto& file : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
@@ -149,11 +151,13 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
CheckFileTypeCounts(wal_dir_, 1, 0, 0);
|
||||
// 2 ssts, 1 manifest
|
||||
CheckFileTypeCounts(dbname_, 0, 2, 1);
|
||||
std::string first("0"), last("999999");
|
||||
std::string first("0");
|
||||
std::string last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
Slice first_slice(first);
|
||||
Slice last_slice(last);
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice));
|
||||
// 1 sst after compaction
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
@@ -180,11 +184,13 @@ TEST_F(DeleteFileTest, WaitForCompactWithWaitForPurgeOptionTest) {
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
std::string first("0"), last("999999");
|
||||
std::string first("0");
|
||||
std::string last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
Slice first_slice(first);
|
||||
Slice last_slice(last);
|
||||
|
||||
CreateTwoLevels();
|
||||
Iterator* itr = nullptr;
|
||||
@@ -223,11 +229,13 @@ TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) {
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
std::string first("0"), last("999999");
|
||||
std::string first("0");
|
||||
std::string last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
Slice first_slice(first);
|
||||
Slice last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = nullptr;
|
||||
@@ -471,11 +479,13 @@ TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) {
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
std::string first("0"), last("999999");
|
||||
std::string first("0");
|
||||
std::string last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
Slice first_slice(first);
|
||||
Slice last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = nullptr;
|
||||
@@ -512,11 +522,13 @@ TEST_F(DeleteFileTest, BackgroundPurgeTestMultipleJobs) {
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
std::string first("0"), last("999999");
|
||||
std::string first("0");
|
||||
std::string last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
Slice first_slice(first);
|
||||
Slice last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
CreateTwoLevels();
|
||||
|
||||
Loaded 100 of 535 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user