mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87d4df1f07 | ||
|
|
faae96da2e | ||
|
|
165026074a | ||
|
|
6a060888aa | ||
|
|
d2ebdea566 | ||
|
|
6f7cabeac8 | ||
|
|
adb9bf5179 | ||
|
|
7dd5e91e39 | ||
|
|
e94141d848 | ||
|
|
bcf88d48ce | ||
|
|
f6d01f0f6e | ||
|
|
e223cd46c6 |
No files matched your search
@@ -96,13 +96,3 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/windows-build-steps"
|
||||
build-linux-arm-test-full:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev
|
||||
- run: make V=1 J=4 -j4 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -607,13 +607,3 @@ jobs:
|
||||
with:
|
||||
name: maven-site
|
||||
path: "${{ github.workspace }}/java/target/site"
|
||||
build-linux-arm:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: sudo apt-get update && sudo apt-get install -y build-essential
|
||||
- run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some
|
||||
- uses: "./.github/actions/post-steps"
|
||||
+1
-7
@@ -653,7 +653,6 @@ set(SOURCES
|
||||
cache/sharded_cache.cc
|
||||
cache/tiered_secondary_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/attribute_group_iterator_impl.cc
|
||||
db/blob/blob_contents.cc
|
||||
db/blob/blob_fetcher.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
@@ -670,7 +669,6 @@ set(SOURCES
|
||||
db/blob/prefetch_buffer_collection.cc
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/coalescing_iterator.cc
|
||||
db/column_family.cc
|
||||
db/compaction/compaction.cc
|
||||
db/compaction/compaction_iterator.cc
|
||||
@@ -717,6 +715,7 @@ set(SOURCES
|
||||
db/memtable_list.cc
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/multi_cf_iterator.cc
|
||||
db/output_validator.cc
|
||||
db/periodic_task_scheduler.cc
|
||||
db/range_del_aggregator.cc
|
||||
@@ -955,7 +954,6 @@ set(SOURCES
|
||||
utilities/transactions/write_prepared_txn_db.cc
|
||||
utilities/transactions/write_unprepared_txn.cc
|
||||
utilities/transactions/write_unprepared_txn_db.cc
|
||||
utilities/types_util.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/wal_filter.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
@@ -1037,10 +1035,8 @@ endif()
|
||||
|
||||
else()
|
||||
list(APPEND SOURCES
|
||||
db/db_impl/db_impl_follower.cc
|
||||
port/port_posix.cc
|
||||
env/env_posix.cc
|
||||
env/fs_on_demand.cc
|
||||
env/fs_posix.cc
|
||||
env/io_posix.cc)
|
||||
endif()
|
||||
@@ -1365,7 +1361,6 @@ if(WITH_TESTS)
|
||||
db/file_indexer_test.cc
|
||||
db/filename_test.cc
|
||||
db/flush_job_test.cc
|
||||
db/db_follower_test.cc
|
||||
db/import_column_family_test.cc
|
||||
db/listener_test.cc
|
||||
db/log_test.cc
|
||||
@@ -1489,7 +1484,6 @@ if(WITH_TESTS)
|
||||
utilities/transactions/lock/range/range_locking_test.cc
|
||||
utilities/transactions/timestamped_snapshot_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/types_util_test.cc
|
||||
utilities/util_merge_operators_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
${PLUGIN_TESTS}
|
||||
|
||||
+5
-41
@@ -1,50 +1,14 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.2.2 (05/31/2024)
|
||||
## 9.1.2 (04/24/2024)
|
||||
### Bug Fixes
|
||||
* Fixed a bug causing stale memory access when using the TieredSecondaryCache with an NVM secondary cache, and a file system that supports return an FS allocated buffer for MultiRead (FSSupportedOps::kFSBuffer is set).
|
||||
|
||||
## 9.2.1 (05/03/2024)
|
||||
### Public API Changes
|
||||
* Add a kAdmPolicyAllowAll option to TieredAdmissionPolicy that admits all blocks evicted from the primary block cache into the compressed secondary cache.
|
||||
|
||||
## 9.2.0 (05/01/2024)
|
||||
### New Features
|
||||
* Added two options `deadline` and `max_size_bytes` for CacheDumper to exit early
|
||||
* Added a new API `GetEntityFromBatchAndDB` to `WriteBatchWithIndex` that can be used for wide-column point lookups with read-your-own-writes consistency. Similarly to `GetFromBatchAndDB`, the API can combine data from the write batch with data from the underlying database if needed. See the API comments for more details.
|
||||
* [Experimental] Introduce two new cross-column-family iterators - CoalescingIterator and AttributeGroupIterator. The CoalescingIterator enables users to iterate over multiple column families and access their values and columns. During this iteration, if the same key exists in more than one column family, the keys in the later column family will overshadow the previous ones. The AttributeGroupIterator allows users to gather wide columns per Column Family and create attribute groups while iterating over keys across all CFs.
|
||||
* Added a new API `MultiGetEntityFromBatchAndDB` to `WriteBatchWithIndex` that can be used for batched wide-column point lookups with read-your-own-writes consistency. Similarly to `MultiGetFromBatchAndDB`, the API can combine data from the write batch with data from the underlying database if needed. See the API comments for more details.
|
||||
* *Adds a `SstFileReader::NewTableIterator` API to support programmatically read a SST file as a raw table file.
|
||||
* Add an option to `WaitForCompactOptions` - `wait_for_purge` to make `WaitForCompact()` API wait for background purge to complete
|
||||
|
||||
### Public API Changes
|
||||
* DeleteRange() will return NotSupported() if row_cache is configured since they don't work together in some cases.
|
||||
* Deprecated `CompactionOptions::compression` since `CompactionOptions`'s API for configuring compression was incomplete, unsafe, and likely unnecessary
|
||||
* Using `OptionChangeMigration()` to migrate from non-FIFO to FIFO compaction
|
||||
with `Options::compaction_options_fifo.max_table_files_size` > 0 can cause
|
||||
the whole DB to be dropped right after migration if the migrated data is larger than
|
||||
`max_table_files_size`
|
||||
|
||||
### Behavior Changes
|
||||
* Enabling `BlockBasedTableOptions::block_align` is now incompatible (i.e., APIs will return `Status::InvalidArgument`) with more ways of enabling compression: `CompactionOptions::compression`, `ColumnFamilyOptions::compression_per_level`, and `ColumnFamilyOptions::bottommost_compression`.
|
||||
* Changed the default value of `CompactionOptions::compression` to `kDisableCompressionOption`, which means the compression type is determined by the `ColumnFamilyOptions`.
|
||||
* `BlockBasedTableOptions::optimize_filters_for_memory` is now set to true by default. When `partition_filters=false`, this could lead to somewhat increased average RSS memory usage by the block cache, but this "extra" usage is within the allowed memory budget and should make memory usage more consistent (by minimizing internal fragmentation for more kinds of blocks).
|
||||
* Dump all keys for cache dumper impl if `SetDumpFilter()` is not called
|
||||
* `CompactRange()` with `CompactRangeOptions::change_level = true` and `CompactRangeOptions::target_level = 0` that ends up moving more than 1 file from non-L0 to L0 will return `Status::Aborted()`.
|
||||
* On distributed file systems that support file system level checksum verification and reconstruction reads, RocksDB will now retry a file read if the initial read fails RocksDB block level or record level checksum verification. This applies to MANIFEST file reads when the DB is opened, and to SST file reads at all times.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug causing `VerifyFileChecksums()` to return false-positive corruption under `BlockBasedTableOptions::block_align=true`
|
||||
* Provide consistent view of the database across the column families for `NewIterators()` API.
|
||||
* Fixed feature interaction bug for `DeleteRange()` together with `ColumnFamilyOptions::memtable_insert_with_hint_prefix_extractor`. The impact of this bug would likely be corruption or crashing.
|
||||
* Fixed hang in `DisableManualCompactions()` where compactions waiting to be scheduled due to conflicts would not be canceled promptly
|
||||
|
||||
## 9.1.1 (04/17/2024)
|
||||
### Bug Fixes
|
||||
* Fixed Java `SstFileMetaData` to prevent throwing `java.lang.NoSuchMethodError`
|
||||
* Fixed a regression when `ColumnFamilyOptions::max_successive_merges > 0` where the CPU overhead for deciding whether to merge could have increased unless the user had set the option `ColumnFamilyOptions::strict_max_successive_merges`
|
||||
* Fixed a bug in `MultiGet()` and `MultiGetEntity()` together with blob files (`ColumnFamilyOptions::enable_blob_files == true`). An error looking up one of the keys could cause the results to be wrong for other keys for which the statuses were `Status::OK`.
|
||||
* Fixed a bug where wrong padded bytes are used to generate file checksum and `DataVerificationInfo::checksum` upon file creation
|
||||
* Correctly implemented the move semantics of `PinnableWideColumns`.
|
||||
* Fixed a bug when the recycle_log_file_num in DBOptions is changed from 0 to non-zero when a DB is reopened. On a subsequent reopen, if a log file created when recycle_log_file_num==0 was reused previously, is alive and is empty, we could end up inserting stale WAL records into the memtable.
|
||||
* *Fix a bug where obsolete files' deletion during DB::Open are not rate limited with `SstFilemManager`'s slow deletion feature even if it's configured.
|
||||
|
||||
## 9.1.0 (03/22/2024)
|
||||
### New Features
|
||||
|
||||
@@ -1610,9 +1610,6 @@ object_registry_test: $(OBJ_DIR)/utilities/object_registry_test.o $(TEST_LIBRARY
|
||||
ttl_test: $(OBJ_DIR)/utilities/ttl/ttl_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
types_util_test: $(OBJ_DIR)/utilities/types_util_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
write_batch_with_index_test: $(OBJ_DIR)/utilities/write_batch_with_index/write_batch_with_index_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1922,9 +1919,6 @@ sst_file_reader_test: $(OBJ_DIR)/table/sst_file_reader_test.o $(TEST_LIBRARY) $(
|
||||
db_secondary_test: $(OBJ_DIR)/db/db_secondary_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_follower_test: $(OBJ_DIR)/db/db_follower_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
block_cache_tracer_test: $(OBJ_DIR)/trace_replay/block_cache_tracer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"cache/sharded_cache.cc",
|
||||
"cache/tiered_secondary_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/attribute_group_iterator_impl.cc",
|
||||
"db/blob/blob_contents.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
@@ -38,7 +37,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/blob/prefetch_buffer_collection.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/coalescing_iterator.cc",
|
||||
"db/column_family.cc",
|
||||
"db/compaction/compaction.cc",
|
||||
"db/compaction/compaction_iterator.cc",
|
||||
@@ -60,7 +58,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/db_impl/db_impl_debug.cc",
|
||||
"db/db_impl/db_impl_experimental.cc",
|
||||
"db/db_impl/db_impl_files.cc",
|
||||
"db/db_impl/db_impl_follower.cc",
|
||||
"db/db_impl/db_impl_open.cc",
|
||||
"db/db_impl/db_impl_readonly.cc",
|
||||
"db/db_impl/db_impl_secondary.cc",
|
||||
@@ -86,6 +83,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
"db/merge_operator.cc",
|
||||
"db/multi_cf_iterator.cc",
|
||||
"db/output_validator.cc",
|
||||
"db/periodic_task_scheduler.cc",
|
||||
"db/range_del_aggregator.cc",
|
||||
@@ -118,7 +116,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"env/env_posix.cc",
|
||||
"env/file_system.cc",
|
||||
"env/file_system_tracer.cc",
|
||||
"env/fs_on_demand.cc",
|
||||
"env/fs_posix.cc",
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
@@ -349,7 +346,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/types_util.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
@@ -4797,12 +4793,6 @@ cpp_unittest_wrapper(name="db_flush_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_follower_test",
|
||||
srcs=["db/db_follower_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_inplace_update_test",
|
||||
srcs=["db/db_inplace_update_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5537,12 +5527,6 @@ cpp_unittest_wrapper(name="ttl_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="types_util_test",
|
||||
srcs=["utilities/types_util_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="udt_util_test",
|
||||
srcs=["util/udt_util_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -113,7 +113,7 @@ CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_BIN/scan-build
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
|
||||
@@ -110,7 +110,7 @@ CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_BIN/scan-build"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
|
||||
Vendored
+3
-6
@@ -134,14 +134,12 @@ bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
|
||||
auto obj = target_->Value(handle);
|
||||
// Ignore dummy entry
|
||||
if (obj != kDummyObj) {
|
||||
bool force = false;
|
||||
bool hit = false;
|
||||
if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowCacheHits) {
|
||||
force = was_hit;
|
||||
} else if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowAll) {
|
||||
force = true;
|
||||
hit = was_hit;
|
||||
}
|
||||
// Spill into secondary cache.
|
||||
secondary_cache_->Insert(key, obj, helper, force).PermitUncheckedError();
|
||||
secondary_cache_->Insert(key, obj, helper, hit).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
// Never takes ownership of obj
|
||||
@@ -663,7 +661,6 @@ std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
|
||||
break;
|
||||
case TieredAdmissionPolicy::kAdmPolicyPlaceholder:
|
||||
case TieredAdmissionPolicy::kAdmPolicyAllowCacheHits:
|
||||
case TieredAdmissionPolicy::kAdmPolicyAllowAll:
|
||||
if (opts.nvm_sec_cache) {
|
||||
valid_adm_policy = false;
|
||||
}
|
||||
|
||||
Vendored
+1
-164
@@ -816,174 +816,11 @@ TEST_P(DBTieredAdmPolicyTest, CompressedOnlyTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_P(DBTieredAdmPolicyTest, CompressedCacheAdmission) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
// We want a block cache of size 5KB, and a compressed secondary cache of
|
||||
// size 5KB. However, we specify a block cache size of 256KB here in order
|
||||
// to take into account the cache reservation in the block cache on
|
||||
// behalf of the compressed cache. The unit of cache reservation is 256KB.
|
||||
// The effective block cache capacity will be calculated as 256 + 5 = 261KB,
|
||||
// and 256KB will be reserved for the compressed cache, leaving 10KB for
|
||||
// the primary block cache. We only have to worry about this here because
|
||||
// the cache size is so small.
|
||||
table_options.block_cache = NewCache(256 * 1024, 5 * 1024, 0, GetParam());
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
size_t comp_cache_usage = compressed_secondary_cache()->TEST_GetUsage();
|
||||
// Disable paranoid_file_checks so that flush will not read back the newly
|
||||
// written file
|
||||
options.paranoid_file_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// The second Get (for 5) will evict the data block loaded by the first
|
||||
// Get, which will be admitted into the compressed secondary cache only
|
||||
// for the kAdmPolicyAllowAll policy
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
v = Get(Key(5));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
if (GetParam() == TieredAdmissionPolicy::kAdmPolicyAllowAll) {
|
||||
ASSERT_GT(compressed_secondary_cache()->TEST_GetUsage(),
|
||||
comp_cache_usage + 128);
|
||||
} else {
|
||||
ASSERT_LT(compressed_secondary_cache()->TEST_GetUsage(),
|
||||
comp_cache_usage + 128);
|
||||
}
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBTieredSecondaryCacheTest, FSBufferTest) {
|
||||
class WrapFS : public FileSystemWrapper {
|
||||
public:
|
||||
explicit WrapFS(const std::shared_ptr<FileSystem>& _target)
|
||||
: FileSystemWrapper(_target) {}
|
||||
~WrapFS() override {}
|
||||
const char* Name() const override { return "WrapFS"; }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
class WrappedRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
explicit WrappedRandomAccessFile(
|
||||
std::unique_ptr<FSRandomAccessFile>& file)
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)) {}
|
||||
|
||||
IOStatus MultiRead(FSReadRequest* reqs, size_t num_reqs,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
for (size_t i = 0; i < num_reqs; ++i) {
|
||||
FSReadRequest& req = reqs[i];
|
||||
FSAllocationPtr buffer(new char[req.len], [](void* ptr) {
|
||||
delete[] static_cast<char*>(ptr);
|
||||
});
|
||||
req.fs_scratch = std::move(buffer);
|
||||
req.status = Read(req.offset, req.len, options, &req.result,
|
||||
static_cast<char*>(req.fs_scratch.get()), dbg);
|
||||
}
|
||||
return IOStatus::OK();
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(new WrappedRandomAccessFile(file));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void SupportedOps(int64_t& supported_ops) override {
|
||||
supported_ops = 1 << FSSupportedOps::kAsyncIO;
|
||||
supported_ops |= 1 << FSSupportedOps::kFSBuffer;
|
||||
}
|
||||
};
|
||||
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<WrapFS> wrap_fs =
|
||||
std::make_shared<WrapFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> wrap_env(new CompositeEnvWrapper(env_, wrap_fs));
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewCache(250 * 1024, 20 * 1024, 256 * 1024,
|
||||
TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
/*ready_before_wait=*/true);
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.env = wrap_env.get();
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
keys.push_back(Key(0));
|
||||
keys.push_back(Key(4));
|
||||
keys.push_back(Key(8));
|
||||
values = MultiGet(keys, /*snapshot=*/nullptr, /*async=*/true);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (const auto& value : values) {
|
||||
ASSERT_EQ(1007, value.size());
|
||||
}
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 3u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_hits(), 0u);
|
||||
|
||||
std::string v = Get(Key(12));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 4u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 4u);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOCK_CACHE_MISS), 4u);
|
||||
|
||||
Close();
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTieredAdmPolicyTest, DBTieredAdmPolicyTest,
|
||||
::testing::Values(TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
TieredAdmissionPolicy::kAdmPolicyPlaceholder,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits,
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowAll));
|
||||
TieredAdmissionPolicy::kAdmPolicyAllowCacheHits));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/attribute_group_iterator_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
const AttributeGroups kNoAttributeGroups;
|
||||
const IteratorAttributeGroups kNoIteratorAttributeGroups;
|
||||
|
||||
void AttributeGroupIteratorImpl::AddToAttributeGroups(
|
||||
const autovector<MultiCfIteratorInfo>& items) {
|
||||
for (const auto& item : items) {
|
||||
attribute_groups_.emplace_back(item.cfh, &item.iterator->columns());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/multi_cf_iterator_impl.h"
|
||||
#include "rocksdb/attribute_groups.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
public:
|
||||
AttributeGroupIteratorImpl(
|
||||
const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
AddToAttributeGroups(items);
|
||||
}) {}
|
||||
~AttributeGroupIteratorImpl() override {}
|
||||
|
||||
// No copy allowed
|
||||
AttributeGroupIteratorImpl(const AttributeGroupIteratorImpl&) = delete;
|
||||
AttributeGroupIteratorImpl& operator=(const AttributeGroupIteratorImpl&) =
|
||||
delete;
|
||||
|
||||
bool Valid() const override { return impl_.Valid(); }
|
||||
void SeekToFirst() override { impl_.SeekToFirst(); }
|
||||
void SeekToLast() override { impl_.SeekToLast(); }
|
||||
void Seek(const Slice& target) override { impl_.Seek(target); }
|
||||
void SeekForPrev(const Slice& target) override { impl_.SeekForPrev(target); }
|
||||
void Next() override { impl_.Next(); }
|
||||
void Prev() override { impl_.Prev(); }
|
||||
Slice key() const override { return impl_.key(); }
|
||||
Status status() const override { return impl_.status(); }
|
||||
|
||||
const IteratorAttributeGroups& attribute_groups() const override {
|
||||
assert(Valid());
|
||||
return attribute_groups_;
|
||||
}
|
||||
|
||||
void Reset() { attribute_groups_.clear(); }
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
IteratorAttributeGroups attribute_groups_;
|
||||
void AddToAttributeGroups(const autovector<MultiCfIteratorInfo>& items);
|
||||
};
|
||||
|
||||
class EmptyAttributeGroupIterator : public AttributeGroupIterator {
|
||||
public:
|
||||
explicit EmptyAttributeGroupIterator(const Status& s) : status_(s) {}
|
||||
bool Valid() const override { return false; }
|
||||
void Seek(const Slice& /*target*/) override {}
|
||||
void SeekForPrev(const Slice& /*target*/) override {}
|
||||
void SeekToFirst() override {}
|
||||
void SeekToLast() override {}
|
||||
void Next() override { assert(false); }
|
||||
void Prev() override { assert(false); }
|
||||
Slice key() const override {
|
||||
assert(false);
|
||||
return Slice();
|
||||
}
|
||||
Status status() const override { return status_; }
|
||||
|
||||
const IteratorAttributeGroups& attribute_groups() const override {
|
||||
return kNoIteratorAttributeGroups;
|
||||
}
|
||||
|
||||
private:
|
||||
Status status_;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<AttributeGroupIterator> NewAttributeGroupErrorIterator(
|
||||
const Status& status) {
|
||||
return std::make_unique<EmptyAttributeGroupIterator>(status);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1551,57 +1551,6 @@ TEST_P(DBBlobBasicIOErrorMultiGetTest, MultipleBlobFiles) {
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetFindTable_IOError) {
|
||||
// Repro test for a specific bug where `MultiGet()` would fail to open a table
|
||||
// in `FindTable()` and then proceed to return raw blob handles for the other
|
||||
// keys.
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Force no table cache so every read will preload the SST file.
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
|
||||
constexpr size_t num_keys = 2;
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr char value1[] = "blob1";
|
||||
|
||||
ASSERT_OK(Put(key1, value1));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr char value2[] = "blob2";
|
||||
|
||||
ASSERT_OK(Put(key2, value2));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::atomic<int> num_files_opened = 0;
|
||||
// This test would be more realistic if we injected an `IOError` from the
|
||||
// `FileSystem`
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::MultiGet:FindTable", [&](void* status) {
|
||||
num_files_opened++;
|
||||
if (num_files_opened == 2) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::array<Slice, num_keys> keys{{key1, key2}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
keys.data(), values.data(), statuses.data());
|
||||
|
||||
ASSERT_TRUE(statuses[0].IsIOError());
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(value2, values[1]);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadBlobCompactionFilter : public CompactionFilter {
|
||||
|
||||
+3
-7
@@ -210,7 +210,6 @@ Status BuildTable(
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
SequenceNumber smallest_preferred_seqno = kMaxSequenceNumber;
|
||||
std::string key_after_flush_buf;
|
||||
std::string value_buf;
|
||||
c_iter.SeekToFirst();
|
||||
@@ -243,8 +242,6 @@ Status BuildTable(
|
||||
if (preferred_seqno < ikey.sequence) {
|
||||
value_after_flush =
|
||||
PackValueAndSeqno(unpacked_value, preferred_seqno, &value_buf);
|
||||
smallest_preferred_seqno =
|
||||
std::min(smallest_preferred_seqno, preferred_seqno);
|
||||
} else {
|
||||
// Cannot get a useful preferred seqno, convert it to a kTypeValue.
|
||||
UpdateInternalKey(&key_after_flush_buf, ikey.sequence, kTypeValue);
|
||||
@@ -329,10 +326,9 @@ Status BuildTable(
|
||||
} else {
|
||||
SeqnoToTimeMapping relevant_mapping;
|
||||
if (seqno_to_time_mapping) {
|
||||
relevant_mapping.CopyFromSeqnoRange(
|
||||
*seqno_to_time_mapping,
|
||||
std::min(meta->fd.smallest_seqno, smallest_preferred_seqno),
|
||||
meta->fd.largest_seqno);
|
||||
relevant_mapping.CopyFromSeqnoRange(*seqno_to_time_mapping,
|
||||
meta->fd.smallest_seqno,
|
||||
meta->fd.largest_seqno);
|
||||
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
|
||||
relevant_mapping.Enforce(tboptions.file_creation_time);
|
||||
}
|
||||
|
||||
@@ -1318,8 +1318,6 @@ int main(int argc, char** argv) {
|
||||
policy = rocksdb_filterpolicy_create_ribbon_hybrid(8.0, 1);
|
||||
}
|
||||
rocksdb_block_based_options_set_filter_policy(table_options, policy);
|
||||
rocksdb_block_based_options_set_optimize_filters_for_memory(table_options,
|
||||
0);
|
||||
|
||||
// Create new database
|
||||
rocksdb_close(db);
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/coalescing_iterator.h"
|
||||
|
||||
#include "db/wide/wide_columns_helper.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void CoalescingIterator::Coalesce(
|
||||
const autovector<MultiCfIteratorInfo>& items) {
|
||||
assert(wide_columns_.empty());
|
||||
MinHeap heap;
|
||||
for (const auto& item : items) {
|
||||
assert(item.iterator);
|
||||
for (auto& column : item.iterator->columns()) {
|
||||
heap.push(WideColumnWithOrder{&column, item.order});
|
||||
}
|
||||
}
|
||||
if (heap.empty()) {
|
||||
return;
|
||||
}
|
||||
wide_columns_.reserve(heap.size());
|
||||
auto current = heap.top();
|
||||
heap.pop();
|
||||
while (!heap.empty()) {
|
||||
int comparison = current.column->name().compare(heap.top().column->name());
|
||||
if (comparison < 0) {
|
||||
wide_columns_.push_back(*current.column);
|
||||
} else if (comparison > 0) {
|
||||
// Shouldn't reach here.
|
||||
// Current item in the heap is greater than the top item in the min heap
|
||||
assert(false);
|
||||
}
|
||||
current = heap.top();
|
||||
heap.pop();
|
||||
}
|
||||
wide_columns_.push_back(*current.column);
|
||||
|
||||
if (WideColumnsHelper::HasDefaultColumn(wide_columns_)) {
|
||||
value_ = WideColumnsHelper::GetDefaultColumn(wide_columns_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/multi_cf_iterator_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// EXPERIMENTAL
|
||||
class CoalescingIterator : public Iterator {
|
||||
public:
|
||||
CoalescingIterator(const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
Coalesce(items);
|
||||
}) {}
|
||||
~CoalescingIterator() override {}
|
||||
|
||||
// No copy allowed
|
||||
CoalescingIterator(const CoalescingIterator&) = delete;
|
||||
CoalescingIterator& operator=(const CoalescingIterator&) = delete;
|
||||
|
||||
bool Valid() const override { return impl_.Valid(); }
|
||||
void SeekToFirst() override { impl_.SeekToFirst(); }
|
||||
void SeekToLast() override { impl_.SeekToLast(); }
|
||||
void Seek(const Slice& target) override { impl_.Seek(target); }
|
||||
void SeekForPrev(const Slice& target) override { impl_.SeekForPrev(target); }
|
||||
void Next() override { impl_.Next(); }
|
||||
void Prev() override { impl_.Prev(); }
|
||||
Slice key() const override { return impl_.key(); }
|
||||
Status status() const override { return impl_.status(); }
|
||||
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return value_;
|
||||
}
|
||||
const WideColumns& columns() const override {
|
||||
assert(Valid());
|
||||
return wide_columns_;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
value_.clear();
|
||||
wide_columns_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
Slice value_;
|
||||
WideColumns wide_columns_;
|
||||
|
||||
struct WideColumnWithOrder {
|
||||
const WideColumn* column;
|
||||
int order;
|
||||
};
|
||||
|
||||
class WideColumnWithOrderComparator {
|
||||
public:
|
||||
explicit WideColumnWithOrderComparator() {}
|
||||
bool operator()(const WideColumnWithOrder& a,
|
||||
const WideColumnWithOrder& b) const {
|
||||
int c = a.column->name().compare(b.column->name());
|
||||
return c == 0 ? a.order - b.order > 0 : c > 0;
|
||||
}
|
||||
};
|
||||
|
||||
using MinHeap =
|
||||
BinaryHeap<WideColumnWithOrder, WideColumnWithOrderComparator>;
|
||||
|
||||
void Coalesce(const autovector<MultiCfIteratorInfo>& items);
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -441,50 +441,6 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, CompressionWithBlockAlign) {
|
||||
Options options;
|
||||
options.compression = CompressionType::kNoCompression;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
std::shared_ptr<FlushedFileCollector> collector =
|
||||
std::make_shared<FlushedFileCollector>();
|
||||
options.listeners.push_back(collector);
|
||||
|
||||
{
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.block_align = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
}
|
||||
|
||||
std::unique_ptr<DB> db;
|
||||
{
|
||||
DB* _db = nullptr;
|
||||
ASSERT_OK(DB::Open(options, db_name_, &_db));
|
||||
db.reset(_db);
|
||||
}
|
||||
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key", "val"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Ensure background work is fully finished including listener callbacks
|
||||
// before accessing listener state.
|
||||
ASSERT_OK(
|
||||
static_cast_with_check<DBImpl>(db.get())->TEST_WaitForBackgroundWork());
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_EQ(1, l0_files.size());
|
||||
|
||||
// We can run this test even without Snappy support because we expect the
|
||||
// `CompactFiles()` to fail before actually invoking Snappy compression.
|
||||
CompactionOptions compaction_opts;
|
||||
compaction_opts.compression = CompressionType::kSnappyCompression;
|
||||
ASSERT_TRUE(db->CompactFiles(compaction_opts, l0_files, 1 /* output_level */)
|
||||
.IsInvalidArgument());
|
||||
|
||||
compaction_opts.compression = CompressionType::kDisableCompressionOption;
|
||||
ASSERT_OK(db->CompactFiles(compaction_opts, l0_files, 1 /* output_level */));
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, GetCompactionJobInfo) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -997,37 +997,25 @@ void CompactionIterator::NextFromInput() {
|
||||
// A special case involving range deletion is handled separately below.
|
||||
auto [unpacked_value, preferred_seqno] =
|
||||
ParsePackedValueWithSeqno(value_);
|
||||
assert(preferred_seqno < ikey_.sequence || ikey_.sequence == 0);
|
||||
assert(preferred_seqno < ikey_.sequence);
|
||||
InternalKey ikey_after_swap(ikey_.user_key, preferred_seqno, kTypeValue);
|
||||
Slice ikey_after_swap_slice(*ikey_after_swap.rep());
|
||||
if (range_del_agg_->ShouldDelete(
|
||||
key_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
AdvanceInputIter();
|
||||
ikey_after_swap_slice,
|
||||
RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// A range tombstone that doesn't cover this kTypeValuePreferredSeqno
|
||||
// entry may end up covering the entry, so it's not safe to swap
|
||||
// preferred sequence number. In this case, we output the entry as is.
|
||||
validity_info_.SetValid(ValidContext::kNewUserKey);
|
||||
} else {
|
||||
InternalKey ikey_after_swap(ikey_.user_key,
|
||||
std::min(preferred_seqno, ikey_.sequence),
|
||||
kTypeValue);
|
||||
Slice ikey_after_swap_slice(*ikey_after_swap.rep());
|
||||
if (range_del_agg_->ShouldDelete(
|
||||
ikey_after_swap_slice,
|
||||
RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// A range tombstone that doesn't cover this kTypeValuePreferredSeqno
|
||||
// entry will end up covering the entry, so it's not safe to swap
|
||||
// preferred sequence number. In this case, we output the entry as is.
|
||||
validity_info_.SetValid(ValidContext::kNewUserKey);
|
||||
} else {
|
||||
if (ikey_.sequence != 0) {
|
||||
iter_stats_.num_timed_put_swap_preferred_seqno++;
|
||||
saved_seq_for_penul_check_ = ikey_.sequence;
|
||||
ikey_.sequence = preferred_seqno;
|
||||
}
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
value_ = unpacked_value;
|
||||
validity_info_.SetValid(ValidContext::kSwapPreferredSeqno);
|
||||
}
|
||||
iter_stats_.num_timed_put_swap_preferred_seqno++;
|
||||
ikey_.sequence = preferred_seqno;
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
value_ = unpacked_value;
|
||||
validity_info_.SetValid(ValidContext::kSwapPreferredSeqno);
|
||||
}
|
||||
} else if (ikey_.type == kTypeMerge) {
|
||||
if (!merge_helper_->HasOperator()) {
|
||||
@@ -1280,21 +1268,6 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
// saved_seq_for_penul_check_ is populated in `NextFromInput` when the
|
||||
// entry's sequence number is non zero and validity context for output this
|
||||
// entry is kSwapPreferredSeqno for use in `DecideOutputLevel`. It should be
|
||||
// cleared out here unconditionally. Otherwise, it may end up getting consumed
|
||||
// incorrectly by a different entry.
|
||||
SequenceNumber seq_for_range_check =
|
||||
(saved_seq_for_penul_check_.has_value() &&
|
||||
saved_seq_for_penul_check_.value() != kMaxSequenceNumber)
|
||||
? saved_seq_for_penul_check_.value()
|
||||
: ikey_.sequence;
|
||||
saved_seq_for_penul_check_ = std::nullopt;
|
||||
ParsedInternalKey ikey_for_range_check = ikey_;
|
||||
if (seq_for_range_check != ikey_.sequence) {
|
||||
ikey_for_range_check.sequence = seq_for_range_check;
|
||||
}
|
||||
if (output_to_penultimate_level_) {
|
||||
// If it's decided to output to the penultimate level, but unsafe to do so,
|
||||
// still output to the last level. For example, moving the data from a lower
|
||||
@@ -1303,7 +1276,7 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
// not from this compaction.
|
||||
// TODO: add statistic for declined output_to_penultimate_level
|
||||
bool safe_to_penultimate_level =
|
||||
compaction_->WithinPenultimateLevelOutputRange(ikey_for_range_check);
|
||||
compaction_->WithinPenultimateLevelOutputRange(ikey_);
|
||||
if (!safe_to_penultimate_level) {
|
||||
output_to_penultimate_level_ = false;
|
||||
// It could happen when disable/enable `last_level_temperature` while
|
||||
@@ -1315,7 +1288,7 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
// snapshot is released before enabling `last_level_temperature` feature
|
||||
// We will migrate the feature to `last_level_temperature` and maybe make
|
||||
// it not dynamically changeable.
|
||||
if (seq_for_range_check > earliest_snapshot_) {
|
||||
if (ikey_.sequence > earliest_snapshot_) {
|
||||
status_ = Status::Corruption(
|
||||
"Unsafe to store Seq later than snapshot in the last level if "
|
||||
"per_key_placement is enabled");
|
||||
|
||||
@@ -437,17 +437,6 @@ class CompactionIterator {
|
||||
// iterator output (or current key in the underlying iterator during
|
||||
// NextFromInput()).
|
||||
ParsedInternalKey ikey_;
|
||||
|
||||
// When a kTypeValuePreferredSeqno entry's preferred seqno is safely swapped
|
||||
// in in this compaction, this field saves its original sequence number for
|
||||
// range checking whether it's safe to be placed on the penultimate level.
|
||||
// This is to ensure when such an entry happens to be the right boundary of
|
||||
// penultimate safe range, it won't get excluded because with the preferred
|
||||
// seqno swapped in, it's now larger than the right boundary (itself before
|
||||
// the swap). This is safe to do, because preferred seqno is swapped in only
|
||||
// when no entries with the same user key exist on lower levels and this entry
|
||||
// is already visible in the earliest snapshot.
|
||||
std::optional<SequenceNumber> saved_seq_for_penul_check_ = kMaxSequenceNumber;
|
||||
// Stores whether ikey_.user_key is valid. If set to false, the user key is
|
||||
// not compared against the current key in the underlying iterator.
|
||||
bool has_current_user_key_ = false;
|
||||
|
||||
@@ -1260,28 +1260,6 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
false /*key_not_exists_beyond_output_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
TimedPut_ShouldBeCoverredByRangeDeletionBeforeSwap_NoOutput) {
|
||||
InitIterators({test::KeyStr("morning", 5, kTypeValuePreferredSeqno),
|
||||
test::KeyStr("morning", 2, kTypeValuePreferredSeqno),
|
||||
test::KeyStr("night", 6, kTypeValue)},
|
||||
{ValueWithPreferredSeqno("zao", 3),
|
||||
ValueWithPreferredSeqno("zao", 1), "wan"},
|
||||
{test::KeyStr("ma", 6, kTypeRangeDeletion)}, {"mz"}, 6,
|
||||
kMaxSequenceNumber /*last_committed_sequence*/,
|
||||
nullptr /*merge_op*/, nullptr /*filter*/,
|
||||
false /*bottommost_level*/,
|
||||
kMaxSequenceNumber /*earliest_write_conflict_snapshot*/,
|
||||
true /*key_not_exists_beyond_output_level*/);
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("night", 6, kTypeValue), c_iter_->key().ToString());
|
||||
ASSERT_EQ("wan", c_iter_->value().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
ASSERT_OK(c_iter_->status());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
TimedPut_WillBeHiddenByRangeDeletionAfterSwap_NoSwap) {
|
||||
InitIterators({test::KeyStr("morning", 5, kTypeValuePreferredSeqno),
|
||||
@@ -1338,19 +1316,6 @@ TEST_F(
|
||||
true /*key_not_exists_beyond_output_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
TimedPut_SequenceNumberAlreadyZeroedOut_ChangeType) {
|
||||
RunTest(
|
||||
{test::KeyStr("bar", 0, kTypeValuePreferredSeqno),
|
||||
test::KeyStr("bar", 0, kTypeValuePreferredSeqno),
|
||||
test::KeyStr("foo", 0, kTypeValue)},
|
||||
{ValueWithPreferredSeqno("bv2", 2), ValueWithPreferredSeqno("bv1", 1),
|
||||
"fv1"},
|
||||
{test::KeyStr("bar", 0, kTypeValue), test::KeyStr("foo", 0, kTypeValue)},
|
||||
{"bv2", "fv1"}, 6 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// Compaction filter should keep uncommitted key as-is, and
|
||||
// * Convert the latest value to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all subsequent merges.
|
||||
|
||||
@@ -27,9 +27,7 @@ Status CompactionOutputs::Finish(
|
||||
if (s.ok()) {
|
||||
SeqnoToTimeMapping relevant_mapping;
|
||||
relevant_mapping.CopyFromSeqnoRange(
|
||||
seqno_to_time_mapping,
|
||||
std::min(smallest_preferred_seqno_, meta->fd.smallest_seqno),
|
||||
meta->fd.largest_seqno);
|
||||
seqno_to_time_mapping, meta->fd.smallest_seqno, meta->fd.largest_seqno);
|
||||
relevant_mapping.SetCapacity(kMaxSeqnoTimePairsPerSST);
|
||||
builder_->SetSeqnoTimeTableProperties(relevant_mapping,
|
||||
meta->oldest_ancester_time);
|
||||
@@ -424,11 +422,6 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
if (ikey.type == kTypeValuePreferredSeqno) {
|
||||
SequenceNumber preferred_seqno = ParsePackedValueForSeqno(value);
|
||||
smallest_preferred_seqno_ =
|
||||
std::min(smallest_preferred_seqno_, preferred_seqno);
|
||||
}
|
||||
s = current_output().meta.UpdateBoundaries(key, value, ikey.sequence,
|
||||
ikey.type);
|
||||
|
||||
|
||||
@@ -297,7 +297,6 @@ class CompactionOutputs {
|
||||
std::unique_ptr<TableBuilder> builder_;
|
||||
std::unique_ptr<WritableFileWriter> file_writer_;
|
||||
uint64_t current_output_file_size_ = 0;
|
||||
SequenceNumber smallest_preferred_seqno_ = kMaxSequenceNumber;
|
||||
|
||||
// all the compaction outputs so far
|
||||
std::vector<Output> outputs_;
|
||||
|
||||
@@ -457,10 +457,9 @@ bool CompactionPicker::IsRangeInCompaction(VersionStorageInfo* vstorage,
|
||||
// Returns false if files on parent level are currently in compaction, which
|
||||
// means that we can't compact them
|
||||
bool CompactionPicker::SetupOtherInputs(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
|
||||
CompactionInputFiles* output_level_inputs, int* parent_index,
|
||||
int base_index, bool only_expand_towards_right) {
|
||||
const std::string& cf_name, VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs, CompactionInputFiles* output_level_inputs,
|
||||
int* parent_index, int base_index, bool only_expand_towards_right) {
|
||||
assert(!inputs->empty());
|
||||
assert(output_level_inputs->empty());
|
||||
const int input_level = inputs->level;
|
||||
@@ -526,15 +525,8 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
|
||||
try_overlapping_inputs = false;
|
||||
}
|
||||
// It helps to reduce write amp and avoid a further separate compaction
|
||||
// to include more input level files without expanding output level files.
|
||||
// So we apply a softer limit. We still need a limit to avoid overly large
|
||||
// compactions and potential high space amp spikes.
|
||||
const uint64_t limit =
|
||||
MultiplyCheckOverflow(mutable_cf_options.max_compaction_bytes, 2.0);
|
||||
if (try_overlapping_inputs && expanded_inputs.size() > inputs->size() &&
|
||||
!AreFilesInCompaction(expanded_inputs.files) &&
|
||||
output_level_inputs_size + expanded_inputs_size < limit) {
|
||||
!AreFilesInCompaction(expanded_inputs.files)) {
|
||||
InternalKey new_start, new_limit;
|
||||
GetRange(expanded_inputs, &new_start, &new_limit);
|
||||
CompactionInputFiles expanded_output_level_inputs;
|
||||
@@ -556,8 +548,7 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
base_index, nullptr);
|
||||
expanded_inputs_size = TotalFileSize(expanded_inputs.files);
|
||||
if (expanded_inputs.size() > inputs->size() &&
|
||||
!AreFilesInCompaction(expanded_inputs.files) &&
|
||||
(output_level_inputs_size + expanded_inputs_size) < limit) {
|
||||
!AreFilesInCompaction(expanded_inputs.files)) {
|
||||
expand_inputs = true;
|
||||
}
|
||||
}
|
||||
@@ -817,8 +808,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
output_level_inputs.level = output_level;
|
||||
if (input_level != output_level) {
|
||||
int parent_index = -1;
|
||||
if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, &inputs,
|
||||
&output_level_inputs, &parent_index, -1)) {
|
||||
if (!SetupOtherInputs(cf_name, vstorage, &inputs, &output_level_inputs,
|
||||
&parent_index, -1)) {
|
||||
// manual compaction is now multi-threaded, so it can
|
||||
// happen that SetupOtherInputs fails
|
||||
// we handle it higher in RunManualCompaction
|
||||
|
||||
@@ -186,7 +186,6 @@ class CompactionPicker {
|
||||
int penultimate_level) const;
|
||||
|
||||
bool SetupOtherInputs(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs,
|
||||
CompactionInputFiles* output_level_inputs,
|
||||
|
||||
@@ -467,9 +467,8 @@ bool LevelCompactionBuilder::SetupOtherInputsIfNeeded() {
|
||||
}
|
||||
if (!is_l0_trivial_move_ &&
|
||||
!compaction_picker_->SetupOtherInputs(
|
||||
cf_name_, mutable_cf_options_, vstorage_, &start_level_inputs_,
|
||||
&output_level_inputs_, &parent_index_, base_index_,
|
||||
round_robin_expanding)) {
|
||||
cf_name_, vstorage_, &start_level_inputs_, &output_level_inputs_,
|
||||
&parent_index_, base_index_, round_robin_expanding)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2465,7 +2465,7 @@ TEST_F(CompactionPickerTest, IsBottommostLevelTest) {
|
||||
DeleteVersionStorage();
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionLimitWhenAddFileFromInputLevel) {
|
||||
TEST_F(CompactionPickerTest, IgnoreCompactionLimitWhenAddFileFromInputLevel) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 800000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
@@ -2473,10 +2473,8 @@ TEST_F(CompactionPickerTest, CompactionLimitWhenAddFileFromInputLevel) {
|
||||
// A compaction should be triggered and pick file 2 and 5.
|
||||
// It pulls in other compaction input file from the input level L1
|
||||
// without pulling in more output level files.
|
||||
// Files 1, 3, 4 are eligible.
|
||||
// Files 1, 3, 4 will be included in the compaction.
|
||||
// File 6 is excluded since it overlaps with file 7.
|
||||
// It can expand input level since in this case, the limit on compaction size
|
||||
// is 2 * max_compaction_bytes.
|
||||
Add(1, 1U, "100", "150", 300000U);
|
||||
Add(1, 2U, "151", "200", 300001U, 0, 0);
|
||||
Add(1, 3U, "201", "250", 300000U, 0, 0);
|
||||
@@ -2500,38 +2498,6 @@ TEST_F(CompactionPickerTest, CompactionLimitWhenAddFileFromInputLevel) {
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, HitCompactionLimitWhenAddFileFromInputLevel) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 800000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// A compaction should be triggered and pick file 2 and 5.
|
||||
// It pulls in other compaction input file from the input level L1
|
||||
// without pulling in more output level files.
|
||||
// Files 1, 3, 4 are eligible.
|
||||
// File 6 is excluded since it overlaps with file 7.
|
||||
// It can not expand input level since total compaction size hit the limit
|
||||
// 2 * max_compaction_bytes.
|
||||
Add(1, 1U, "100", "150", 400000U);
|
||||
Add(1, 2U, "151", "200", 400001U, 0, 0);
|
||||
Add(1, 3U, "201", "250", 400000U, 0, 0);
|
||||
Add(1, 4U, "251", "300", 400000U, 0, 0);
|
||||
Add(1, 6U, "325", "400", 400000U, 0, 0);
|
||||
Add(2, 5U, "100", "350", 1U);
|
||||
Add(2, 7U, "375", "425", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, IsTrivialMoveOn) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 10001u;
|
||||
|
||||
@@ -1139,8 +1139,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
// from bottom_start_idx and bottom_end_idx, but for now, we use
|
||||
// SetupOtherInputs() for simplicity.
|
||||
int parent_index = -1; // Create and use bottom_start_idx?
|
||||
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
|
||||
&second_last_level_inputs,
|
||||
if (!picker_->SetupOtherInputs(cf_name_, vstorage_, &second_last_level_inputs,
|
||||
&bottom_level_inputs, &parent_index,
|
||||
/*base_index=*/-1)) {
|
||||
return nullptr;
|
||||
@@ -1311,9 +1310,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
int parent_index = -1;
|
||||
|
||||
output_level_inputs.level = output_level;
|
||||
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
|
||||
&start_level_inputs, &output_level_inputs,
|
||||
&parent_index, -1)) {
|
||||
if (!picker_->SetupOtherInputs(cf_name_, vstorage_, &start_level_inputs,
|
||||
&output_level_inputs, &parent_index, -1)) {
|
||||
return nullptr;
|
||||
}
|
||||
inputs.push_back(start_level_inputs);
|
||||
|
||||
@@ -1584,16 +1584,7 @@ TEST_F(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test Param: protection_bytes_per_key for WriteBatch
|
||||
class TimedPutPrecludeLastLevelTest
|
||||
: public PrecludeLastLevelTest,
|
||||
public testing::WithParamInterface<size_t> {
|
||||
public:
|
||||
TimedPutPrecludeLastLevelTest()
|
||||
: PrecludeLastLevelTest("timed_put_preclude_last_level_test") {}
|
||||
};
|
||||
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
TEST_F(PrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
const int kNumKeys = 100;
|
||||
@@ -1607,8 +1598,6 @@ TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
options.num_levels = kNumLevels;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
@@ -1617,7 +1606,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
});
|
||||
|
||||
for (int i = 0; i < kNumKeys / 2; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100), wo));
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(100)));
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(rnd.Uniform(2)));
|
||||
});
|
||||
@@ -1631,7 +1620,7 @@ TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
// These data are eligible to be put on the last level once written to db
|
||||
// and compaction will fast track them to the last level.
|
||||
for (int i = kNumKeys / 2; i < kNumKeys; i++) {
|
||||
ASSERT_OK(TimedPut(0, Key(i), rnd.RandomString(100), 50, wo));
|
||||
ASSERT_OK(TimedPut(0, Key(i), rnd.RandomString(100), 50));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
@@ -1651,92 +1640,6 @@ TEST_P(TimedPutPrecludeLastLevelTest, FastTrackTimedPutToLastLevel) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, InterleavedTimedPutAndPut) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.disable_auto_compactions = true;
|
||||
options.preclude_last_level_data_seconds = 1 * 24 * 60 * 60;
|
||||
options.env = mock_env_.get();
|
||||
options.num_levels = 7;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
|
||||
// Start time: kMockStartTime = 10000000;
|
||||
ASSERT_OK(TimedPut(0, Key(0), "v0", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
ASSERT_OK(Put(Key(1), "v1", wo));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(TimedPutPrecludeLastLevelTest, PreserveTimedPutOnPenultimateLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.disable_auto_compactions = true;
|
||||
options.preclude_last_level_data_seconds = 3 * 24 * 60 * 60;
|
||||
int seconds_between_recording = (3 * 24 * 60 * 60) / kMaxSeqnoTimePairsPerCF;
|
||||
options.env = mock_env_.get();
|
||||
options.num_levels = 7;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions wo;
|
||||
wo.protection_bytes_per_key = GetParam();
|
||||
|
||||
// Creating a snapshot to manually control when preferred sequence number is
|
||||
// swapped in. An entry's preferred seqno won't get swapped in until it's
|
||||
// visible to the earliest snapshot. With this, we can test relevant seqno to
|
||||
// time mapping recorded in SST file also covers preferred seqno, not just
|
||||
// the seqno in the internal keys.
|
||||
auto* snap1 = db_->GetSnapshot();
|
||||
// Start time: kMockStartTime = 10000000;
|
||||
ASSERT_OK(TimedPut(0, Key(0), "v0", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
ASSERT_OK(TimedPut(0, Key(1), "v1", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
ASSERT_OK(TimedPut(0, Key(2), "v2", kMockStartTime - 1 * 24 * 60 * 60, wo));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Should still be in penultimate level.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
// Wait one more day and release snapshot. Data's preferred seqno should be
|
||||
// swapped in, but data should still stay in penultimate level. SST file's
|
||||
// seqno to time mapping should continue to cover preferred seqno after
|
||||
// compaction.
|
||||
db_->ReleaseSnapshot(snap1);
|
||||
mock_clock_->MockSleepForSeconds(1 * 24 * 60 * 60);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kHot), 0);
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
// Wait one more day and data are eligible to be placed on last level.
|
||||
// Instead of waiting exactly one more day, here we waited
|
||||
// `seconds_between_recording` less seconds to show that it's not precise.
|
||||
// Data could start to be placed on cold tier one recording interval before
|
||||
// they exactly become cold based on the setting. For this one column family
|
||||
// setting preserving 3 days of recording, it's about 43 minutes.
|
||||
mock_clock_->MockSleepForSeconds(1 * 24 * 60 * 60 -
|
||||
seconds_between_recording);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kHot), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(TimedPutPrecludeLastLevelTest,
|
||||
TimedPutPrecludeLastLevelTest, ::testing::Values(0, 8));
|
||||
|
||||
TEST_F(PrecludeLastLevelTest, LastLevelOnlyCompactionPartial) {
|
||||
const int kNumTrigger = 4;
|
||||
const int kNumLevels = 7;
|
||||
|
||||
+12
-15
@@ -464,14 +464,12 @@ TEST_F(DBBasicTest, TimedPutBasic) {
|
||||
ASSERT_EQ("bv1", Get(1, "bar"));
|
||||
ASSERT_OK(TimedPut(1, "baz", "bzv1", /*write_unix_time=*/0));
|
||||
ASSERT_EQ("bzv1", Get(1, "baz"));
|
||||
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;
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), handles_[1], begin_rdel, end_rdel));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "bar"));
|
||||
}
|
||||
std::string range_del_begin = "b";
|
||||
std::string range_del_end = "baz";
|
||||
Slice begin_rdel = range_del_begin, end_rdel = range_del_end;
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), handles_[1], begin_rdel, end_rdel));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "bar"));
|
||||
|
||||
ASSERT_EQ("bzv1", Get(1, "baz"));
|
||||
ASSERT_OK(SingleDelete(1, "baz"));
|
||||
@@ -1415,7 +1413,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCF) {
|
||||
int get_sv_count = 0;
|
||||
ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check<DBImpl>(db_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (++get_sv_count == 2) {
|
||||
// After MultiGet refs a couple of CFs, flush all CFs so MultiGet
|
||||
// is forced to repeat the process
|
||||
@@ -1515,10 +1513,9 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFMutex) {
|
||||
int retries = 0;
|
||||
bool last_try = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::LastTry",
|
||||
[&](void* /*arg*/) { last_try = true; });
|
||||
"DBImpl::MultiGet::LastTry", [&](void* /*arg*/) { last_try = true; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (last_try) {
|
||||
return;
|
||||
}
|
||||
@@ -1534,10 +1531,10 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFMutex) {
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::MultiCFSnapshot::AfterLastTryRefSV",
|
||||
{"DBImpl::MultiGet::AfterLastTryRefSV",
|
||||
"DBMultiGetTestWithParam::MultiGetMultiCFMutex:BeforeCreateSV"},
|
||||
{"DBMultiGetTestWithParam::MultiGetMultiCFMutex:AfterCreateSV",
|
||||
"DBImpl::MultiCFSnapshot::BeforeLastTryUnRefSV"},
|
||||
"DBImpl::MultiGet::BeforeLastTryUnRefSV"},
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -1603,7 +1600,7 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) {
|
||||
int get_sv_count = 0;
|
||||
ROCKSDB_NAMESPACE::DBImpl* db = static_cast_with_check<DBImpl>(db_);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
"DBImpl::MultiGet::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (++get_sv_count == 2) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
|
||||
@@ -529,7 +529,6 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.filter_policy = Create(10, bfp_impl_);
|
||||
table_options.optimize_filters_for_memory = false;
|
||||
table_options.partition_filters = partition_filters_;
|
||||
if (partition_filters_) {
|
||||
table_options.index_type =
|
||||
@@ -1696,7 +1695,6 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy = policy;
|
||||
table_options.format_version = 5;
|
||||
table_options.optimize_filters_for_memory = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
@@ -6143,14 +6143,6 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<std::string> pending_compaction_cfs;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SchedulePendingCompaction::cfd", [&](void* arg) {
|
||||
const std::string& cf_name =
|
||||
static_cast<ColumnFamilyData*>(arg)->GetName();
|
||||
pending_compaction_cfs.emplace_back(cf_name);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Block all compact threads in thread pool.
|
||||
@@ -6205,29 +6197,7 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
}
|
||||
|
||||
// All CFs are pending compaction
|
||||
unsigned int tp_len = env_->GetThreadPoolQueueLen(Env::LOW);
|
||||
if (cf_count != tp_len) {
|
||||
// The test is flaky and fails the assertion below.
|
||||
// Print some debug information.
|
||||
uint64_t num_running_flushes = 0;
|
||||
if (db_->GetIntProperty(DB::Properties::kNumRunningFlushes,
|
||||
&num_running_flushes)) {
|
||||
fprintf(stdout, "Running flushes: %" PRIu64 "\n", num_running_flushes);
|
||||
}
|
||||
fprintf(stdout,
|
||||
"%zu CF in compaction queue: ", pending_compaction_cfs.size());
|
||||
for (const auto& cf_name : pending_compaction_cfs) {
|
||||
fprintf(stdout, "%s, ", cf_name.c_str());
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
|
||||
// print lsm
|
||||
for (unsigned int cf = 0; cf < cf_count; cf++) {
|
||||
fprintf(stdout, "%s: %s\n", cf_names[cf], FilesPerLevel(cf).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(cf_count, tp_len);
|
||||
ASSERT_EQ(cf_count, env_->GetThreadPoolQueueLen(Env::LOW));
|
||||
|
||||
// Unblock all compaction threads
|
||||
for (size_t i = 0; i < kTotalCompactTasks; i++) {
|
||||
@@ -9981,62 +9951,6 @@ TEST_F(DBCompactionTest, TurnOnLevelCompactionDynamicLevelBytesUCToLC) {
|
||||
ASSERT_EQ(expected_lsm, FilesPerLevel(1));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisallowRefitFilesFromNonL0ToL02) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
options.num_levels = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// To set up LSM shape:
|
||||
// L0
|
||||
// L1
|
||||
// L2:[a@1, k@3], [k@2, z@4] (sorted by ascending smallest key)
|
||||
// Both of these 2 files have epoch number = 1
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("a", "@1"));
|
||||
ASSERT_OK(Put("k", "@2"));
|
||||
const Snapshot* s2 = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("k", "@3"));
|
||||
ASSERT_OK(Put("z", "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
// Cut file between k@3 and k@2
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionOutputs::ShouldStopBefore::manual_decision",
|
||||
[options](void* p) {
|
||||
auto* pair = (std::pair<bool*, const Slice>*)p;
|
||||
if ((options.comparator->Compare(ExtractUserKey(pair->second), "k") ==
|
||||
0) &&
|
||||
(GetInternalKeySeqno(pair->second) == 2)) {
|
||||
*(pair->first) = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
Status s = dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel());
|
||||
std::vector<LiveFileMetaData> files;
|
||||
dbfull()->GetLiveFilesMetaData(&files);
|
||||
ASSERT_EQ(files.size(), 2);
|
||||
ASSERT_EQ(files[0].smallestkey, "a");
|
||||
ASSERT_EQ(files[0].largestkey, "k");
|
||||
ASSERT_EQ(files[1].smallestkey, "k");
|
||||
ASSERT_EQ(files[1].largestkey, "z");
|
||||
|
||||
// Disallow moving 2 non-L0 files to L0
|
||||
CompactRangeOptions cro2;
|
||||
cro2.change_level = true;
|
||||
cro2.target_level = 0;
|
||||
s = dbfull()->CompactRange(cro2, nullptr, nullptr);
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
|
||||
db_->ReleaseSnapshot(s1);
|
||||
db_->ReleaseSnapshot(s2);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DrainUnnecessaryLevelsAfterMultiplierChanged) {
|
||||
// When the level size multiplier increases such that fewer levels become
|
||||
// necessary, unnecessary levels should to be drained.
|
||||
|
||||
+4
-23
@@ -151,29 +151,6 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
size_t wal_size = files.size();
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Number of log files %" ROCKSDB_PRIszt " (%" ROCKSDB_PRIszt
|
||||
" required by manifest)",
|
||||
wal_size, required_by_manifest.size());
|
||||
#ifndef NDEBUG
|
||||
std::ostringstream wal_names;
|
||||
for (const auto& wal : files) {
|
||||
wal_names << wal->PathName() << " ";
|
||||
}
|
||||
|
||||
std::ostringstream wal_required_by_manifest_names;
|
||||
for (const auto& wal : required_by_manifest) {
|
||||
wal_required_by_manifest_names << wal << ".log ";
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Log files : %s .Log files required by manifest: %s.",
|
||||
wal_names.str().c_str(),
|
||||
wal_required_by_manifest_names.str().c_str());
|
||||
#endif // NDEBUG
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -394,6 +371,10 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
}
|
||||
|
||||
size_t wal_size = live_wal_files.size();
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Number of log files %" ROCKSDB_PRIszt, live_wal_files.size());
|
||||
|
||||
// Link WAL files. Copy exact size of last one because it is the only one
|
||||
// that has changes after the last flush.
|
||||
auto wal_dir = immutable_db_options_.GetWalDir();
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2024-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifdef OS_LINUX
|
||||
|
||||
class DBFollowerTest : public DBTestBase {
|
||||
public:
|
||||
// Create directories for leader and follower
|
||||
// Create the leader DB object
|
||||
DBFollowerTest() : DBTestBase("/db_follower_test", /*env_do_fsync*/ false) {
|
||||
follower_name_ = dbname_ + "/follower";
|
||||
Close();
|
||||
Destroy(CurrentOptions());
|
||||
EXPECT_EQ(env_->CreateDirIfMissing(dbname_), Status::OK());
|
||||
dbname_ = dbname_ + "/leader";
|
||||
Reopen(CurrentOptions());
|
||||
}
|
||||
|
||||
~DBFollowerTest() {
|
||||
follower_.reset();
|
||||
EXPECT_EQ(DestroyDB(follower_name_, CurrentOptions()), Status::OK());
|
||||
}
|
||||
|
||||
protected:
|
||||
Status OpenAsFollower() {
|
||||
return DB::OpenAsFollower(CurrentOptions(), follower_name_, dbname_,
|
||||
&follower_);
|
||||
}
|
||||
DB* follower() { return follower_.get(); }
|
||||
|
||||
private:
|
||||
std::string follower_name_;
|
||||
std::unique_ptr<DB> follower_;
|
||||
};
|
||||
|
||||
TEST_F(DBFollowerTest, Basic) {
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(OpenAsFollower());
|
||||
std::string val;
|
||||
ASSERT_OK(follower()->Get(ReadOptions(), "k1", &val));
|
||||
ASSERT_EQ(val, "v1");
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+136
-168
@@ -26,9 +26,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/attribute_group_iterator_impl.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/coalescing_iterator.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/convenience_impl.h"
|
||||
#include "db/db_info_dumper.h"
|
||||
@@ -47,6 +45,7 @@
|
||||
#include "db/memtable.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/multi_cf_iterator.h"
|
||||
#include "db/periodic_task_scheduler.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/table_cache.h"
|
||||
@@ -1854,7 +1853,6 @@ void DBImpl::SchedulePurge() {
|
||||
}
|
||||
|
||||
void DBImpl::BackgroundCallPurge() {
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallPurge:beforeMutexLock");
|
||||
mutex_.Lock();
|
||||
|
||||
while (!logs_to_free_queue_.empty()) {
|
||||
@@ -2517,12 +2515,12 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
return s;
|
||||
}
|
||||
|
||||
template <class T, typename IterDerefFuncType>
|
||||
Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
ReadCallback* callback,
|
||||
IterDerefFuncType iter_deref_func, T* cf_list,
|
||||
bool extra_sv_ref, SequenceNumber* snapshot,
|
||||
bool* sv_from_thread_local) {
|
||||
template <class T>
|
||||
Status DBImpl::MultiCFSnapshot(
|
||||
const ReadOptions& read_options, ReadCallback* callback,
|
||||
std::function<MultiGetColumnFamilyData*(typename T::iterator&)>&
|
||||
iter_deref_func,
|
||||
T* cf_list, SequenceNumber* snapshot, bool* sv_from_thread_local) {
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
assert(sv_from_thread_local);
|
||||
@@ -2539,7 +2537,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
SuperVersion* super_version = node->super_version;
|
||||
ColumnFamilyData* cfd = node->cfd;
|
||||
if (super_version != nullptr) {
|
||||
if (*sv_from_thread_local && !extra_sv_ref) {
|
||||
if (*sv_from_thread_local) {
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
} else {
|
||||
CleanupSuperVersion(super_version);
|
||||
@@ -2555,11 +2553,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
// super version
|
||||
auto cf_iter = cf_list->begin();
|
||||
auto node = iter_deref_func(cf_iter);
|
||||
if (extra_sv_ref) {
|
||||
node->super_version = node->cfd->GetReferencedSuperVersion(this);
|
||||
} else {
|
||||
node->super_version = GetAndRefSuperVersion(node->cfd);
|
||||
}
|
||||
node->super_version = GetAndRefSuperVersion(node->cfd);
|
||||
if (check_read_ts) {
|
||||
s = FailIfReadCollapsedHistory(node->cfd, node->super_version,
|
||||
*(read_options.timestamp));
|
||||
@@ -2606,7 +2600,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
}
|
||||
if (read_options.snapshot == nullptr) {
|
||||
if (last_try) {
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::LastTry");
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::LastTry");
|
||||
// We're close to max number of retries. For the last retry,
|
||||
// acquire the lock so we're sure to succeed
|
||||
mutex_.Lock();
|
||||
@@ -2621,15 +2615,11 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
++cf_iter) {
|
||||
auto node = iter_deref_func(cf_iter);
|
||||
if (!last_try) {
|
||||
if (extra_sv_ref) {
|
||||
node->super_version = node->cfd->GetReferencedSuperVersion(this);
|
||||
} else {
|
||||
node->super_version = GetAndRefSuperVersion(node->cfd);
|
||||
}
|
||||
node->super_version = GetAndRefSuperVersion(node->cfd);
|
||||
} else {
|
||||
node->super_version = node->cfd->GetSuperVersion()->Ref();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::AfterRefSV");
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::AfterRefSV");
|
||||
if (check_read_ts) {
|
||||
s = FailIfReadCollapsedHistory(node->cfd, node->super_version,
|
||||
*(read_options.timestamp));
|
||||
@@ -2643,7 +2633,6 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
break;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot");
|
||||
if (read_options.snapshot != nullptr || last_try) {
|
||||
// If user passed a snapshot, then we don't care if a memtable is
|
||||
// sealed or compaction happens because the snapshot would ensure
|
||||
@@ -2667,7 +2656,7 @@ Status DBImpl::MultiCFSnapshot(const ReadOptions& read_options,
|
||||
if (!retry) {
|
||||
if (last_try) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::AfterLastTryRefSV");
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::AfterLastTryRefSV");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2779,37 +2768,37 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
}
|
||||
PrepareMultiGetKeys(num_keys, sorted_input, &sorted_keys);
|
||||
|
||||
autovector<MultiGetKeyRangePerCf, MultiGetContext::MAX_BATCH_SIZE>
|
||||
key_range_per_cf;
|
||||
autovector<ColumnFamilySuperVersionPair, MultiGetContext::MAX_BATCH_SIZE>
|
||||
cf_sv_pairs;
|
||||
autovector<MultiGetColumnFamilyData, MultiGetContext::MAX_BATCH_SIZE>
|
||||
multiget_cf_data;
|
||||
size_t cf_start = 0;
|
||||
ColumnFamilyHandle* cf = sorted_keys[0]->column_family;
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
KeyContext* key_ctx = sorted_keys[i];
|
||||
if (key_ctx->column_family != cf) {
|
||||
key_range_per_cf.emplace_back(cf_start, i - cf_start);
|
||||
cf_sv_pairs.emplace_back(cf, nullptr);
|
||||
multiget_cf_data.emplace_back(cf, cf_start, i - cf_start, nullptr);
|
||||
cf_start = i;
|
||||
cf = key_ctx->column_family;
|
||||
}
|
||||
}
|
||||
|
||||
key_range_per_cf.emplace_back(cf_start, num_keys - cf_start);
|
||||
cf_sv_pairs.emplace_back(cf, nullptr);
|
||||
multiget_cf_data.emplace_back(cf, cf_start, num_keys - cf_start, nullptr);
|
||||
|
||||
SequenceNumber consistent_seqnum = kMaxSequenceNumber;
|
||||
bool sv_from_thread_local = false;
|
||||
Status s = MultiCFSnapshot<autovector<ColumnFamilySuperVersionPair,
|
||||
MultiGetContext::MAX_BATCH_SIZE>>(
|
||||
read_options, nullptr,
|
||||
[](autovector<ColumnFamilySuperVersionPair,
|
||||
MultiGetContext::MAX_BATCH_SIZE>::iterator& cf_iter) {
|
||||
return &(*cf_iter);
|
||||
},
|
||||
&cf_sv_pairs,
|
||||
/* extra_sv_ref */ false, &consistent_seqnum, &sv_from_thread_local);
|
||||
std::function<MultiGetColumnFamilyData*(
|
||||
autovector<MultiGetColumnFamilyData,
|
||||
MultiGetContext::MAX_BATCH_SIZE>::iterator&)>
|
||||
iter_deref_lambda =
|
||||
[](autovector<MultiGetColumnFamilyData,
|
||||
MultiGetContext::MAX_BATCH_SIZE>::iterator& cf_iter) {
|
||||
return &(*cf_iter);
|
||||
};
|
||||
|
||||
SequenceNumber consistent_seqnum;
|
||||
bool sv_from_thread_local;
|
||||
Status s = MultiCFSnapshot<
|
||||
autovector<MultiGetColumnFamilyData, MultiGetContext::MAX_BATCH_SIZE>>(
|
||||
read_options, nullptr, iter_deref_lambda, &multiget_cf_data,
|
||||
&consistent_seqnum, &sv_from_thread_local);
|
||||
|
||||
if (!s.ok()) {
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
@@ -2827,40 +2816,31 @@ void DBImpl::MultiGetCommon(const ReadOptions& read_options,
|
||||
read_callback = ×tamp_read_callback;
|
||||
}
|
||||
|
||||
assert(key_range_per_cf.size() == cf_sv_pairs.size());
|
||||
auto key_range_per_cf_iter = key_range_per_cf.begin();
|
||||
auto cf_sv_pair_iter = cf_sv_pairs.begin();
|
||||
while (key_range_per_cf_iter != key_range_per_cf.end() &&
|
||||
cf_sv_pair_iter != cf_sv_pairs.end()) {
|
||||
s = MultiGetImpl(read_options, key_range_per_cf_iter->start,
|
||||
key_range_per_cf_iter->num_keys, &sorted_keys,
|
||||
cf_sv_pair_iter->super_version, consistent_seqnum,
|
||||
auto cf_iter = multiget_cf_data.begin();
|
||||
for (; cf_iter != multiget_cf_data.end(); ++cf_iter) {
|
||||
s = MultiGetImpl(read_options, cf_iter->start, cf_iter->num_keys,
|
||||
&sorted_keys, cf_iter->super_version, consistent_seqnum,
|
||||
read_callback);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
++key_range_per_cf_iter;
|
||||
++cf_sv_pair_iter;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(s.IsTimedOut() || s.IsAborted());
|
||||
for (++key_range_per_cf_iter;
|
||||
key_range_per_cf_iter != key_range_per_cf.end();
|
||||
++key_range_per_cf_iter) {
|
||||
for (size_t i = key_range_per_cf_iter->start;
|
||||
i < key_range_per_cf_iter->start + key_range_per_cf_iter->num_keys;
|
||||
for (++cf_iter; cf_iter != multiget_cf_data.end(); ++cf_iter) {
|
||||
for (size_t i = cf_iter->start; i < cf_iter->start + cf_iter->num_keys;
|
||||
++i) {
|
||||
*sorted_keys[i]->s = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
for (const auto& iter : multiget_cf_data) {
|
||||
if (sv_from_thread_local) {
|
||||
ReturnAndCleanupSuperVersion(cf_sv_pair.cfd, cf_sv_pair.super_version);
|
||||
ReturnAndCleanupSuperVersion(iter.cfd, iter.super_version);
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::MultiCFSnapshot::BeforeLastTryUnRefSV");
|
||||
CleanupSuperVersion(cf_sv_pair.super_version);
|
||||
TEST_SYNC_POINT("DBImpl::MultiGet::BeforeLastTryUnRefSV");
|
||||
CleanupSuperVersion(iter.super_version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2992,18 +2972,21 @@ void DBImpl::MultiGetWithCallbackImpl(
|
||||
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
ReadCallback* callback,
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE>* sorted_keys) {
|
||||
std::array<ColumnFamilySuperVersionPair, 1> cf_sv_pairs;
|
||||
cf_sv_pairs[0] = ColumnFamilySuperVersionPair(column_family, nullptr);
|
||||
std::array<MultiGetColumnFamilyData, 1> multiget_cf_data;
|
||||
multiget_cf_data[0] = MultiGetColumnFamilyData(column_family, nullptr);
|
||||
std::function<MultiGetColumnFamilyData*(
|
||||
std::array<MultiGetColumnFamilyData, 1>::iterator&)>
|
||||
iter_deref_lambda =
|
||||
[](std::array<MultiGetColumnFamilyData, 1>::iterator& cf_iter) {
|
||||
return &(*cf_iter);
|
||||
};
|
||||
|
||||
size_t num_keys = sorted_keys->size();
|
||||
SequenceNumber consistent_seqnum = kMaxSequenceNumber;
|
||||
bool sv_from_thread_local = false;
|
||||
Status s = MultiCFSnapshot<std::array<ColumnFamilySuperVersionPair, 1>>(
|
||||
read_options, callback,
|
||||
[](std::array<ColumnFamilySuperVersionPair, 1>::iterator& cf_iter) {
|
||||
return &(*cf_iter);
|
||||
},
|
||||
&cf_sv_pairs,
|
||||
/* extra_sv_ref */ false, &consistent_seqnum, &sv_from_thread_local);
|
||||
SequenceNumber consistent_seqnum;
|
||||
bool sv_from_thread_local;
|
||||
Status s = MultiCFSnapshot<std::array<MultiGetColumnFamilyData, 1>>(
|
||||
read_options, callback, iter_deref_lambda, &multiget_cf_data,
|
||||
&consistent_seqnum, &sv_from_thread_local);
|
||||
if (!s.ok()) {
|
||||
return;
|
||||
}
|
||||
@@ -3042,11 +3025,11 @@ void DBImpl::MultiGetWithCallbackImpl(
|
||||
}
|
||||
|
||||
s = MultiGetImpl(read_options, 0, num_keys, sorted_keys,
|
||||
cf_sv_pairs[0].super_version, consistent_seqnum,
|
||||
multiget_cf_data[0].super_version, consistent_seqnum,
|
||||
read_callback);
|
||||
assert(s.ok() || s.IsTimedOut() || s.IsAborted());
|
||||
ReturnAndCleanupSuperVersion(cf_sv_pairs[0].cfd,
|
||||
cf_sv_pairs[0].super_version);
|
||||
ReturnAndCleanupSuperVersion(multiget_cf_data[0].cfd,
|
||||
multiget_cf_data[0].super_version);
|
||||
}
|
||||
|
||||
// The actual implementation of batched MultiGet. Parameters -
|
||||
@@ -3761,49 +3744,29 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> DBImpl::NewCoalescingIterator(
|
||||
std::unique_ptr<Iterator> DBImpl::NewMultiCfIterator(
|
||||
const ReadOptions& _read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) {
|
||||
return NewMultiCfIterator<Iterator, CoalescingIterator>(
|
||||
_read_options, column_families, [](const Status& s) {
|
||||
return std::unique_ptr<Iterator>(NewErrorIterator(s));
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<AttributeGroupIterator> DBImpl::NewAttributeGroupIterator(
|
||||
const ReadOptions& _read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) {
|
||||
return NewMultiCfIterator<AttributeGroupIterator, AttributeGroupIteratorImpl>(
|
||||
_read_options, column_families,
|
||||
[](const Status& s) { return NewAttributeGroupErrorIterator(s); });
|
||||
}
|
||||
|
||||
template <typename IterType, typename ImplType, typename ErrorIteratorFuncType>
|
||||
std::unique_ptr<IterType> DBImpl::NewMultiCfIterator(
|
||||
const ReadOptions& _read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
ErrorIteratorFuncType error_iterator_func) {
|
||||
if (column_families.size() == 0) {
|
||||
return error_iterator_func(
|
||||
Status::InvalidArgument("No Column Family was provided"));
|
||||
return std::unique_ptr<Iterator>(NewErrorIterator(
|
||||
Status::InvalidArgument("No Column Family was provided")));
|
||||
}
|
||||
const Comparator* first_comparator = column_families[0]->GetComparator();
|
||||
for (size_t i = 1; i < column_families.size(); ++i) {
|
||||
const Comparator* cf_comparator = column_families[i]->GetComparator();
|
||||
if (first_comparator != cf_comparator &&
|
||||
first_comparator->GetId().compare(cf_comparator->GetId()) != 0) {
|
||||
return error_iterator_func(Status::InvalidArgument(
|
||||
"Different comparators are being used across CFs"));
|
||||
return std::unique_ptr<Iterator>(NewErrorIterator(Status::InvalidArgument(
|
||||
"Different comparators are being used across CFs")));
|
||||
}
|
||||
}
|
||||
std::vector<Iterator*> child_iterators;
|
||||
Status s = NewIterators(_read_options, column_families, &child_iterators);
|
||||
if (!s.ok()) {
|
||||
return error_iterator_func(s);
|
||||
if (s.ok()) {
|
||||
return std::make_unique<MultiCfIterator>(first_comparator, column_families,
|
||||
std::move(child_iterators));
|
||||
}
|
||||
return std::make_unique<ImplType>(column_families[0]->GetComparator(),
|
||||
column_families,
|
||||
std::move(child_iterators));
|
||||
return std::unique_ptr<Iterator>(NewErrorIterator(s));
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
@@ -3828,62 +3791,69 @@ Status DBImpl::NewIterators(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
}
|
||||
|
||||
autovector<ColumnFamilySuperVersionPair, MultiGetContext::MAX_BATCH_SIZE>
|
||||
cf_sv_pairs;
|
||||
|
||||
Status s;
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
if (read_options.timestamp) {
|
||||
s = FailIfTsMismatchCf(cf, *(read_options.timestamp));
|
||||
} else {
|
||||
s = FailIfCfHasTs(cf);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
cf_sv_pairs.emplace_back(cf, nullptr);
|
||||
}
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
|
||||
SequenceNumber consistent_seqnum = kMaxSequenceNumber;
|
||||
bool sv_from_thread_local = false;
|
||||
s = MultiCFSnapshot<autovector<ColumnFamilySuperVersionPair,
|
||||
MultiGetContext::MAX_BATCH_SIZE>>(
|
||||
read_options, nullptr /* read_callback*/,
|
||||
[](autovector<ColumnFamilySuperVersionPair,
|
||||
MultiGetContext::MAX_BATCH_SIZE>::iterator& cf_iter) {
|
||||
return &(*cf_iter);
|
||||
},
|
||||
&cf_sv_pairs,
|
||||
/* extra_sv_ref */ true, &consistent_seqnum, &sv_from_thread_local);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(cf_sv_pairs.size() == column_families.size());
|
||||
if (read_options.tailing) {
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
auto iter = new ForwardIterator(this, read_options, cf_sv_pair.cfd,
|
||||
cf_sv_pair.super_version,
|
||||
/* allow_unprepared_value */ true);
|
||||
iterators->push_back(
|
||||
NewDBIterator(env_, read_options, *cf_sv_pair.cfd->ioptions(),
|
||||
cf_sv_pair.super_version->mutable_cf_options,
|
||||
cf_sv_pair.cfd->user_comparator(), iter,
|
||||
cf_sv_pair.super_version->current, kMaxSequenceNumber,
|
||||
cf_sv_pair.super_version->mutable_cf_options
|
||||
.max_sequential_skip_in_iterations,
|
||||
nullptr /*read_callback*/, cf_sv_pair.cfh));
|
||||
if (read_options.timestamp) {
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Status s = FailIfTsMismatchCf(cf, *(read_options.timestamp));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
iterators->push_back(NewIteratorImpl(
|
||||
read_options, cf_sv_pair.cfh, cf_sv_pair.super_version,
|
||||
consistent_seqnum, nullptr /*read_callback*/));
|
||||
for (auto* cf : column_families) {
|
||||
assert(cf);
|
||||
const Status s = FailIfCfHasTs(cf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
autovector<std::tuple<ColumnFamilyHandleImpl*, SuperVersion*>> cfh_to_sv;
|
||||
const bool check_read_ts =
|
||||
read_options.timestamp && read_options.timestamp->size() > 0;
|
||||
for (auto cf : column_families) {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(cf);
|
||||
auto cfd = cfh->cfd();
|
||||
SuperVersion* sv = cfd->GetReferencedSuperVersion(this);
|
||||
cfh_to_sv.emplace_back(cfh, sv);
|
||||
if (check_read_ts) {
|
||||
const Status s =
|
||||
FailIfReadCollapsedHistory(cfd, sv, *(read_options.timestamp));
|
||||
if (!s.ok()) {
|
||||
for (auto prev_entry : cfh_to_sv) {
|
||||
CleanupSuperVersion(std::get<1>(prev_entry));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(cfh_to_sv.size() == column_families.size());
|
||||
if (read_options.tailing) {
|
||||
for (auto [cfh, sv] : cfh_to_sv) {
|
||||
auto iter = new ForwardIterator(this, read_options, cfh->cfd(), sv,
|
||||
/* allow_unprepared_value */ true);
|
||||
iterators->push_back(NewDBIterator(
|
||||
env_, read_options, *cfh->cfd()->ioptions(), sv->mutable_cf_options,
|
||||
cfh->cfd()->user_comparator(), iter, sv->current, kMaxSequenceNumber,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
nullptr /*read_callback*/, cfh));
|
||||
}
|
||||
} else {
|
||||
// Note: no need to consider the special case of
|
||||
// last_seq_same_as_publish_seq_==false since NewIterators is overridden in
|
||||
// WritePreparedTxnDB
|
||||
auto snapshot = read_options.snapshot != nullptr
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: versions_->LastSequence();
|
||||
for (auto [cfh, sv] : cfh_to_sv) {
|
||||
iterators->push_back(NewIteratorImpl(read_options, cfh, sv, snapshot,
|
||||
nullptr /*read_callback*/));
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -3998,8 +3968,8 @@ DBImpl::CreateTimestampedSnapshotImpl(SequenceNumber snapshot_seq, uint64_t ts,
|
||||
std::shared_ptr<const SnapshotImpl> latest =
|
||||
timestamped_snapshots_.GetSnapshot(std::numeric_limits<uint64_t>::max());
|
||||
|
||||
// If there is already a latest timestamped snapshot, then we need to do
|
||||
// some checks.
|
||||
// If there is already a latest timestamped snapshot, then we need to do some
|
||||
// checks.
|
||||
if (latest) {
|
||||
uint64_t latest_snap_ts = latest->GetTimestamp();
|
||||
SequenceNumber latest_snap_seq = latest->GetSequenceNumber();
|
||||
@@ -4008,8 +3978,8 @@ DBImpl::CreateTimestampedSnapshotImpl(SequenceNumber snapshot_seq, uint64_t ts,
|
||||
Status status;
|
||||
std::shared_ptr<const SnapshotImpl> ret;
|
||||
if (latest_snap_ts > ts) {
|
||||
// A snapshot created later cannot have smaller timestamp than a
|
||||
// previous timestamped snapshot.
|
||||
// A snapshot created later cannot have smaller timestamp than a previous
|
||||
// timestamped snapshot.
|
||||
needs_create_snap = false;
|
||||
std::ostringstream oss;
|
||||
oss << "snapshot exists with larger timestamp " << latest_snap_ts << " > "
|
||||
@@ -4123,8 +4093,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
|
||||
// Calculate a new threshold, skipping those CFs where compactions are
|
||||
// scheduled. We do not do the same pass as the previous loop because
|
||||
// mutex might be unlocked during the loop, making the result
|
||||
// inaccurate.
|
||||
// mutex might be unlocked during the loop, making the result inaccurate.
|
||||
SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (CfdListContains(cf_scheduled, cfd) ||
|
||||
@@ -4552,8 +4521,7 @@ Status DBImpl::GetApproximateSizes(const SizeApproximationOptions& options,
|
||||
sizes[i] = 0;
|
||||
if (options.include_files) {
|
||||
sizes[i] += versions_->ApproximateSize(
|
||||
options, read_options, v, k1.Encode(), k2.Encode(),
|
||||
/*start_level=*/0,
|
||||
options, read_options, v, k1.Encode(), k2.Encode(), /*start_level=*/0,
|
||||
/*end_level=*/-1, TableReaderCaller::kUserApproximateSize);
|
||||
}
|
||||
if (options.include_memtables) {
|
||||
@@ -4838,9 +4806,9 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
auto* sv = GetAndRefSuperVersion(cfd);
|
||||
{
|
||||
// Without mutex, Version::GetColumnFamilyMetaData will have data race
|
||||
// with Compaction::MarkFilesBeingCompacted. One solution is to use mutex,
|
||||
// but this may cause regression. An alternative is to make
|
||||
// Without mutex, Version::GetColumnFamilyMetaData will have data race with
|
||||
// Compaction::MarkFilesBeingCompacted. One solution is to use mutex, but
|
||||
// this may cause regression. An alternative is to make
|
||||
// FileMetaData::being_compacted atomic, but it will make FileMetaData
|
||||
// non-copy-able. Another option is to separate these variables from
|
||||
// original FileMetaData struct, and this requires re-organization of data
|
||||
|
||||
+46
-86
@@ -50,7 +50,6 @@
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/attribute_groups.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
@@ -352,13 +351,9 @@ class DBImpl : public DB {
|
||||
const Snapshot* GetSnapshot() override;
|
||||
void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
|
||||
// EXPERIMENTAL
|
||||
std::unique_ptr<Iterator> NewCoalescingIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
|
||||
// EXPERIMENTAL
|
||||
std::unique_ptr<AttributeGroupIterator> NewAttributeGroupIterator(
|
||||
// UNDER CONSTRUCTION - DO NOT USE
|
||||
// Return a cross-column-family iterator from a consistent database state.
|
||||
std::unique_ptr<Iterator> NewMultiCfIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
|
||||
@@ -821,8 +816,6 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t MinLogNumberToKeep();
|
||||
|
||||
uint64_t MinLogNumberToRecycle();
|
||||
|
||||
// Returns the lower bound file number for SSTs that won't be deleted, even if
|
||||
// they're obsolete. This lower bound is used internally to prevent newly
|
||||
// created flush/compaction output files from being deleted before they're
|
||||
@@ -1059,8 +1052,7 @@ class DBImpl : public DB {
|
||||
static Status Open(const DBOptions& db_options, const std::string& name,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch, const bool batch_per_txn,
|
||||
const bool is_retry, bool* can_retry);
|
||||
const bool seq_per_batch, const bool batch_per_txn);
|
||||
|
||||
static IOStatus CreateAndNewDirectory(
|
||||
FileSystem* fs, const std::string& dirname,
|
||||
@@ -1421,8 +1413,9 @@ class DBImpl : public DB {
|
||||
autovector<ColumnFamilyData*> cfds_;
|
||||
autovector<const MutableCFOptions*> mutable_cf_opts_;
|
||||
autovector<autovector<VersionEdit*>> edit_lists_;
|
||||
// All existing data files (SST files and Blob files) found during DB::Open.
|
||||
std::vector<std::string> existing_data_files_;
|
||||
// Stale SST files to delete found upon recovery. This stores a mapping from
|
||||
// such a file's absolute path to its parent directory.
|
||||
std::unordered_map<std::string, std::string> files_to_delete_;
|
||||
bool is_new_db_ = false;
|
||||
};
|
||||
|
||||
@@ -1536,7 +1529,7 @@ class DBImpl : public DB {
|
||||
Status WriteRecoverableState();
|
||||
|
||||
// Actual implementation of Close()
|
||||
virtual Status CloseImpl();
|
||||
Status CloseImpl();
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
@@ -1548,9 +1541,9 @@ class DBImpl : public DB {
|
||||
virtual Status Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_wal_file_exists = false,
|
||||
bool error_if_data_exists_in_wals = false, bool is_retry = false,
|
||||
bool error_if_data_exists_in_wals = false,
|
||||
uint64_t* recovered_seq = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr, bool* can_retry = nullptr);
|
||||
RecoveryContext* recovery_ctx = nullptr);
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
@@ -1560,36 +1553,22 @@ class DBImpl : public DB {
|
||||
// Assign db_id_ and write DB ID to manifest if necessary.
|
||||
void SetDBId(std::string&& id, bool read_only, RecoveryContext* recovery_ctx);
|
||||
|
||||
// Collect a deduplicated collection of paths used by this DB, including
|
||||
// dbname_, DBOptions.db_paths, ColumnFamilyOptions.cf_paths.
|
||||
std::set<std::string> CollectAllDBPaths();
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
// returns.
|
||||
// It stores all existing data files (SST and Blob) in RecoveryContext. In
|
||||
// the meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
// recovery_ctx stores the context about version edits. All those edits are
|
||||
// persisted to new Manifest after successfully syncing the new WAL.
|
||||
Status MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx);
|
||||
|
||||
// Track existing data files, including both referenced and unreferenced SST
|
||||
// and Blob files in SstFileManager. This is only called during DB::Open and
|
||||
// it's called before any file deletion start so that their deletion can be
|
||||
// properly rate limited.
|
||||
// Files may not be referenced in the MANIFEST because (e.g.
|
||||
// After recovery, there may be SST files in db/cf paths that are
|
||||
// not referenced in the MANIFEST (e.g.
|
||||
// 1. It's best effort recovery;
|
||||
// 2. The VersionEdits referencing the SST files are appended to
|
||||
// RecoveryContext, DB crashes when syncing the MANIFEST, the VersionEdits are
|
||||
// still not synced to MANIFEST during recovery.)
|
||||
//
|
||||
// If the file is referenced in Manifest (typically that's the
|
||||
// vast majority of all files), since it already has the file size
|
||||
// on record, we don't need to query the file system. Otherwise, we query the
|
||||
// file system for the size of an unreferenced file.
|
||||
void TrackExistingDataFiles(
|
||||
const std::vector<std::string>& existing_data_files);
|
||||
// It stores the SST files to be deleted in RecoveryContext. In the
|
||||
// meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
// recovery_ctx stores the context about version edits and files to be
|
||||
// deleted. All those edits are persisted to new Manifest after successfully
|
||||
// syncing the new WAL.
|
||||
Status DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx);
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
@@ -1944,7 +1923,7 @@ class DBImpl : public DB {
|
||||
// corrupted_log_found is set to true if we recover from a corrupted log file.
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool is_retry, bool* corrupted_log_found,
|
||||
bool* corrupted_log_found,
|
||||
RecoveryContext* recovery_ctx);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
@@ -2352,7 +2331,10 @@ class DBImpl : public DB {
|
||||
// A structure to hold the information required to process MultiGet of keys
|
||||
// belonging to one column family. For a multi column family MultiGet, there
|
||||
// will be a container of these objects.
|
||||
struct MultiGetKeyRangePerCf {
|
||||
struct MultiGetColumnFamilyData {
|
||||
ColumnFamilyHandle* cf;
|
||||
ColumnFamilyData* cfd;
|
||||
|
||||
// For the batched MultiGet which relies on sorted keys, start specifies
|
||||
// the index of first key belonging to this column family in the sorted
|
||||
// list.
|
||||
@@ -2362,33 +2344,31 @@ class DBImpl : public DB {
|
||||
// belonging to this column family in the sorted list
|
||||
size_t num_keys;
|
||||
|
||||
MultiGetKeyRangePerCf() : start(0), num_keys(0) {}
|
||||
|
||||
MultiGetKeyRangePerCf(size_t first, size_t count)
|
||||
: start(first), num_keys(count) {}
|
||||
};
|
||||
|
||||
// A structure to contain ColumnFamilyData and the SuperVersion obtained for
|
||||
// the consistent view of DB
|
||||
struct ColumnFamilySuperVersionPair {
|
||||
ColumnFamilyHandleImpl* cfh;
|
||||
ColumnFamilyData* cfd;
|
||||
|
||||
// SuperVersion for the column family obtained in a manner that ensures a
|
||||
// consistent view across all column families in the DB
|
||||
SuperVersion* super_version;
|
||||
ColumnFamilySuperVersionPair(ColumnFamilyHandle* column_family,
|
||||
SuperVersion* sv)
|
||||
: cfh(static_cast<ColumnFamilyHandleImpl*>(column_family)),
|
||||
cfd(cfh->cfd()),
|
||||
MultiGetColumnFamilyData(ColumnFamilyHandle* column_family,
|
||||
SuperVersion* sv)
|
||||
: cf(column_family),
|
||||
cfd(static_cast<ColumnFamilyHandleImpl*>(cf)->cfd()),
|
||||
start(0),
|
||||
num_keys(0),
|
||||
super_version(sv) {}
|
||||
|
||||
ColumnFamilySuperVersionPair() = default;
|
||||
MultiGetColumnFamilyData(ColumnFamilyHandle* column_family, size_t first,
|
||||
size_t count, SuperVersion* sv)
|
||||
: cf(column_family),
|
||||
cfd(static_cast<ColumnFamilyHandleImpl*>(cf)->cfd()),
|
||||
start(first),
|
||||
num_keys(count),
|
||||
super_version(sv) {}
|
||||
|
||||
MultiGetColumnFamilyData() = default;
|
||||
};
|
||||
|
||||
// A common function to obtain a consistent snapshot, which can be implicit
|
||||
// if the user doesn't specify a snapshot in read_options, across
|
||||
// multiple column families. It will attempt to get an implicit
|
||||
// multiple column families for MultiGet. It will attempt to get an implicit
|
||||
// snapshot without acquiring the db_mutes, but will give up after a few
|
||||
// tries and acquire the mutex if a memtable flush happens. The template
|
||||
// allows both the batched and non-batched MultiGet to call this with
|
||||
@@ -2397,26 +2377,18 @@ class DBImpl : public DB {
|
||||
// If callback is non-null, the callback is refreshed with the snapshot
|
||||
// sequence number
|
||||
//
|
||||
// `extra_sv_ref` is used to indicate whether thread-local SuperVersion
|
||||
// should be obtained with an extra ref (by GetReferencedSuperVersion()) or
|
||||
// not (by GetAndRefSuperVersion()). For instance, point lookup like MultiGet
|
||||
// does not require SuperVersion to be re-acquired throughout the entire
|
||||
// invocation (no need extra ref), while MultiCfIterators may need the
|
||||
// SuperVersion to be updated during Refresh() (requires extra ref).
|
||||
//
|
||||
// `sv_from_thread_local` being set to false indicates that the SuperVersion
|
||||
// obtained from the ColumnFamilyData, whereas true indicates they are thread
|
||||
// local.
|
||||
//
|
||||
// A non-OK status will be returned if for a column family that enables
|
||||
// user-defined timestamp feature, the specified `ReadOptions.timestamp`
|
||||
// attemps to read collapsed history.
|
||||
template <class T, typename IterDerefFuncType>
|
||||
Status MultiCFSnapshot(const ReadOptions& read_options,
|
||||
ReadCallback* callback,
|
||||
IterDerefFuncType iter_deref_func, T* cf_list,
|
||||
bool extra_sv_ref, SequenceNumber* snapshot,
|
||||
bool* sv_from_thread_local);
|
||||
template <class T>
|
||||
Status MultiCFSnapshot(
|
||||
const ReadOptions& read_options, ReadCallback* callback,
|
||||
std::function<MultiGetColumnFamilyData*(typename T::iterator&)>&
|
||||
iter_deref_func,
|
||||
T* cf_list, SequenceNumber* snapshot, bool* sv_from_thread_local);
|
||||
|
||||
// The actual implementation of the batching MultiGet. The caller is expected
|
||||
// to have acquired the SuperVersion and pass in a snapshot sequence number
|
||||
@@ -2439,13 +2411,6 @@ class DBImpl : public DB {
|
||||
|
||||
bool ShouldReferenceSuperVersion(const MergeContext& merge_context);
|
||||
|
||||
template <typename IterType, typename ImplType,
|
||||
typename ErrorIteratorFuncType>
|
||||
std::unique_ptr<IterType> NewMultiCfIterator(
|
||||
const ReadOptions& _read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
ErrorIteratorFuncType error_iterator_func);
|
||||
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
|
||||
@@ -2497,11 +2462,6 @@ class DBImpl : public DB {
|
||||
uint64_t logfile_number_;
|
||||
// Log files that we can recycle. Must be protected by db mutex_.
|
||||
std::deque<uint64_t> log_recycle_files_;
|
||||
// The minimum log file number taht can be recycled, if log recycling is
|
||||
// enabled. This is used to ensure that log files created by previous
|
||||
// instances of the database are not recycled, as we cannot be sure they
|
||||
// were created in the recyclable format.
|
||||
uint64_t min_log_number_to_recycle_;
|
||||
// Protected by log_write_mutex_.
|
||||
bool log_dir_synced_;
|
||||
// Without two_write_queues, read and writes to log_empty_ are protected by
|
||||
|
||||
@@ -1420,14 +1420,6 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
if (compact_options.compression !=
|
||||
CompressionType::kDisableCompressionOption) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Found use of deprecated option "
|
||||
"`CompactionOptions::compression`",
|
||||
cfd->GetName().c_str(), job_context.job_id);
|
||||
}
|
||||
|
||||
// Perform CompactFiles
|
||||
TEST_SYNC_POINT("TestCompactFiles::IngestExternalFile2");
|
||||
TEST_SYNC_POINT_CALLBACK("TestCompactFiles:PausingManualCompaction:3",
|
||||
@@ -1782,8 +1774,6 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
|
||||
// REQUIREMENT: block all background work by calling PauseBackgroundWork()
|
||||
// before calling this function
|
||||
// TODO (hx235): Replace Status::NotSupported() with Status::Aborted() for
|
||||
// better semantics like CompactFiles()
|
||||
Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
assert(level < cfd->NumberLevels());
|
||||
if (target_level >= cfd->NumberLevels()) {
|
||||
@@ -1819,8 +1809,6 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
if (to_level != level) {
|
||||
std::vector<CompactionInputFiles> input(1);
|
||||
input[0].level = level;
|
||||
// TODO (hx235): Only refit the output files in the current manual
|
||||
// compaction instead of all the files in the output level
|
||||
for (auto& f : vstorage->LevelFiles(level)) {
|
||||
input[0].files.push_back(f);
|
||||
}
|
||||
@@ -1852,12 +1840,6 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
}
|
||||
} else {
|
||||
// to_level < level
|
||||
if (to_level == 0 && input[0].files.size() > 1) {
|
||||
refitting_level_ = false;
|
||||
return Status::Aborted(
|
||||
"Moving more than 1 file from non-L0 to L0 is not allowed as it "
|
||||
"does not bring any benefit to read nor write throughput.");
|
||||
}
|
||||
// Check levels are empty for a trivial move
|
||||
for (int l = to_level; l < level; l++) {
|
||||
if (vstorage->NumLevelFiles(l) > 0) {
|
||||
@@ -3051,8 +3033,6 @@ void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
return;
|
||||
}
|
||||
if (!cfd->queued_for_compaction() && cfd->NeedsCompaction()) {
|
||||
TEST_SYNC_POINT_CALLBACK("SchedulePendingCompaction::cfd",
|
||||
static_cast<void*>(cfd));
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
@@ -4365,7 +4345,6 @@ Status DBImpl::WaitForCompact(
|
||||
}
|
||||
if ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ || unscheduled_compactions_ ||
|
||||
(wait_for_compact_options.wait_for_purge && bg_purge_scheduled_) ||
|
||||
unscheduled_flushes_ || error_handler_.IsRecoveryInProgress()) &&
|
||||
(error_handler_.GetBGError().ok())) {
|
||||
if (wait_for_compact_options.timeout.count()) {
|
||||
@@ -4373,7 +4352,6 @@ Status DBImpl::WaitForCompact(
|
||||
return Status::TimedOut();
|
||||
}
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::WaitForCompact:InsideLoop");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
} else if (wait_for_compact_options.close_db) {
|
||||
|
||||
+43
-19
@@ -28,8 +28,6 @@ uint64_t DBImpl::MinLogNumberToKeep() {
|
||||
return versions_->min_log_number_to_keep();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinLogNumberToRecycle() { return min_log_number_to_recycle_; }
|
||||
|
||||
uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
|
||||
mutex_.AssertHeld();
|
||||
if (!pending_outputs_.empty()) {
|
||||
@@ -177,10 +175,31 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
versions_->AddLiveFiles(&job_context->sst_live, &job_context->blob_live);
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
// PurgeObsoleteFiles will dedupe duplicate files.
|
||||
std::set<std::string> paths;
|
||||
for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size();
|
||||
path_id++) {
|
||||
paths.insert(immutable_db_options_.db_paths[path_id].path);
|
||||
}
|
||||
|
||||
// Note that if cf_paths is not specified in the ColumnFamilyOptions
|
||||
// of a particular column family, we use db_paths as the cf_paths
|
||||
// setting. Hence, there can be multiple duplicates of files from db_paths
|
||||
// in the following code. The duplicate are removed while identifying
|
||||
// unique files in PurgeObsoleteFiles.
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
for (size_t path_id = 0; path_id < cfd->ioptions()->cf_paths.size();
|
||||
path_id++) {
|
||||
auto& path = cfd->ioptions()->cf_paths[path_id].path;
|
||||
|
||||
if (paths.find(path) == paths.end()) {
|
||||
paths.insert(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IOOptions io_opts;
|
||||
io_opts.do_not_recurse = true;
|
||||
for (auto& path : CollectAllDBPaths()) {
|
||||
for (auto& path : paths) {
|
||||
// set of all files in the directory. We'll exclude files that are still
|
||||
// alive in the subsequent processings.
|
||||
std::vector<std::string> files;
|
||||
@@ -279,8 +298,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
if (immutable_db_options_.recycle_log_file_num >
|
||||
log_recycle_files_.size() &&
|
||||
earliest.number >= MinLogNumberToRecycle()) {
|
||||
log_recycle_files_.size()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"adding log %" PRIu64 " to recycle list\n",
|
||||
earliest.number);
|
||||
@@ -945,26 +963,28 @@ Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
return s;
|
||||
}
|
||||
|
||||
std::set<std::string> DBImpl::CollectAllDBPaths() {
|
||||
std::set<std::string> all_db_paths;
|
||||
all_db_paths.insert(NormalizePath(dbname_));
|
||||
Status DBImpl::DeleteUnreferencedSstFiles(RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(NormalizePath(dbname_ + std::string(1, kFilePathSeparator)));
|
||||
for (const auto& db_path : immutable_db_options_.db_paths) {
|
||||
all_db_paths.insert(NormalizePath(db_path.path));
|
||||
paths.push_back(
|
||||
NormalizePath(db_path.path + std::string(1, kFilePathSeparator)));
|
||||
}
|
||||
for (const auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
for (const auto& cf_path : cfd->ioptions()->cf_paths) {
|
||||
all_db_paths.insert(NormalizePath(cf_path.path));
|
||||
paths.push_back(
|
||||
NormalizePath(cf_path.path + std::string(1, kFilePathSeparator)));
|
||||
}
|
||||
}
|
||||
return all_db_paths;
|
||||
}
|
||||
// Dedup paths
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
|
||||
Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
uint64_t largest_file_number = next_file_number;
|
||||
Status s;
|
||||
for (const auto& path : CollectAllDBPaths()) {
|
||||
for (const auto& path : paths) {
|
||||
std::vector<std::string> files;
|
||||
s = env_->GetChildren(path, &files);
|
||||
if (!s.ok()) {
|
||||
@@ -976,10 +996,13 @@ Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
|
||||
if (!ParseFileName(fname, &number, &type)) {
|
||||
continue;
|
||||
}
|
||||
const std::string normalized_fpath = path + kFilePathSeparator + fname;
|
||||
// path ends with '/' or '\\'
|
||||
const std::string normalized_fpath = path + fname;
|
||||
largest_file_number = std::max(largest_file_number, number);
|
||||
if ((type == kTableFile || type == kBlobFile)) {
|
||||
recovery_ctx->existing_data_files_.push_back(normalized_fpath);
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
recovery_ctx->files_to_delete_.find(normalized_fpath) ==
|
||||
recovery_ctx->files_to_delete_.end()) {
|
||||
recovery_ctx->files_to_delete_.emplace(normalized_fpath, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -999,4 +1022,5 @@ Status DBImpl::MaybeUpdateNextFileNumber(RecoveryContext* recovery_ctx) {
|
||||
recovery_ctx->UpdateVersionEdits(default_cfd, edit);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,310 +0,0 @@
|
||||
// Copyright (c) 2024-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_impl/db_impl_follower.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "env/fs_on_demand.h"
|
||||
#include "logging/auto_roll_logger.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/configurable.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/write_batch_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
DBImplFollower::DBImplFollower(const DBOptions& db_options,
|
||||
std::unique_ptr<Env>&& env,
|
||||
const std::string& dbname, std::string src_path)
|
||||
: DBImplSecondary(db_options, dbname, ""),
|
||||
env_guard_(std::move(env)),
|
||||
stop_requested_(false),
|
||||
src_path_(std::move(src_path)),
|
||||
cv_(&mu_) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in follower mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
}
|
||||
|
||||
DBImplFollower::~DBImplFollower() {
|
||||
Status s = Close();
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Error closing DB : %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Recover a follower DB instance by reading the MANIFEST. The verification
|
||||
// as part of the MANIFEST replay will ensure that local links to the
|
||||
// leader's files are created, thus ensuring we can continue reading them
|
||||
// even if the leader deletes those files due to compaction.
|
||||
// TODO:
|
||||
// 1. Devise a mechanism to prevent misconfiguration by, for example,
|
||||
// keeping a local copy of the IDENTITY file and cross checking
|
||||
// 2. Make the recovery more robust by retrying if the first attempt
|
||||
// fails.
|
||||
Status DBImplFollower::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
||||
bool /*error_if_data_exists_in_wals*/, bool /*is_retry*/, uint64_t*,
|
||||
RecoveryContext* /*recovery_ctx*/, bool* /*can_retry*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
JobContext job_context(0);
|
||||
Status s;
|
||||
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
||||
->Recover(column_families, &manifest_reader_, &manifest_reporter_,
|
||||
&manifest_reader_status_);
|
||||
if (!s.ok()) {
|
||||
if (manifest_reader_status_) {
|
||||
manifest_reader_status_->PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok()) {
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
|
||||
// Start the periodic catch-up thread
|
||||
// TODO: See if it makes sense to have a threadpool, rather than a thread
|
||||
// per follower DB instance
|
||||
catch_up_thread_.reset(
|
||||
new port::Thread(&DBImplFollower::PeriodicRefresh, this));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Try to catch up by tailing the MANIFEST.
|
||||
// TODO:
|
||||
// 1. Cleanup obsolete files afterward
|
||||
// 2. Add some error notifications and statistics
|
||||
Status DBImplFollower::TryCatchUpWithLeader() {
|
||||
assert(versions_.get() != nullptr);
|
||||
assert(manifest_reader_.get() != nullptr);
|
||||
Status s;
|
||||
// read the manifest and apply new changes to the follower instance
|
||||
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
||||
JobContext job_context(0, true /*create_superversion*/);
|
||||
{
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
|
||||
->ReadAndApply(&mutex_, &manifest_reader_,
|
||||
manifest_reader_status_.get(), &cfds_changed);
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Last sequence is %" PRIu64,
|
||||
static_cast<uint64_t>(versions_->LastSequence()));
|
||||
for (ColumnFamilyData* cfd : cfds_changed) {
|
||||
if (cfd->IsDropped()) {
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] is dropped\n",
|
||||
cfd->GetName().c_str());
|
||||
continue;
|
||||
}
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] Level summary: %s\n", cfd->GetName().c_str(),
|
||||
cfd->current()->storage_info()->LevelSummary(&tmp));
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : cfds_changed) {
|
||||
if (cfd->mem()->GetEarliestSequenceNumber() <
|
||||
versions_->LastSequence()) {
|
||||
// Construct a new memtable with earliest sequence number set to the
|
||||
// last sequence number in the VersionSet. This matters when
|
||||
// DBImpl::MultiCFSnapshot tries to get consistent references
|
||||
// to super versions in a lock free manner, it checks the earliest
|
||||
// sequence number to detect if there was a change in version in
|
||||
// the meantime.
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
MemTable* new_mem = cfd->ConstructNewMemtable(
|
||||
mutable_cf_options, versions_->LastSequence());
|
||||
cfd->mem()->SetNextLogNumber(cfd->GetLogNumber());
|
||||
cfd->mem()->ConstructFragmentedRangeTombstones();
|
||||
cfd->imm()->Add(cfd->mem(), &job_context.memtables_to_free);
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
}
|
||||
|
||||
// This will check if the old memtable is still referenced
|
||||
cfd->imm()->RemoveOldMemTables(cfd->GetLogNumber(),
|
||||
&job_context.memtables_to_free);
|
||||
auto& sv_context = job_context.superversion_contexts.back();
|
||||
cfd->InstallSuperVersion(&sv_context, &mutex_);
|
||||
sv_context.NewSuperVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImplFollower::PeriodicRefresh() {
|
||||
while (!stop_requested_.load()) {
|
||||
MutexLock l(&mu_);
|
||||
int64_t wait_until =
|
||||
immutable_db_options_.clock->NowMicros() +
|
||||
immutable_db_options_.follower_refresh_catchup_period_ms * 1000;
|
||||
immutable_db_options_.clock->TimedWait(
|
||||
&cv_, std::chrono::microseconds(wait_until));
|
||||
if (stop_requested_.load()) {
|
||||
break;
|
||||
}
|
||||
Status s;
|
||||
for (uint64_t i = 0;
|
||||
i < immutable_db_options_.follower_catchup_retry_count &&
|
||||
!stop_requested_.load();
|
||||
++i) {
|
||||
s = TryCatchUpWithLeader();
|
||||
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Successful catch up on attempt %llu",
|
||||
static_cast<unsigned long long>(i));
|
||||
break;
|
||||
}
|
||||
wait_until = immutable_db_options_.clock->NowMicros() +
|
||||
immutable_db_options_.follower_catchup_retry_wait_ms * 1000;
|
||||
immutable_db_options_.clock->TimedWait(
|
||||
&cv_, std::chrono::microseconds(wait_until));
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Catch up unsuccessful");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImplFollower::Close() {
|
||||
if (catch_up_thread_) {
|
||||
stop_requested_.store(true);
|
||||
{
|
||||
MutexLock l(&mu_);
|
||||
cv_.SignalAll();
|
||||
}
|
||||
catch_up_thread_->join();
|
||||
catch_up_thread_.reset();
|
||||
}
|
||||
|
||||
return DBImpl::Close();
|
||||
}
|
||||
|
||||
Status DB::OpenAsFollower(const Options& options, const std::string& dbname,
|
||||
const std::string& leader_path,
|
||||
std::unique_ptr<DB>* dbptr) {
|
||||
dbptr->reset();
|
||||
|
||||
DBOptions db_options(options);
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
|
||||
Status s = DB::OpenAsFollower(db_options, dbname, leader_path,
|
||||
column_families, &handles, dbptr);
|
||||
if (s.ok()) {
|
||||
assert(handles.size() == 1);
|
||||
delete handles[0];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::OpenAsFollower(
|
||||
const DBOptions& db_options, const std::string& dbname,
|
||||
const std::string& src_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, std::unique_ptr<DB>* dbptr) {
|
||||
dbptr->reset();
|
||||
|
||||
FileSystem* fs = db_options.env->GetFileSystem().get();
|
||||
{
|
||||
IOStatus io_s;
|
||||
if (db_options.create_if_missing) {
|
||||
io_s = fs->CreateDirIfMissing(dbname, IOOptions(), nullptr);
|
||||
} else {
|
||||
io_s = fs->FileExists(dbname, IOOptions(), nullptr);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
return static_cast<Status>(io_s);
|
||||
}
|
||||
}
|
||||
std::unique_ptr<Env> new_env(new CompositeEnvWrapper(
|
||||
db_options.env, NewOnDemandFileSystem(db_options.env->GetFileSystem(),
|
||||
src_path, dbname)));
|
||||
|
||||
DBOptions tmp_opts(db_options);
|
||||
Status s;
|
||||
tmp_opts.env = new_env.get();
|
||||
if (nullptr == tmp_opts.info_log) {
|
||||
s = CreateLoggerFromOptions(dbname, tmp_opts, &tmp_opts.info_log);
|
||||
if (!s.ok()) {
|
||||
tmp_opts.info_log = nullptr;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
handles->clear();
|
||||
DBImplFollower* impl =
|
||||
new DBImplFollower(tmp_opts, std::move(new_env), dbname, src_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_, impl->io_tracer_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
|
||||
impl->mutex_.Lock();
|
||||
s = impl->Recover(column_families, /*read_only=*/true,
|
||||
/*error_if_wal_file_exists=*/false,
|
||||
/*error_if_data_exists_in_wals=*/false);
|
||||
if (s.ok()) {
|
||||
for (const auto& cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (nullptr == cfd) {
|
||||
s = Status::InvalidArgument("Column family not found", cf.name);
|
||||
break;
|
||||
}
|
||||
handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
}
|
||||
}
|
||||
SuperVersionContext sv_context(false /* create_superversion */);
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
sv_context.Clean();
|
||||
if (s.ok()) {
|
||||
dbptr->reset(impl);
|
||||
for (auto h : *handles) {
|
||||
impl->NewThreadStatusCfInfo(
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(h)->cfd());
|
||||
}
|
||||
} else {
|
||||
for (auto h : *handles) {
|
||||
delete h;
|
||||
}
|
||||
handles->clear();
|
||||
delete impl;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright (c) 2024-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_impl/db_impl_secondary.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBImplFollower : public DBImplSecondary {
|
||||
public:
|
||||
DBImplFollower(const DBOptions& db_options, std::unique_ptr<Env>&& env,
|
||||
const std::string& dbname, std::string src_path);
|
||||
~DBImplFollower();
|
||||
|
||||
Status Close() override;
|
||||
|
||||
protected:
|
||||
bool OwnTablesAndLogs() const override {
|
||||
// TODO: Change this to true once we've properly implemented file
|
||||
// deletion for the read scaling case
|
||||
return false;
|
||||
}
|
||||
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
||||
bool /*error_if_data_exists_in_wals*/,
|
||||
bool /*is_retry*/ = false, uint64_t* = nullptr,
|
||||
RecoveryContext* /*recovery_ctx*/ = nullptr,
|
||||
bool* /*can_retry*/ = nullptr) override;
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
Status TryCatchUpWithLeader();
|
||||
void PeriodicRefresh();
|
||||
|
||||
std::unique_ptr<Env> env_guard_;
|
||||
std::unique_ptr<port::Thread> catch_up_thread_;
|
||||
std::atomic<bool> stop_requested_;
|
||||
std::string src_path_;
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+103
-115
@@ -175,6 +175,15 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
}
|
||||
}
|
||||
}
|
||||
// When the DB is stopped, it's possible that there are some .trash files that
|
||||
// were not deleted yet, when we open the DB we will find these .trash files
|
||||
// and schedule them to be deleted (or delete immediately if SstFileManager
|
||||
// was not used)
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(result.sst_file_manager.get());
|
||||
for (size_t i = 0; i < result.db_paths.size(); i++) {
|
||||
DeleteScheduler::CleanupDirectory(result.env, sfm, result.db_paths[i].path)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
// Create a default SstFileManager for purposes of tracking compaction size
|
||||
// and facilitating recovery from out of space errors.
|
||||
@@ -404,8 +413,7 @@ IOStatus Directories::SetDirectories(FileSystem* fs, const std::string& dbname,
|
||||
Status DBImpl::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
bool error_if_wal_file_exists, bool error_if_data_exists_in_wals,
|
||||
bool is_retry, uint64_t* recovered_seq, RecoveryContext* recovery_ctx,
|
||||
bool* can_retry) {
|
||||
uint64_t* recovered_seq, RecoveryContext* recovery_ctx) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
@@ -521,31 +529,7 @@ Status DBImpl::Recover(
|
||||
Status s;
|
||||
bool missing_table_file = false;
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
// Status of reading the descriptor file
|
||||
Status desc_status;
|
||||
s = versions_->Recover(column_families, read_only, &db_id_,
|
||||
/*no_error_if_files_missing=*/false, is_retry,
|
||||
&desc_status);
|
||||
desc_status.PermitUncheckedError();
|
||||
if (can_retry) {
|
||||
// If we're opening for the first time and the failure is likely due to
|
||||
// a corrupt MANIFEST file (could result in either the log::Reader
|
||||
// detecting a corrupt record, or SST files not found error due to
|
||||
// discarding badly formed tail records)
|
||||
if (!is_retry &&
|
||||
(desc_status.IsCorruption() || s.IsNotFound() || s.IsCorruption()) &&
|
||||
CheckFSFeatureSupport(fs_.get(),
|
||||
FSSupportedOps::kVerifyAndReconstructRead)) {
|
||||
*can_retry = true;
|
||||
ROCKS_LOG_ERROR(
|
||||
immutable_db_options_.info_log,
|
||||
"Possible corruption detected while replaying MANIFEST %s, %s. "
|
||||
"Will be retried.",
|
||||
desc_status.ToString().c_str(), s.ToString().c_str());
|
||||
} else {
|
||||
*can_retry = false;
|
||||
}
|
||||
}
|
||||
s = versions_->Recover(column_families, read_only, &db_id_);
|
||||
} else {
|
||||
assert(!files_in_dbname.empty());
|
||||
s = versions_->TryRecover(column_families, read_only, files_in_dbname,
|
||||
@@ -660,7 +644,7 @@ Status DBImpl::Recover(
|
||||
s = SetupDBId(write_options, read_only, recovery_ctx);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
if (s.ok() && !read_only) {
|
||||
s = MaybeUpdateNextFileNumber(recovery_ctx);
|
||||
s = DeleteUnreferencedSstFiles(recovery_ctx);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
@@ -783,8 +767,8 @@ Status DBImpl::Recover(
|
||||
std::sort(wals.begin(), wals.end());
|
||||
|
||||
bool corrupted_wal_found = false;
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only, is_retry,
|
||||
&corrupted_wal_found, recovery_ctx);
|
||||
s = RecoverLogFiles(wals, &next_sequence, read_only, &corrupted_wal_found,
|
||||
recovery_ctx);
|
||||
if (corrupted_wal_found && recovered_seq != nullptr) {
|
||||
*recovered_seq = next_sequence;
|
||||
}
|
||||
@@ -962,6 +946,19 @@ Status DBImpl::LogAndApplyForRecovery(const RecoveryContext& recovery_ctx) {
|
||||
recovery_ctx.mutable_cf_opts_, read_options,
|
||||
write_options, recovery_ctx.edit_lists_,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
if (s.ok() && !(recovery_ctx.files_to_delete_.empty())) {
|
||||
mutex_.Unlock();
|
||||
for (const auto& stale_sst_file : recovery_ctx.files_to_delete_) {
|
||||
s = DeleteDBFile(&immutable_db_options_, stale_sst_file.first,
|
||||
stale_sst_file.second,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/false);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1081,7 +1078,7 @@ bool DBImpl::InvokeWalFilterIfNeededOnWalRecord(uint64_t wal_number,
|
||||
// REQUIRES: wal_numbers are sorted in ascending order
|
||||
Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool is_retry, bool* corrupted_wal_found,
|
||||
bool* corrupted_wal_found,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
@@ -1192,7 +1189,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(
|
||||
std::move(file), fname, immutable_db_options_.log_readahead_size,
|
||||
io_tracer_, /*listeners=*/{}, /*rate_limiter=*/nullptr, is_retry));
|
||||
io_tracer_));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
@@ -1836,12 +1833,8 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const bool kBatchPerTxn = true;
|
||||
ThreadStatusUtil::SetEnableTracking(db_options.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OperationType::OP_DBOPEN);
|
||||
bool can_retry = false;
|
||||
Status s;
|
||||
do {
|
||||
s = DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!kSeqPerBatch, kBatchPerTxn, can_retry, &can_retry);
|
||||
} while (!s.ok() && can_retry);
|
||||
Status s = DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
ThreadStatusUtil::ResetThreadStatus();
|
||||
return s;
|
||||
}
|
||||
@@ -1960,55 +1953,10 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
return io_s;
|
||||
}
|
||||
|
||||
void DBImpl::TrackExistingDataFiles(
|
||||
const std::vector<std::string>& existing_data_files) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
assert(sfm);
|
||||
std::vector<ColumnFamilyMetaData> metadata;
|
||||
GetAllColumnFamilyMetaData(&metadata);
|
||||
|
||||
std::unordered_set<std::string> referenced_files;
|
||||
for (const auto& md : metadata) {
|
||||
for (const auto& lmd : md.levels) {
|
||||
for (const auto& fmd : lmd.files) {
|
||||
// We're assuming that each sst file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path =
|
||||
fmd.directory + kFilePathSeparator + fmd.relative_filename;
|
||||
sfm->OnAddFile(file_path, fmd.size).PermitUncheckedError();
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
for (const auto& bmd : md.blob_files) {
|
||||
std::string name = bmd.blob_file_name;
|
||||
// The BlobMetaData.blob_file_name may start with "/".
|
||||
if (!name.empty() && name[0] == kFilePathSeparator) {
|
||||
name = name.substr(1);
|
||||
}
|
||||
// We're assuming that each blob file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path = bmd.blob_file_path + kFilePathSeparator + name;
|
||||
sfm->OnAddFile(file_path, bmd.blob_file_size).PermitUncheckedError();
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file_path : existing_data_files) {
|
||||
if (referenced_files.find(file_path) != referenced_files.end()) {
|
||||
continue;
|
||||
}
|
||||
// There shouldn't be any duplicated files. In case there is, SstFileManager
|
||||
// will take care of deduping it.
|
||||
sfm->OnAddFile(file_path).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch, const bool batch_per_txn,
|
||||
const bool is_retry, bool* can_retry) {
|
||||
const bool seq_per_batch, const bool batch_per_txn) {
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
const ReadOptions read_options(Env::IOActivity::kDBOpen);
|
||||
|
||||
@@ -2051,7 +1999,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
paths.emplace_back(cf_path.path);
|
||||
}
|
||||
}
|
||||
for (const auto& path : paths) {
|
||||
for (auto& path : paths) {
|
||||
s = impl->env_->CreateDirIfMissing(path);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
@@ -2081,8 +2029,8 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
uint64_t recovered_seq(kMaxSequenceNumber);
|
||||
s = impl->Recover(column_families, false /* read_only */,
|
||||
false /* error_if_wal_file_exists */,
|
||||
false /* error_if_data_exists_in_wals */, is_retry,
|
||||
&recovered_seq, &recovery_ctx, can_retry);
|
||||
false /* error_if_data_exists_in_wals */, &recovered_seq,
|
||||
&recovery_ctx);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
log::Writer* new_log = nullptr;
|
||||
@@ -2090,11 +2038,6 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size);
|
||||
s = impl->CreateWAL(write_options, new_log_number, 0 /*recycle_log_number*/,
|
||||
preallocate_block_size, &new_log);
|
||||
if (s.ok()) {
|
||||
// Prevent log files created by previous instance from being recycled.
|
||||
// They might be in alive_log_file_, and might get recycled otherwise.
|
||||
impl->min_log_number_to_recycle_ = new_log_number;
|
||||
}
|
||||
if (s.ok()) {
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
@@ -2222,6 +2165,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
|
||||
*dbptr = impl;
|
||||
impl->opened_successfully_ = true;
|
||||
impl->DeleteObsoleteFiles();
|
||||
TEST_SYNC_POINT("DBImpl::Open:AfterDeleteFiles");
|
||||
impl->MaybeScheduleFlushOrCompaction();
|
||||
} else {
|
||||
persist_options_status.PermitUncheckedError();
|
||||
}
|
||||
@@ -2236,10 +2182,73 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
ROCKS_LOG_INFO(impl->immutable_db_options_.info_log,
|
||||
"SstFileManager instance %p", sfm);
|
||||
|
||||
impl->TrackExistingDataFiles(recovery_ctx.existing_data_files_);
|
||||
// Notify SstFileManager about all sst files that already exist in
|
||||
// db_paths[0] and cf_paths[0] when the DB is opened.
|
||||
|
||||
// SstFileManagerImpl needs to know sizes of the files. For files whose size
|
||||
// we already know (sst files that appear in manifest - typically that's the
|
||||
// vast majority of all files), we'll pass the size to SstFileManager.
|
||||
// For all other files SstFileManager will query the size from filesystem.
|
||||
|
||||
std::vector<ColumnFamilyMetaData> metadata;
|
||||
impl->GetAllColumnFamilyMetaData(&metadata);
|
||||
|
||||
std::unordered_map<std::string, uint64_t> known_file_sizes;
|
||||
for (const auto& md : metadata) {
|
||||
for (const auto& lmd : md.levels) {
|
||||
for (const auto& fmd : lmd.files) {
|
||||
known_file_sizes[fmd.relative_filename] = fmd.size;
|
||||
}
|
||||
}
|
||||
for (const auto& bmd : md.blob_files) {
|
||||
std::string name = bmd.blob_file_name;
|
||||
// The BlobMetaData.blob_file_name may start with "/".
|
||||
if (!name.empty() && name[0] == '/') {
|
||||
name = name.substr(1);
|
||||
}
|
||||
known_file_sizes[name] = bmd.blob_file_size;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> paths;
|
||||
paths.emplace_back(impl->immutable_db_options_.db_paths[0].path);
|
||||
for (auto& cf : column_families) {
|
||||
if (!cf.options.cf_paths.empty()) {
|
||||
paths.emplace_back(cf.options.cf_paths[0].path);
|
||||
}
|
||||
}
|
||||
// Remove duplicate paths.
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
IOOptions io_opts;
|
||||
io_opts.do_not_recurse = true;
|
||||
for (auto& path : paths) {
|
||||
std::vector<std::string> existing_files;
|
||||
impl->immutable_db_options_.fs
|
||||
->GetChildren(path, io_opts, &existing_files,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.PermitUncheckedError(); //**TODO: What do to on error?
|
||||
for (auto& file_name : existing_files) {
|
||||
uint64_t file_number;
|
||||
FileType file_type;
|
||||
std::string file_path = path + "/" + file_name;
|
||||
if (ParseFileName(file_name, &file_number, &file_type) &&
|
||||
(file_type == kTableFile || file_type == kBlobFile)) {
|
||||
// TODO: Check for errors from OnAddFile?
|
||||
if (known_file_sizes.count(file_name)) {
|
||||
// We're assuming that each sst file name exists in at most one of
|
||||
// the paths.
|
||||
sfm->OnAddFile(file_path, known_file_sizes.at(file_name))
|
||||
.PermitUncheckedError();
|
||||
} else {
|
||||
sfm->OnAddFile(file_path).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve some disk buffer space. This is a heuristic - when we run out
|
||||
// of disk space, this ensures that there is at least write_buffer_size
|
||||
// of disk space, this ensures that there is atleast write_buffer_size
|
||||
// amount of free space before we resume DB writes. In low disk space
|
||||
// conditions, we want to avoid a lot of small L0 files due to frequent
|
||||
// WAL write failures and resultant forced flushes
|
||||
@@ -2247,27 +2256,6 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->immutable_db_options_.db_paths[0].path);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// When the DB is stopped, it's possible that there are some .trash files
|
||||
// that were not deleted yet, when we open the DB we will find these .trash
|
||||
// files and schedule them to be deleted (or delete immediately if
|
||||
// SstFileManager was not used).
|
||||
// Note that we only start doing this and below delete obsolete file after
|
||||
// `TrackExistingDataFiles` are called, the `max_trash_db_ratio` is
|
||||
// ineffective otherwise and these files' deletion won't be rate limited
|
||||
// which can cause discard stall.
|
||||
for (const auto& path : impl->CollectAllDBPaths()) {
|
||||
DeleteScheduler::CleanupDirectory(impl->immutable_db_options_.env, sfm,
|
||||
path)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
impl->mutex_.Lock();
|
||||
// This will do a full scan.
|
||||
impl->DeleteObsoleteFiles();
|
||||
TEST_SYNC_POINT("DBImpl::Open:AfterDeleteFiles");
|
||||
impl->MaybeScheduleFlushOrCompaction();
|
||||
impl->mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_HEADER(impl->immutable_db_options_.info_log, "DB pointer %p",
|
||||
|
||||
@@ -33,8 +33,8 @@ DBImplSecondary::~DBImplSecondary() = default;
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_wal_file_exists*/,
|
||||
bool /*error_if_data_exists_in_wals*/, bool /*is_retry*/, uint64_t*,
|
||||
RecoveryContext* /*recovery_ctx*/, bool* /*can_retry*/) {
|
||||
bool /*error_if_data_exists_in_wals*/, uint64_t*,
|
||||
RecoveryContext* /*recovery_ctx*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
JobContext job_context(0);
|
||||
|
||||
@@ -82,9 +82,8 @@ class DBImplSecondary : public DBImpl {
|
||||
// and log_readers_ to facilitate future operations.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, bool error_if_wal_file_exists,
|
||||
bool error_if_data_exists_in_wals, bool is_retry = false,
|
||||
uint64_t* = nullptr, RecoveryContext* recovery_ctx = nullptr,
|
||||
bool* can_retry = nullptr) override;
|
||||
bool error_if_data_exists_in_wals, uint64_t* = nullptr,
|
||||
RecoveryContext* recovery_ctx = nullptr) override;
|
||||
|
||||
// Can return IOError due to files being deleted by the primary. To avoid
|
||||
// IOError in this case, application can coordinate between primary and
|
||||
@@ -277,10 +276,6 @@ class DBImplSecondary : public DBImpl {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
@@ -309,6 +304,10 @@ class DBImplSecondary : public DBImpl {
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result);
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
|
||||
// Cache log readers for each log number, used for continue WAL replay
|
||||
// after recovery
|
||||
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
|
||||
|
||||
+29
-83
@@ -21,15 +21,14 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
int num_writable_file_errors_;
|
||||
|
||||
explicit CorruptionFS(const std::shared_ptr<FileSystem>& _target,
|
||||
bool fs_buffer, bool verify_read)
|
||||
bool fs_buffer)
|
||||
: FileSystemWrapper(_target),
|
||||
writable_file_error_(false),
|
||||
num_writable_file_errors_(0),
|
||||
corruption_trigger_(INT_MAX),
|
||||
read_count_(0),
|
||||
rnd_(300),
|
||||
fs_buffer_(fs_buffer),
|
||||
verify_read_(verify_read) {}
|
||||
fs_buffer_(fs_buffer) {}
|
||||
~CorruptionFS() override {
|
||||
// Assert that the corruption was reset, which means it got triggered
|
||||
assert(corruption_trigger_ == INT_MAX);
|
||||
@@ -114,13 +113,11 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
}
|
||||
|
||||
void SupportedOps(int64_t& supported_ops) override {
|
||||
supported_ops = 1 << FSSupportedOps::kAsyncIO;
|
||||
supported_ops = 1 << FSSupportedOps::kVerifyAndReconstructRead |
|
||||
1 << FSSupportedOps::kAsyncIO;
|
||||
if (fs_buffer_) {
|
||||
supported_ops |= 1 << FSSupportedOps::kFSBuffer;
|
||||
}
|
||||
if (verify_read_) {
|
||||
supported_ops |= 1 << FSSupportedOps::kVerifyAndReconstructRead;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -128,7 +125,6 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
int read_count_;
|
||||
Random rnd_;
|
||||
bool fs_buffer_;
|
||||
bool verify_read_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -700,24 +696,23 @@ TEST_F(DBIOFailureTest, CompactionSstSyncError) {
|
||||
|
||||
class DBIOCorruptionTest
|
||||
: public DBIOFailureTest,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
DBIOCorruptionTest() : DBIOFailureTest() {
|
||||
BlockBasedTableOptions bbto;
|
||||
options_ = CurrentOptions();
|
||||
Options options = CurrentOptions();
|
||||
|
||||
base_env_ = env_;
|
||||
EXPECT_NE(base_env_, nullptr);
|
||||
fs_.reset(new CorruptionFS(base_env_->GetFileSystem(),
|
||||
std::get<0>(GetParam()),
|
||||
std::get<2>(GetParam())));
|
||||
fs_.reset(
|
||||
new CorruptionFS(base_env_->GetFileSystem(), std::get<0>(GetParam())));
|
||||
env_guard_ = NewCompositeEnv(fs_);
|
||||
options_.env = env_guard_.get();
|
||||
options.env = env_guard_.get();
|
||||
bbto.num_file_reads_for_auto_readahead = 0;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options_.disable_auto_compactions = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options_);
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
~DBIOCorruptionTest() {
|
||||
@@ -725,13 +720,10 @@ class DBIOCorruptionTest
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
Status ReopenDB() { return TryReopen(options_); }
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Env> env_guard_;
|
||||
std::shared_ptr<CorruptionFS> fs_;
|
||||
Env* base_env_;
|
||||
Options options_;
|
||||
};
|
||||
|
||||
TEST_P(DBIOCorruptionTest, GetReadCorruptionRetry) {
|
||||
@@ -745,13 +737,8 @@ TEST_P(DBIOCorruptionTest, GetReadCorruptionRetry) {
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
Status s = dbfull()->Get(ReadOptions(), "key1", &val);
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
ASSERT_OK(dbfull()->Get(ReadOptions(), "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, IterReadCorruptionRetry) {
|
||||
@@ -771,11 +758,7 @@ TEST_P(DBIOCorruptionTest, IterReadCorruptionRetry) {
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
iter->Next();
|
||||
}
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(iter->status());
|
||||
} else {
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -796,13 +779,8 @@ TEST_P(DBIOCorruptionTest, MultiGetReadCorruptionRetry) {
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data());
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_EQ(values[0].ToString(), "val1");
|
||||
ASSERT_EQ(values[1].ToString(), "val2");
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_TRUE(statuses[1].IsCorruption());
|
||||
}
|
||||
ASSERT_EQ(values[0].ToString(), "val1");
|
||||
ASSERT_EQ(values[1].ToString(), "val2");
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, CompactionReadCorruptionRetry) {
|
||||
@@ -815,18 +793,13 @@ TEST_P(DBIOCorruptionTest, CompactionReadCorruptionRetry) {
|
||||
ASSERT_OK(Put("key2", "val2"));
|
||||
ASSERT_OK(Flush());
|
||||
fs->SetCorruptionTrigger(1);
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
@@ -835,44 +808,17 @@ TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
fs->SetCorruptionTrigger(1);
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
CorruptionFS* fs =
|
||||
static_cast<CorruptionFS*>(env_guard_->GetFileSystem().get());
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
ASSERT_OK(Flush());
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::Recover:StartManifestRead",
|
||||
[&](void* /*arg*/) { fs->SetCorruptionTrigger(0); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(ReopenDB());
|
||||
} else {
|
||||
ASSERT_EQ(ReopenDB(), Status::Corruption());
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
}
|
||||
|
||||
// The parameters are - 1. Use FS provided buffer, 2. Use async IO ReadOption,
|
||||
// 3. Retry with verify_and_reconstruct_read IOOption
|
||||
INSTANTIATE_TEST_CASE_P(DBIOCorruptionTest, DBIOCorruptionTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool()));
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -231,7 +231,6 @@ bool DBIter::SetValueAndColumnsFromEntity(Slice slice) {
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
wide_columns_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3555,121 +3555,6 @@ TEST_F(DBIteratorTest, ErrorWhenReadFile) {
|
||||
iter->Reset();
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, IteratorsConsistentViewImplicitSnapshot) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2"}, options);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush:done",
|
||||
"DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot"}});
|
||||
|
||||
bool flushed = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (!flushed) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val_new"));
|
||||
}
|
||||
// After SV is obtained for the first CF, flush for the second CF
|
||||
ASSERT_OK(Flush(1));
|
||||
flushed = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ReadOptions read_options;
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_OK(db_->NewIterators(read_options, handles_, &iters));
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto iter = iters[i];
|
||||
ASSERT_OK(iter->status());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(IterStatus(iter), "cf" + std::to_string(i) + "_key->cf" +
|
||||
std::to_string(i) + "_val_new");
|
||||
}
|
||||
for (auto* iter : iters) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// Thread-local SVs are no longer obsolete nor in use
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(handles_[i])->cfd();
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBIteratorTest, IteratorsConsistentViewExplicitSnapshot) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.atomic_flush = true;
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2"}, options);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush:done",
|
||||
"DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot"}});
|
||||
|
||||
bool flushed = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (!flushed) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val_new"));
|
||||
}
|
||||
// After SV is obtained for the first CF, do the atomic flush()
|
||||
ASSERT_OK(Flush());
|
||||
flushed = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// Explicit snapshot wouldn't force reloading all svs. We should expect old
|
||||
// values
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ReadOptions read_options;
|
||||
read_options.snapshot = snapshot;
|
||||
std::vector<Iterator*> iters;
|
||||
ASSERT_OK(db_->NewIterators(read_options, handles_, &iters));
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto iter = iters[i];
|
||||
ASSERT_OK(iter->status());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(IterStatus(iter), "cf" + std::to_string(i) + "_key->cf" +
|
||||
std::to_string(i) + "_val");
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
for (auto* iter : iters) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// Thread-local SV for cf_0 is obsolete (atomic flush happened after the first
|
||||
// SV Ref)
|
||||
auto* cfd0 =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(handles_[0])->cfd();
|
||||
ASSERT_EQ(cfd0->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
|
||||
ASSERT_NE(cfd0->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
|
||||
// Rest are not InUse nor Obsolete
|
||||
for (int i = 1; i < 3; ++i) {
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(handles_[i])->cfd();
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVObsolete);
|
||||
ASSERT_NE(cfd->TEST_GetLocalSV()->Get(), SuperVersion::kSVInUse);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -313,10 +313,6 @@ TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
ASSERT_EQ("foo_v3", Get("foo_k3"));
|
||||
ASSERT_EQ("bar_v1", Get("bar_k1"));
|
||||
ASSERT_EQ("bar_v2", Get("bar_k2"));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), "foo_k1", "foo_k4"));
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_in());
|
||||
ASSERT_EQ(hint_bar, rep->last_hint_out());
|
||||
ASSERT_EQ(5, rep->num_insert_with_hint());
|
||||
ASSERT_EQ("vvv", Get("NotInPrefixDomain"));
|
||||
}
|
||||
|
||||
|
||||
@@ -3806,21 +3806,6 @@ TEST_F(DBRangeDelTest, RefreshWithSnapshot) {
|
||||
iter.reset();
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, RowCache) {
|
||||
Options options = CurrentOptions();
|
||||
options.row_cache = NewLRUCache(8 << 10);
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put(Key(3), "val"));
|
||||
ASSERT_TRUE(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(3), Key(5))
|
||||
.IsNotSupported());
|
||||
WriteBatch wb;
|
||||
ASSERT_OK(wb.Put(Key(6), "abc"));
|
||||
ASSERT_OK(wb.DeleteRange(Key(1), Key(5)));
|
||||
ASSERT_TRUE(db_->Write(WriteOptions(), &wb).IsNotSupported());
|
||||
ASSERT_EQ(Get(Key(3)), "val");
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+11
-45
@@ -21,8 +21,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBSSTTest : public DBTestBase {
|
||||
public:
|
||||
DBSSTTest(const std::string& test_name = "db_sst_test")
|
||||
: DBTestBase(test_name, /*env_do_fsync=*/true) {}
|
||||
DBSSTTest() : DBTestBase("db_sst_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
// A class which remembers the name of each flushed file.
|
||||
@@ -938,23 +937,15 @@ INSTANTIATE_TEST_CASE_P(DBWALTestWithParam, DBWALTestWithParam,
|
||||
::testing::Values(std::make_tuple("", true),
|
||||
std::make_tuple("_wal_dir", false)));
|
||||
|
||||
// Test param: max_trash_db_ratio for DeleteScheduler
|
||||
class DBObsoleteFileDeletionOnOpenTest
|
||||
: public DBSSTTest,
|
||||
public ::testing::WithParamInterface<double> {
|
||||
public:
|
||||
explicit DBObsoleteFileDeletionOnOpenTest()
|
||||
: DBSSTTest("db_sst_deletion_on_open_test") {}
|
||||
};
|
||||
|
||||
TEST_P(DBObsoleteFileDeletionOnOpenTest, Basic) {
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrashAndObsoleteSstFile) {
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
// Set an extra high trash ratio to prevent immediate/non-rate limited
|
||||
// deletions
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
double max_trash_db_ratio = GetParam();
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(max_trash_db_ratio);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1000.0);
|
||||
|
||||
int bg_delete_file = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -974,19 +965,10 @@ TEST_P(DBObsoleteFileDeletionOnOpenTest, Basic) {
|
||||
WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash", false));
|
||||
// Manually add an obsolete sst file. Obsolete SST files are discovered and
|
||||
// deleted upon recovery.
|
||||
uint64_t sst_file_number = 100;
|
||||
const std::string kObsoleteSstFileOne =
|
||||
MakeTableFileName(dbname_, sst_file_number);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", kObsoleteSstFileOne, false));
|
||||
// The slow deletion on recovery had a bug before where a file's size is not
|
||||
// first tracked in `total_size_` in SstFileManager before passed to
|
||||
// DeleteScheduler. The first obsolete file is still slow deleted because
|
||||
// 0 (total_trash_size_) > 0 (total_size_) * 1000 (max_trash_db_ratio)
|
||||
// is always false.
|
||||
// Here we explicitly create a second obsolete file to verify this bug's fix
|
||||
const std::string kObsoleteSstFileTwo =
|
||||
MakeTableFileName(dbname_, sst_file_number - 1);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", kObsoleteSstFileTwo, false));
|
||||
constexpr uint64_t kSstFileNumber = 100;
|
||||
const std::string kObsoleteSstFile =
|
||||
MakeTableFileName(dbname_, kSstFileNumber);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", kObsoleteSstFile, false));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files and obsolete
|
||||
// SST files with rate limiting.
|
||||
@@ -995,26 +977,10 @@ TEST_P(DBObsoleteFileDeletionOnOpenTest, Basic) {
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(kObsoleteSstFileOne));
|
||||
ASSERT_NOK(env_->FileExists(kObsoleteSstFileTwo));
|
||||
// The files in the DB's directory are all either trash or obsolete sst files.
|
||||
// So the trash/db ratio is 1. A ratio equal to or higher than 1 should
|
||||
// schedule all files' deletion in background. A ratio lower than 1 may
|
||||
// send some files to be deleted immediately.
|
||||
if (max_trash_db_ratio < 1) {
|
||||
ASSERT_LE(bg_delete_file, 5);
|
||||
} else {
|
||||
ASSERT_EQ(bg_delete_file, 5);
|
||||
}
|
||||
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0);
|
||||
ASSERT_EQ(sfm->delete_scheduler()->GetTotalTrashSize(), 0);
|
||||
ASSERT_NOK(env_->FileExists(kObsoleteSstFile));
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBObsoleteFileDeletionOnOpenTest,
|
||||
DBObsoleteFileDeletionOnOpenTest,
|
||||
::testing::Values(0, 0.5, 1, 1.2));
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
// db_paths using CompactRangeOptions, make sure that files that were
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
|
||||
+3
-11
@@ -26,7 +26,6 @@
|
||||
#endif
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "db/attribute_group_iterator_impl.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
@@ -3200,18 +3199,11 @@ class ModelDB : public DB {
|
||||
return Status::NotSupported("Not supported yet");
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> NewCoalescingIterator(
|
||||
// UNDER CONSTRUCTION - DO NOT USE
|
||||
std::unique_ptr<Iterator> NewMultiCfIterator(
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_families*/) override {
|
||||
return std::unique_ptr<Iterator>(
|
||||
NewErrorIterator(Status::NotSupported("Not supported yet")));
|
||||
}
|
||||
|
||||
std::unique_ptr<AttributeGroupIterator> NewAttributeGroupIterator(
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_families*/) override {
|
||||
return NewAttributeGroupErrorIterator(
|
||||
Status::NotSupported("Not supported yet"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Snapshot* GetSnapshot() override {
|
||||
|
||||
+1
-6
@@ -154,9 +154,6 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
if ((skip_mask & kSkipMmapReads) && option_config == kWalDirAndMmapReads) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipRowCache) && option_config == kRowCache) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -769,9 +766,7 @@ Status DBTestBase::TimedPut(const Slice& k, const Slice& v,
|
||||
|
||||
Status DBTestBase::TimedPut(int cf, const Slice& k, const Slice& v,
|
||||
uint64_t write_unix_time, WriteOptions wo) {
|
||||
WriteBatch wb(/*reserved_bytes=*/0, /*max_bytes=*/0,
|
||||
wo.protection_bytes_per_key,
|
||||
/*default_cf_ts_sz=*/0);
|
||||
WriteBatch wb;
|
||||
ColumnFamilyHandle* cfh;
|
||||
if (cf != 0) {
|
||||
cfh = handles_[cf];
|
||||
|
||||
+1
-4
@@ -1071,7 +1071,6 @@ class DBTestBase : public testing::Test {
|
||||
kSkipNoSeekToLast = 32,
|
||||
kSkipFIFOCompaction = 128,
|
||||
kSkipMmapReads = 256,
|
||||
kSkipRowCache = 512,
|
||||
};
|
||||
|
||||
const int kRangeDelSkipConfigs =
|
||||
@@ -1079,9 +1078,7 @@ class DBTestBase : public testing::Test {
|
||||
kSkipPlainTable |
|
||||
// MmapReads disables the iterator pinning that RangeDelAggregator
|
||||
// requires.
|
||||
kSkipMmapReads |
|
||||
// Not compatible yet.
|
||||
kSkipRowCache;
|
||||
kSkipMmapReads;
|
||||
|
||||
// `env_do_fsync` decides whether the special Env would do real
|
||||
// fsync for files and directories. Skipping fsync can speed up
|
||||
|
||||
@@ -814,7 +814,6 @@ TEST_F(DBWALTest, WALWithChecksumHandoff) {
|
||||
writeOpt.disableWAL = false;
|
||||
// Data is persisted in the WAL
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, handles_[1], "zoo", "v3"));
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
// The hash does not match, write fails
|
||||
fault_fs->SetChecksumHandoffFuncType(ChecksumType::kxxHash);
|
||||
writeOpt.disableWAL = false;
|
||||
|
||||
+2
-31
@@ -795,7 +795,7 @@ TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) {
|
||||
std::thread threads[10];
|
||||
for (int t = 0; t < 10; t++) {
|
||||
threads[t] = std::thread([t, wal_key_prefix, wal_value, no_wal_key_prefix,
|
||||
no_wal_value, &options, this] {
|
||||
no_wal_value, this] {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ROCKSDB_NAMESPACE::WriteOptions write_option_disable;
|
||||
write_option_disable.disableWAL = true;
|
||||
@@ -806,10 +806,7 @@ TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) {
|
||||
std::string wal_key =
|
||||
wal_key_prefix + std::to_string(i) + "_" + std::to_string(i);
|
||||
ASSERT_OK(this->Put(wal_key, wal_value, write_option_default));
|
||||
ASSERT_OK(dbfull()->SyncWAL())
|
||||
<< "options.env: " << options.env << ", env_: " << env_
|
||||
<< ", env_->is_wal_sync_thread_safe_: "
|
||||
<< env_->is_wal_sync_thread_safe_.load();
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
}
|
||||
return;
|
||||
});
|
||||
@@ -913,32 +910,6 @@ TEST_P(DBWriteTest, RecycleLogTestCFAheadOfWAL) {
|
||||
Status::Corruption());
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, RecycleLogToggleTest) {
|
||||
Options options = GetOptions();
|
||||
options.recycle_log_file_num = 0;
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
|
||||
Destroy(options);
|
||||
Reopen(options);
|
||||
// After opening, a new log gets created, say 1.log
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
|
||||
options.recycle_log_file_num = 1;
|
||||
Reopen(options);
|
||||
// 1.log is added to alive_log_files_
|
||||
ASSERT_OK(Put(Key(2), "val1"));
|
||||
ASSERT_OK(Flush());
|
||||
// 1.log should be deleted and not recycled, since it
|
||||
// was created by the previous Reopen
|
||||
ASSERT_OK(Put(Key(1), "val2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
options.recycle_log_file_num = 1;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(Get(Key(1)), "val2");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
|
||||
testing::Values(DBTestBase::kDefault,
|
||||
DBTestBase::kConcurrentWALWrites,
|
||||
|
||||
+1
-43
@@ -223,49 +223,6 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, WaitForCompactWithWaitForPurgeOptionTest) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
Destroy(options);
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
CreateTwoLevels();
|
||||
Iterator* itr = nullptr;
|
||||
ReadOptions read_options;
|
||||
read_options.background_purge_on_iterator_cleanup = true;
|
||||
itr = db_->NewIterator(read_options);
|
||||
ASSERT_OK(itr->status());
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &first_slice, &last_slice));
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkPurge:start", "DeleteFileTest::WaitForPurgeTest"},
|
||||
{"DBImpl::WaitForCompact:InsideLoop",
|
||||
"DBImpl::BackgroundCallPurge:beforeMutexLock"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
delete itr;
|
||||
|
||||
TEST_SYNC_POINT("DeleteFileTest::WaitForPurgeTest");
|
||||
// At this point, purge got started, but can't finish due to sync points
|
||||
// not purged yet
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
|
||||
// The sync point in WaitForCompact should unblock the purge
|
||||
WaitForCompactOptions wait_for_compact_options;
|
||||
wait_for_compact_options.wait_for_purge = true;
|
||||
Status s = dbfull()->WaitForCompact(wait_for_compact_options);
|
||||
ASSERT_OK(s);
|
||||
|
||||
// Now files should be purged
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) {
|
||||
Options options = CurrentOptions();
|
||||
SetOptions(&options);
|
||||
@@ -643,3 +600,4 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -1238,7 +1238,6 @@ TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(
|
||||
ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT));
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
|
||||
@@ -578,10 +578,8 @@ TEST_P(FaultInjectionTest, NoDuplicateTrailingEntries) {
|
||||
|
||||
fault_fs->DisableWriteErrorInjection();
|
||||
|
||||
// Flush remaining data from its buffer to underlying file.
|
||||
ASSERT_OK(log_writer->file()->writable_file()->Sync(IOOptions(),
|
||||
nullptr /* dbg */));
|
||||
// Closing the log writer will cause WritableFileWriter::Close()
|
||||
// Closing the log writer will cause WritableFileWriter::Close() and flush
|
||||
// remaining data from its buffer to underlying file.
|
||||
log_writer.reset();
|
||||
|
||||
{
|
||||
|
||||
@@ -65,11 +65,11 @@ Status ImportColumnFamilyJob::Prepare(uint64_t next_file_number,
|
||||
largest = file_to_import.largest_internal_key;
|
||||
} else {
|
||||
if (cfd_->internal_comparator().Compare(
|
||||
smallest, file_to_import.smallest_internal_key) > 0) {
|
||||
smallest, file_to_import.smallest_internal_key) < 0) {
|
||||
smallest = file_to_import.smallest_internal_key;
|
||||
}
|
||||
if (cfd_->internal_comparator().Compare(
|
||||
largest, file_to_import.largest_internal_key) < 0) {
|
||||
largest, file_to_import.largest_internal_key) > 0) {
|
||||
largest = file_to_import.largest_internal_key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,71 +881,6 @@ TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilyWithOverlap) {
|
||||
delete db_copy;
|
||||
ASSERT_OK(DestroyDir(env_, dbname_ + "/db_copy"));
|
||||
}
|
||||
|
||||
TEST_F(ImportColumnFamilyTest, ImportMultiColumnFamilySeveralFilesWithOverlap) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
SstFileWriter sfw_cf1(EnvOptions(), options, handles_[1]);
|
||||
const std::string file1_sst_name = "file1.sst";
|
||||
const std::string file1_sst = sst_files_dir_ + file1_sst_name;
|
||||
ASSERT_OK(sfw_cf1.Open(file1_sst));
|
||||
ASSERT_OK(sfw_cf1.Put("K1", "V1"));
|
||||
ASSERT_OK(sfw_cf1.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf1.Finish());
|
||||
|
||||
SstFileWriter sfw_cf2(EnvOptions(), options, handles_[1]);
|
||||
const std::string file2_sst_name = "file2.sst";
|
||||
const std::string file2_sst = sst_files_dir_ + file2_sst_name;
|
||||
ASSERT_OK(sfw_cf2.Open(file2_sst));
|
||||
ASSERT_OK(sfw_cf2.Put("K2", "V2"));
|
||||
ASSERT_OK(sfw_cf2.Put("K3", "V3"));
|
||||
ASSERT_OK(sfw_cf2.Finish());
|
||||
|
||||
ColumnFamilyHandle* second_cfh = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamily(options, "toto", &second_cfh));
|
||||
|
||||
SstFileWriter sfw_cf3(EnvOptions(), options, second_cfh);
|
||||
const std::string file3_sst_name = "file3.sst";
|
||||
const std::string file3_sst = sst_files_dir_ + file3_sst_name;
|
||||
ASSERT_OK(sfw_cf3.Open(file3_sst));
|
||||
ASSERT_OK(sfw_cf3.Put("K3", "V3"));
|
||||
ASSERT_OK(sfw_cf3.Put("K4", "V4"));
|
||||
ASSERT_OK(sfw_cf3.Finish());
|
||||
|
||||
SstFileWriter sfw_cf4(EnvOptions(), options, second_cfh);
|
||||
const std::string file4_sst_name = "file4.sst";
|
||||
const std::string file4_sst = sst_files_dir_ + file4_sst_name;
|
||||
ASSERT_OK(sfw_cf4.Open(file4_sst));
|
||||
ASSERT_OK(sfw_cf4.Put("K4", "V4"));
|
||||
ASSERT_OK(sfw_cf4.Put("K5", "V5"));
|
||||
ASSERT_OK(sfw_cf4.Finish());
|
||||
|
||||
ExportImportFilesMetaData metadata1, metadata2;
|
||||
metadata1.files.push_back(
|
||||
LiveFileMetaDataInit(file1_sst_name, sst_files_dir_, 1, 1, 2));
|
||||
metadata1.files.push_back(
|
||||
LiveFileMetaDataInit(file2_sst_name, sst_files_dir_, 1, 3, 4));
|
||||
metadata1.db_comparator_name = options.comparator->Name();
|
||||
metadata2.files.push_back(
|
||||
LiveFileMetaDataInit(file3_sst_name, sst_files_dir_, 1, 1, 2));
|
||||
metadata2.files.push_back(
|
||||
LiveFileMetaDataInit(file4_sst_name, sst_files_dir_, 1, 3, 4));
|
||||
metadata2.db_comparator_name = options.comparator->Name();
|
||||
|
||||
std::vector<const ExportImportFilesMetaData*> metadatas{&metadata1,
|
||||
&metadata2};
|
||||
|
||||
ASSERT_EQ(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
metadatas, &import_cfh_),
|
||||
Status::InvalidArgument("CFs have overlapping ranges"));
|
||||
ASSERT_EQ(import_cfh_, nullptr);
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(second_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(second_cfh));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+2
-6
@@ -232,9 +232,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
// produce a hole in the recovered data. Report an error here, which
|
||||
// higher layers can choose to ignore when it's provable there is no
|
||||
// hole.
|
||||
ReportCorruption(
|
||||
scratch->size(),
|
||||
"error reading trailing data due to encountering EOF");
|
||||
ReportCorruption(scratch->size(), "error reading trailing data");
|
||||
}
|
||||
// This can be caused by the writer dying immediately after
|
||||
// writing a physical record but before completing the next; don't
|
||||
@@ -254,9 +252,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
// produce a hole in the recovered data. Report an error here,
|
||||
// which higher layers can choose to ignore when it's provable
|
||||
// there is no hole.
|
||||
ReportCorruption(
|
||||
scratch->size(),
|
||||
"error reading trailing data due to encountering old record");
|
||||
ReportCorruption(scratch->size(), "error reading trailing data");
|
||||
}
|
||||
// This can be caused by the writer dying immediately after
|
||||
// writing a physical record but before completing the next; don't
|
||||
|
||||
+2
-3
@@ -765,9 +765,8 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
Slice key_without_ts = StripTimestampFromUserKey(key, ts_sz_);
|
||||
|
||||
if (!allow_concurrent) {
|
||||
// Extract prefix for insert with hint. Hints are for point key table
|
||||
// (`table_`) only, not `range_del_table_`.
|
||||
if (table == table_ && insert_with_hint_prefix_extractor_ != nullptr &&
|
||||
// Extract prefix for insert with hint.
|
||||
if (insert_with_hint_prefix_extractor_ != nullptr &&
|
||||
insert_with_hint_prefix_extractor_->InDomain(key_slice)) {
|
||||
Slice prefix = insert_with_hint_prefix_extractor_->Transform(key_slice);
|
||||
bool res = table->InsertKeyWithHint(handle, &insert_hints_[prefix]);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/multi_cf_iterator.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
template <typename BinaryHeap, typename ChildSeekFuncType>
|
||||
void MultiCfIterator::SeekCommon(BinaryHeap& heap,
|
||||
ChildSeekFuncType child_seek_func) {
|
||||
heap.clear();
|
||||
int i = 0;
|
||||
for (auto& cfh_iter_pair : cfh_iter_pairs_) {
|
||||
auto& cfh = cfh_iter_pair.first;
|
||||
auto& iter = cfh_iter_pair.second;
|
||||
child_seek_func(iter.get());
|
||||
if (iter->Valid()) {
|
||||
assert(iter->status().ok());
|
||||
heap.push(MultiCfIteratorInfo{iter.get(), cfh, i});
|
||||
} else {
|
||||
considerStatus(iter->status());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BinaryHeap, typename AdvanceFuncType>
|
||||
void MultiCfIterator::AdvanceIterator(BinaryHeap& heap,
|
||||
AdvanceFuncType advance_func) {
|
||||
// 1. Keep the top iterator (by popping it from the heap)
|
||||
// 2. Make sure all others have iterated past the top iterator key slice
|
||||
// 3. Advance the top iterator, and add it back to the heap if valid
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
if (!heap.empty()) {
|
||||
auto* current = heap.top().iterator;
|
||||
while (current->Valid() &&
|
||||
comparator_->Compare(top.iterator->key(), current->key()) == 0) {
|
||||
assert(current->status().ok());
|
||||
advance_func(current);
|
||||
if (current->Valid()) {
|
||||
heap.replace_top(heap.top());
|
||||
} else {
|
||||
considerStatus(current->status());
|
||||
heap.pop();
|
||||
}
|
||||
if (!heap.empty()) {
|
||||
current = heap.top().iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
advance_func(top.iterator);
|
||||
if (top.iterator->Valid()) {
|
||||
assert(top.iterator->status().ok());
|
||||
heap.push(top);
|
||||
} else {
|
||||
considerStatus(top.iterator->status());
|
||||
}
|
||||
}
|
||||
|
||||
void MultiCfIterator::SeekToFirst() {
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() { InitMinHeap(); });
|
||||
SeekCommon(min_heap, [](Iterator* iter) { iter->SeekToFirst(); });
|
||||
}
|
||||
void MultiCfIterator::Seek(const Slice& target) {
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() { InitMinHeap(); });
|
||||
SeekCommon(min_heap, [&target](Iterator* iter) { iter->Seek(target); });
|
||||
}
|
||||
void MultiCfIterator::SeekToLast() {
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() { InitMaxHeap(); });
|
||||
SeekCommon(max_heap, [](Iterator* iter) { iter->SeekToLast(); });
|
||||
}
|
||||
void MultiCfIterator::SeekForPrev(const Slice& target) {
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() { InitMaxHeap(); });
|
||||
SeekCommon(max_heap,
|
||||
[&target](Iterator* iter) { iter->SeekForPrev(target); });
|
||||
}
|
||||
|
||||
void MultiCfIterator::Next() {
|
||||
assert(Valid());
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() {
|
||||
Slice target = key();
|
||||
InitMinHeap();
|
||||
Seek(target);
|
||||
});
|
||||
AdvanceIterator(min_heap, [](Iterator* iter) { iter->Next(); });
|
||||
}
|
||||
void MultiCfIterator::Prev() {
|
||||
assert(Valid());
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() {
|
||||
Slice target = key();
|
||||
InitMaxHeap();
|
||||
SeekForPrev(target);
|
||||
});
|
||||
AdvanceIterator(max_heap, [](Iterator* iter) { iter->Prev(); });
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/heap.h"
|
||||
#include "util/overload.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// UNDER CONSTRUCTION - DO NOT USE
|
||||
// A cross-column-family iterator from a consistent database state.
|
||||
// When the same key exists in more than one column families, the iterator
|
||||
// selects the value from the first column family containing the key, in the
|
||||
// order provided in the `column_families` parameter.
|
||||
class MultiCfIterator : public Iterator {
|
||||
public:
|
||||
MultiCfIterator(const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: comparator_(comparator),
|
||||
heap_(MultiCfMinHeap(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_))) {
|
||||
assert(column_families.size() > 0 &&
|
||||
column_families.size() == child_iterators.size());
|
||||
cfh_iter_pairs_.reserve(column_families.size());
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
cfh_iter_pairs_.emplace_back(
|
||||
column_families[i], std::unique_ptr<Iterator>(child_iterators[i]));
|
||||
}
|
||||
}
|
||||
~MultiCfIterator() override { status_.PermitUncheckedError(); }
|
||||
|
||||
// No copy allowed
|
||||
MultiCfIterator(const MultiCfIterator&) = delete;
|
||||
MultiCfIterator& operator=(const MultiCfIterator&) = delete;
|
||||
|
||||
private:
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>
|
||||
cfh_iter_pairs_;
|
||||
ReadOptions read_options_;
|
||||
Status status_;
|
||||
|
||||
AttributeGroups attribute_groups_;
|
||||
|
||||
struct MultiCfIteratorInfo {
|
||||
Iterator* iterator;
|
||||
ColumnFamilyHandle* cfh;
|
||||
int order;
|
||||
};
|
||||
|
||||
template <typename CompareOp>
|
||||
class MultiCfHeapItemComparator {
|
||||
public:
|
||||
explicit MultiCfHeapItemComparator(const Comparator* comparator)
|
||||
: comparator_(comparator) {}
|
||||
bool operator()(const MultiCfIteratorInfo& a,
|
||||
const MultiCfIteratorInfo& b) const {
|
||||
assert(a.iterator);
|
||||
assert(b.iterator);
|
||||
assert(a.iterator->Valid());
|
||||
assert(b.iterator->Valid());
|
||||
int c = comparator_->Compare(a.iterator->key(), b.iterator->key());
|
||||
assert(c != 0 || a.order != b.order);
|
||||
return c == 0 ? a.order - b.order > 0 : CompareOp()(c, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* comparator_;
|
||||
};
|
||||
const Comparator* comparator_;
|
||||
using MultiCfMinHeap =
|
||||
BinaryHeap<MultiCfIteratorInfo,
|
||||
MultiCfHeapItemComparator<std::greater<int>>>;
|
||||
using MultiCfMaxHeap = BinaryHeap<MultiCfIteratorInfo,
|
||||
MultiCfHeapItemComparator<std::less<int>>>;
|
||||
|
||||
using MultiCfIterHeap = std::variant<MultiCfMinHeap, MultiCfMaxHeap>;
|
||||
|
||||
MultiCfIterHeap heap_;
|
||||
|
||||
// TODO: Lower and Upper bounds
|
||||
|
||||
Iterator* current() const {
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
auto& max_heap = std::get<MultiCfMaxHeap>(heap_);
|
||||
return max_heap.top().iterator;
|
||||
}
|
||||
auto& min_heap = std::get<MultiCfMinHeap>(heap_);
|
||||
return min_heap.top().iterator;
|
||||
}
|
||||
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return current()->key();
|
||||
}
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return current()->value();
|
||||
}
|
||||
const WideColumns& columns() const override {
|
||||
assert(Valid());
|
||||
return current()->columns();
|
||||
}
|
||||
|
||||
bool Valid() const override {
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
auto& max_heap = std::get<MultiCfMaxHeap>(heap_);
|
||||
return !max_heap.empty() && status_.ok();
|
||||
}
|
||||
auto& min_heap = std::get<MultiCfMinHeap>(heap_);
|
||||
return !min_heap.empty() && status_.ok();
|
||||
}
|
||||
|
||||
Status status() const override { return status_; }
|
||||
void considerStatus(Status s) {
|
||||
if (!s.ok() && status_.ok()) {
|
||||
status_ = std::move(s);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HeapType, typename InitFunc>
|
||||
HeapType& GetHeap(InitFunc initFunc) {
|
||||
if (!std::holds_alternative<HeapType>(heap_)) {
|
||||
initFunc();
|
||||
}
|
||||
return std::get<HeapType>(heap_);
|
||||
}
|
||||
|
||||
void InitMinHeap() {
|
||||
heap_.emplace<MultiCfMinHeap>(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_));
|
||||
}
|
||||
void InitMaxHeap() {
|
||||
heap_.emplace<MultiCfMaxHeap>(
|
||||
MultiCfHeapItemComparator<std::less<int>>(comparator_));
|
||||
}
|
||||
|
||||
template <typename BinaryHeap, typename ChildSeekFuncType>
|
||||
void SeekCommon(BinaryHeap& heap, ChildSeekFuncType child_seek_func);
|
||||
template <typename BinaryHeap, typename AdvanceFuncType>
|
||||
void AdvanceIterator(BinaryHeap& heap, AdvanceFuncType advance_func);
|
||||
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
void Seek(const Slice& /*target*/) override;
|
||||
void SeekForPrev(const Slice& /*target*/) override;
|
||||
void Next() override;
|
||||
void Prev() override;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,289 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/heap.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct MultiCfIteratorInfo {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Iterator* iterator;
|
||||
int order;
|
||||
};
|
||||
|
||||
class MultiCfIteratorImpl {
|
||||
public:
|
||||
MultiCfIteratorImpl(
|
||||
const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators,
|
||||
std::function<void()> reset_func,
|
||||
std::function<void(const autovector<MultiCfIteratorInfo>&)> populate_func)
|
||||
: comparator_(comparator),
|
||||
heap_(MultiCfMinHeap(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_))),
|
||||
reset_func_(std::move(reset_func)),
|
||||
populate_func_(std::move(populate_func)) {
|
||||
assert(column_families.size() > 0 &&
|
||||
column_families.size() == child_iterators.size());
|
||||
cfh_iter_pairs_.reserve(column_families.size());
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
cfh_iter_pairs_.emplace_back(
|
||||
column_families[i], std::unique_ptr<Iterator>(child_iterators[i]));
|
||||
}
|
||||
}
|
||||
~MultiCfIteratorImpl() { status_.PermitUncheckedError(); }
|
||||
|
||||
// No copy allowed
|
||||
MultiCfIteratorImpl(const MultiCfIteratorImpl&) = delete;
|
||||
MultiCfIteratorImpl& operator=(const MultiCfIteratorImpl&) = delete;
|
||||
|
||||
Slice key() const {
|
||||
assert(Valid());
|
||||
return current()->key();
|
||||
}
|
||||
|
||||
bool Valid() const {
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
auto& max_heap = std::get<MultiCfMaxHeap>(heap_);
|
||||
return !max_heap.empty() && status_.ok();
|
||||
}
|
||||
auto& min_heap = std::get<MultiCfMinHeap>(heap_);
|
||||
return !min_heap.empty() && status_.ok();
|
||||
}
|
||||
|
||||
Status status() const { return status_; }
|
||||
|
||||
void SeekToFirst() {
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() { InitMinHeap(); });
|
||||
SeekCommon(min_heap, [](Iterator* iter) { iter->SeekToFirst(); });
|
||||
}
|
||||
void Seek(const Slice& target) {
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() { InitMinHeap(); });
|
||||
SeekCommon(min_heap, [&target](Iterator* iter) { iter->Seek(target); });
|
||||
}
|
||||
void SeekToLast() {
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() { InitMaxHeap(); });
|
||||
SeekCommon(max_heap, [](Iterator* iter) { iter->SeekToLast(); });
|
||||
}
|
||||
void SeekForPrev(const Slice& target) {
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() { InitMaxHeap(); });
|
||||
SeekCommon(max_heap,
|
||||
[&target](Iterator* iter) { iter->SeekForPrev(target); });
|
||||
}
|
||||
|
||||
void Next() {
|
||||
assert(Valid());
|
||||
auto& min_heap = GetHeap<MultiCfMinHeap>([this]() {
|
||||
Slice target = key();
|
||||
InitMinHeap();
|
||||
Seek(target);
|
||||
});
|
||||
AdvanceIterator(min_heap, [](Iterator* iter) { iter->Next(); });
|
||||
}
|
||||
void Prev() {
|
||||
assert(Valid());
|
||||
auto& max_heap = GetHeap<MultiCfMaxHeap>([this]() {
|
||||
Slice target = key();
|
||||
InitMaxHeap();
|
||||
SeekForPrev(target);
|
||||
});
|
||||
AdvanceIterator(max_heap, [](Iterator* iter) { iter->Prev(); });
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>
|
||||
cfh_iter_pairs_;
|
||||
Status status_;
|
||||
|
||||
template <typename CompareOp>
|
||||
class MultiCfHeapItemComparator {
|
||||
public:
|
||||
explicit MultiCfHeapItemComparator(const Comparator* comparator)
|
||||
: comparator_(comparator) {}
|
||||
bool operator()(const MultiCfIteratorInfo& a,
|
||||
const MultiCfIteratorInfo& b) const {
|
||||
assert(a.iterator);
|
||||
assert(b.iterator);
|
||||
assert(a.iterator->Valid());
|
||||
assert(b.iterator->Valid());
|
||||
int c = comparator_->Compare(a.iterator->key(), b.iterator->key());
|
||||
assert(c != 0 || a.order != b.order);
|
||||
return c == 0 ? a.order - b.order > 0 : CompareOp()(c, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* comparator_;
|
||||
};
|
||||
const Comparator* comparator_;
|
||||
using MultiCfMinHeap =
|
||||
BinaryHeap<MultiCfIteratorInfo,
|
||||
MultiCfHeapItemComparator<std::greater<int>>>;
|
||||
using MultiCfMaxHeap = BinaryHeap<MultiCfIteratorInfo,
|
||||
MultiCfHeapItemComparator<std::less<int>>>;
|
||||
|
||||
using MultiCfIterHeap = std::variant<MultiCfMinHeap, MultiCfMaxHeap>;
|
||||
|
||||
MultiCfIterHeap heap_;
|
||||
|
||||
std::function<void()> reset_func_;
|
||||
std::function<void(autovector<MultiCfIteratorInfo>)> populate_func_;
|
||||
|
||||
Iterator* current() const {
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
auto& max_heap = std::get<MultiCfMaxHeap>(heap_);
|
||||
return max_heap.top().iterator;
|
||||
}
|
||||
auto& min_heap = std::get<MultiCfMinHeap>(heap_);
|
||||
return min_heap.top().iterator;
|
||||
}
|
||||
|
||||
void considerStatus(Status s) {
|
||||
if (!s.ok() && status_.ok()) {
|
||||
status_ = std::move(s);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HeapType, typename InitFunc>
|
||||
HeapType& GetHeap(InitFunc initFunc) {
|
||||
if (!std::holds_alternative<HeapType>(heap_)) {
|
||||
initFunc();
|
||||
}
|
||||
return std::get<HeapType>(heap_);
|
||||
}
|
||||
|
||||
void InitMinHeap() {
|
||||
heap_.emplace<MultiCfMinHeap>(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_));
|
||||
}
|
||||
void InitMaxHeap() {
|
||||
heap_.emplace<MultiCfMaxHeap>(
|
||||
MultiCfHeapItemComparator<std::less<int>>(comparator_));
|
||||
}
|
||||
|
||||
template <typename BinaryHeap, typename ChildSeekFuncType>
|
||||
void SeekCommon(BinaryHeap& heap, ChildSeekFuncType child_seek_func) {
|
||||
reset_func_();
|
||||
heap.clear();
|
||||
int i = 0;
|
||||
for (auto& [cfh, iter] : cfh_iter_pairs_) {
|
||||
child_seek_func(iter.get());
|
||||
if (iter->Valid()) {
|
||||
assert(iter->status().ok());
|
||||
heap.push(MultiCfIteratorInfo{cfh, iter.get(), i});
|
||||
} else {
|
||||
considerStatus(iter->status());
|
||||
if (!status_.ok()) {
|
||||
// Non-OK status from the iterator. Bail out early
|
||||
heap.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (!heap.empty()) {
|
||||
PopulateIterator(heap);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BinaryHeap, typename AdvanceFuncType>
|
||||
void AdvanceIterator(BinaryHeap& heap, AdvanceFuncType advance_func) {
|
||||
assert(!heap.empty());
|
||||
reset_func_();
|
||||
|
||||
// 1. Keep the top iterator (by popping it from the heap)
|
||||
// 2. Make sure all others have iterated past the top iterator key slice
|
||||
// 3. Advance the top iterator, and add it back to the heap if valid
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
if (!heap.empty()) {
|
||||
auto current = heap.top();
|
||||
assert(current.iterator);
|
||||
while (current.iterator->Valid() &&
|
||||
comparator_->Compare(top.iterator->key(),
|
||||
current.iterator->key()) == 0) {
|
||||
assert(current.iterator->status().ok());
|
||||
advance_func(current.iterator);
|
||||
if (current.iterator->Valid()) {
|
||||
heap.replace_top(heap.top());
|
||||
} else {
|
||||
considerStatus(current.iterator->status());
|
||||
if (!status_.ok()) {
|
||||
heap.clear();
|
||||
return;
|
||||
} else {
|
||||
heap.pop();
|
||||
}
|
||||
}
|
||||
if (!heap.empty()) {
|
||||
current = heap.top();
|
||||
}
|
||||
}
|
||||
}
|
||||
advance_func(top.iterator);
|
||||
if (top.iterator->Valid()) {
|
||||
assert(top.iterator->status().ok());
|
||||
heap.push(top);
|
||||
} else {
|
||||
considerStatus(top.iterator->status());
|
||||
if (!status_.ok()) {
|
||||
heap.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!heap.empty()) {
|
||||
PopulateIterator(heap);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BinaryHeap>
|
||||
void PopulateIterator(BinaryHeap& heap) {
|
||||
// 1. Keep the top iterator (by popping it from the heap) and add it to list
|
||||
// to populate
|
||||
// 2. For all non-top iterators having the same key as top iter popped
|
||||
// from the previous step, add them to the same list and pop it
|
||||
// temporarily from the heap
|
||||
// 3. Once no other iters have the same key as the top iter from step 1,
|
||||
// populate the value/columns and attribute_groups from the list
|
||||
// collected in step 1 and 2 and add all the iters back to the heap
|
||||
assert(!heap.empty());
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
autovector<MultiCfIteratorInfo> to_populate;
|
||||
to_populate.push_back(top);
|
||||
if (!heap.empty()) {
|
||||
auto current = heap.top();
|
||||
assert(current.iterator);
|
||||
while (current.iterator->Valid() &&
|
||||
comparator_->Compare(top.iterator->key(),
|
||||
current.iterator->key()) == 0) {
|
||||
assert(current.iterator->status().ok());
|
||||
to_populate.push_back(current);
|
||||
heap.pop();
|
||||
if (!heap.empty()) {
|
||||
current = heap.top();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the items back to the heap
|
||||
for (auto& item : to_populate) {
|
||||
heap.push(item);
|
||||
}
|
||||
populate_func_(to_populate);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+95
-516
@@ -3,37 +3,43 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/attribute_groups.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CoalescingIteratorTest : public DBTestBase {
|
||||
class MultiCfIteratorTest : public DBTestBase {
|
||||
public:
|
||||
CoalescingIteratorTest()
|
||||
: DBTestBase("coalescing_iterator_test", /*env_do_fsync=*/true) {}
|
||||
MultiCfIteratorTest()
|
||||
: DBTestBase("multi_cf_iterator_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
// Verify Iteration of CoalescingIterator
|
||||
// Verify Iteration of MultiCfIterator
|
||||
// by SeekToFirst() + Next() and SeekToLast() + Prev()
|
||||
void verifyCoalescingIterator(const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
const std::vector<Slice>& expected_keys,
|
||||
const std::vector<Slice>& expected_values,
|
||||
const std::optional<std::vector<WideColumns>>&
|
||||
expected_wide_columns = std::nullopt,
|
||||
const Slice* lower_bound = nullptr,
|
||||
const Slice* upper_bound = nullptr) {
|
||||
void verifyMultiCfIterator(
|
||||
const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
const std::vector<Slice>& expected_keys,
|
||||
const std::optional<std::vector<Slice>>& expected_values = std::nullopt,
|
||||
const std::optional<std::vector<WideColumns>>& expected_wide_columns =
|
||||
std::nullopt,
|
||||
const std::optional<std::vector<AttributeGroups>>&
|
||||
expected_attribute_groups = std::nullopt) {
|
||||
int i = 0;
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = lower_bound;
|
||||
read_options.iterate_upper_bound = upper_bound;
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs);
|
||||
db_->NewMultiCfIterator(ReadOptions(), cfhs);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
ASSERT_EQ(expected_values[i], iter->value());
|
||||
if (expected_values.has_value()) {
|
||||
ASSERT_EQ(expected_values.value()[i], iter->value());
|
||||
}
|
||||
if (expected_wide_columns.has_value()) {
|
||||
ASSERT_EQ(expected_wide_columns.value()[i], iter->columns());
|
||||
}
|
||||
if (expected_attribute_groups.has_value()) {
|
||||
// TODO - Add this back when attribute_groups() API is added
|
||||
// ASSERT_EQ(expected_attribute_groups.value()[i],
|
||||
// iter->attribute_groups());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
@@ -42,10 +48,17 @@ class CoalescingIteratorTest : public DBTestBase {
|
||||
int rev_i = i - 1;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(expected_keys[rev_i], iter->key());
|
||||
ASSERT_EQ(expected_values[rev_i], iter->value());
|
||||
if (expected_values.has_value()) {
|
||||
ASSERT_EQ(expected_values.value()[rev_i], iter->value());
|
||||
}
|
||||
if (expected_wide_columns.has_value()) {
|
||||
ASSERT_EQ(expected_wide_columns.value()[rev_i], iter->columns());
|
||||
}
|
||||
if (expected_attribute_groups.has_value()) {
|
||||
// TODO - Add this back when attribute_groups() API is added
|
||||
// ASSERT_EQ(expected_attribute_groups.value()[rev_i],
|
||||
// iter->attribute_groups());
|
||||
}
|
||||
rev_i--;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
@@ -65,20 +78,20 @@ class CoalescingIteratorTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CoalescingIteratorTest, InvalidArguments) {
|
||||
TEST_F(MultiCfIteratorTest, InvalidArguments) {
|
||||
Options options = GetDefaultOptions();
|
||||
{
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
// Invalid - No CF is provided
|
||||
std::unique_ptr<Iterator> iter_with_no_cf =
|
||||
db_->NewCoalescingIterator(ReadOptions(), {});
|
||||
db_->NewMultiCfIterator(ReadOptions(), {});
|
||||
ASSERT_NOK(iter_with_no_cf->status());
|
||||
ASSERT_TRUE(iter_with_no_cf->status().IsInvalidArgument());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
TEST_F(MultiCfIteratorTest, SimpleValues) {
|
||||
Options options = GetDefaultOptions();
|
||||
{
|
||||
// Case 1: Unique key per CF
|
||||
@@ -96,21 +109,19 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
// Test for iteration over CF default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values);
|
||||
verifyMultiCfIterator(cfhs_order_0_1_2_3, expected_keys, expected_values);
|
||||
|
||||
// Test for iteration over CF 3->1->default_cf->2
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_1_0_2 = {
|
||||
handles_[3], handles_[1], handles_[0], handles_[2]};
|
||||
// Iteration order and the return values should be the same since keys are
|
||||
// unique per CF
|
||||
verifyCoalescingIterator(cfhs_order_3_1_0_2, expected_keys,
|
||||
expected_values);
|
||||
verifyMultiCfIterator(cfhs_order_3_1_0_2, expected_keys, expected_values);
|
||||
|
||||
// Verify Seek()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
db_->NewMultiCfIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
iter->Seek("key_1");
|
||||
@@ -125,7 +136,7 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
// Verify SeekForPrev()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
db_->NewMultiCfIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_1");
|
||||
@@ -161,358 +172,56 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
// Test for iteration over CFs default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_3_val", "key_2_cf_2_val",
|
||||
"key_3_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values);
|
||||
std::vector<Slice> expected_values = {"key_1_cf_0_val", "key_2_cf_1_val",
|
||||
"key_3_cf_0_val"};
|
||||
verifyMultiCfIterator(cfhs_order_0_1_2_3, expected_keys, expected_values);
|
||||
|
||||
// Test for iteration over CFs 3->2->default_cf->1
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1 = {
|
||||
handles_[3], handles_[2], handles_[0], handles_[1]};
|
||||
expected_values = {"key_1_cf_0_val", "key_2_cf_1_val", "key_3_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values);
|
||||
expected_values = {"key_1_cf_3_val", "key_2_cf_2_val", "key_3_cf_3_val"};
|
||||
verifyMultiCfIterator(cfhs_order_3_2_0_1, expected_keys, expected_values);
|
||||
|
||||
// Verify Seek()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_3_2_0_1);
|
||||
db_->NewMultiCfIterator(ReadOptions(), cfhs_order_3_2_0_1);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_3_val");
|
||||
iter->Seek("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_3_val");
|
||||
iter->Seek("key_2");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_2_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_1_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_3_val");
|
||||
iter->Seek("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
// Verify SeekForPrev()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_3_2_0_1);
|
||||
db_->NewMultiCfIterator(ReadOptions(), cfhs_order_3_2_0_1);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_3_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_2_val");
|
||||
iter->SeekForPrev("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_1_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_3_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Options options = GetDefaultOptions();
|
||||
{
|
||||
// Case 1: Unique key per CF
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, "key_1", "key_1_cf_0_val"));
|
||||
ASSERT_OK(Put(1, "key_2", "key_2_cf_1_val"));
|
||||
ASSERT_OK(Put(2, "key_3", "key_3_cf_2_val"));
|
||||
ASSERT_OK(Put(3, "key_4", "key_4_cf_3_val"));
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
|
||||
// with lower_bound
|
||||
{
|
||||
// lower_bound is inclusive
|
||||
Slice lb = Slice("key_2");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3", "key_4"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_2_val",
|
||||
"key_4_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
{
|
||||
// upper_bound is exclusive
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_0_val", "key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
{
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_4");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
|
||||
{
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_4");
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = &lb;
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
// Verify Seek() with bounds
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_0_1_2_3);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_2_val");
|
||||
iter->Seek("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
// Verify SeekForPrev() with bounds
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_0_1_2_3);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_2");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_2_val");
|
||||
iter->SeekForPrev("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_2_val");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_2_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Case 2: Same key in multiple CFs
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, "key_1", "key_1_cf_0_val"));
|
||||
ASSERT_OK(Put(3, "key_1", "key_1_cf_3_val"));
|
||||
ASSERT_OK(Put(1, "key_2", "key_2_cf_1_val"));
|
||||
ASSERT_OK(Put(2, "key_2", "key_2_cf_2_val"));
|
||||
ASSERT_OK(Put(0, "key_3", "key_3_cf_0_val"));
|
||||
ASSERT_OK(Put(1, "key_3", "key_3_cf_1_val"));
|
||||
ASSERT_OK(Put(3, "key_3", "key_3_cf_3_val"));
|
||||
|
||||
// Test for iteration over CFs default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
// with lower_bound
|
||||
{
|
||||
// lower_bound is inclusive
|
||||
Slice lb = Slice("key_2");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_2_val", "key_3_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
{
|
||||
// upper_bound is exclusive
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_3_val", "key_2_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
{
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_2"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
|
||||
// Test for iteration over CFs 3->2->default_cf->1
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1 = {
|
||||
handles_[3], handles_[2], handles_[0], handles_[1]};
|
||||
{
|
||||
// lower_bound is inclusive
|
||||
Slice lb = Slice("key_2");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
{
|
||||
// upper_bound is exclusive
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_0_val", "key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
{
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_2"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
{
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_3");
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = &lb;
|
||||
read_options.iterate_upper_bound = &ub;
|
||||
// Verify Seek() with bounds
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_3_2_0_1);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->Seek("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
// Verify SeekForPrev() with bounds
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_3_2_0_1);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("key_2");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->SeekForPrev("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, ConsistentViewExplicitSnapshot) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.atomic_flush = true;
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush:done",
|
||||
"DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot"}});
|
||||
|
||||
bool flushed = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (!flushed) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val_new"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
flushed = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
ReadOptions read_options;
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
read_options.snapshot = snapshot;
|
||||
// Verify Seek()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_0_1_2_3);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf0_key->cf0_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf1_key->cf1_val");
|
||||
}
|
||||
// Verify SeekForPrev()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_0_1_2_3);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("cf2_key");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf2_key->cf2_val");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf1_key->cf1_val");
|
||||
}
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, ConsistentViewImplicitSnapshot) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val"));
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush:done",
|
||||
"DBImpl::MultiCFSnapshot::BeforeCheckingSnapshot"}});
|
||||
|
||||
bool flushed = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::MultiCFSnapshot::AfterRefSV", [&](void* /*arg*/) {
|
||||
if (!flushed) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ASSERT_OK(Put(i, "cf" + std::to_string(i) + "_key",
|
||||
"cf" + std::to_string(i) + "_val_new"));
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
flushed = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
// Verify Seek()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
iter->Seek("cf2_key");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf2_key->cf2_val_new");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf3_key->cf3_val_new");
|
||||
}
|
||||
// Verify SeekForPrev()
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), cfhs_order_0_1_2_3);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekForPrev("cf1_key");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf1_key->cf1_val_new");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "cf0_key->cf0_val_new");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, EmptyCfs) {
|
||||
TEST_F(MultiCfIteratorTest, EmptyCfs) {
|
||||
Options options = GetDefaultOptions();
|
||||
{
|
||||
// Case 1: No keys in any of the CFs
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
db_->NewMultiCfIterator(ReadOptions(), handles_);
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekToLast();
|
||||
@@ -528,7 +237,7 @@ TEST_F(CoalescingIteratorTest, EmptyCfs) {
|
||||
// Case 2: A single key exists in only one of the CF. Rest CFs are empty.
|
||||
ASSERT_OK(Put(1, "key_1", "key_1_cf_1_val"));
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
db_->NewMultiCfIterator(ReadOptions(), handles_);
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_1_val");
|
||||
iter->Next();
|
||||
@@ -542,21 +251,21 @@ TEST_F(CoalescingIteratorTest, EmptyCfs) {
|
||||
// Case 3: same key exists in all of the CFs except one (cf_2)
|
||||
ASSERT_OK(Put(0, "key_1", "key_1_cf_0_val"));
|
||||
ASSERT_OK(Put(3, "key_1", "key_1_cf_3_val"));
|
||||
// handles_ are in the order of 0->1->2->3
|
||||
// handles_ are in the order of 0->1->2->3. We should expect value from cf_0
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
db_->NewMultiCfIterator(ReadOptions(), handles_);
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_3_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
iter->SeekToLast();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_3_val");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, WideColumns) {
|
||||
TEST_F(MultiCfIteratorTest, WideColumns) {
|
||||
// Set up the DB and Column Families
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
@@ -565,65 +274,30 @@ TEST_F(CoalescingIteratorTest, WideColumns) {
|
||||
WideColumns key_1_columns_in_cf_2{
|
||||
{kDefaultWideColumnName, "cf_2_col_val_0_key_1"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_1"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"},
|
||||
{"cf_overlap_col_name", "cf_2_overlap_value_key_1"}};
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"}};
|
||||
WideColumns key_1_columns_in_cf_3{
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_1"},
|
||||
{"cf_3_col_name_2", "cf_3_col_val_2_key_1"},
|
||||
{"cf_3_col_name_3", "cf_3_col_val_3_key_1"},
|
||||
{"cf_overlap_col_name", "cf_3_overlap_value_key_1"}};
|
||||
WideColumns key_1_expected_columns_cfh_order_2_3{
|
||||
{kDefaultWideColumnName, "cf_2_col_val_0_key_1"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_1"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"},
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_1"},
|
||||
{"cf_3_col_name_2", "cf_3_col_val_2_key_1"},
|
||||
{"cf_3_col_name_3", "cf_3_col_val_3_key_1"},
|
||||
{"cf_overlap_col_name", "cf_3_overlap_value_key_1"}};
|
||||
WideColumns key_1_expected_columns_cfh_order_3_2{
|
||||
{kDefaultWideColumnName, "cf_2_col_val_0_key_1"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_1"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"},
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_1"},
|
||||
{"cf_3_col_name_2", "cf_3_col_val_2_key_1"},
|
||||
{"cf_3_col_name_3", "cf_3_col_val_3_key_1"},
|
||||
{"cf_overlap_col_name", "cf_2_overlap_value_key_1"}};
|
||||
{"cf_3_col_name_3", "cf_3_col_val_3_key_1"}};
|
||||
|
||||
constexpr char key_2[] = "key_2";
|
||||
WideColumns key_2_columns_in_cf_1{
|
||||
{"cf_overlap_col_name", "cf_1_overlap_value_key_2"}};
|
||||
{"cf_1_col_name_1", "cf_1_col_val_1_key_2"}};
|
||||
WideColumns key_2_columns_in_cf_2{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_2"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_2"},
|
||||
{"cf_overlap_col_name", "cf_2_overlap_value_key_2"}};
|
||||
WideColumns key_2_expected_columns_cfh_order_1_2{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_2"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_2"},
|
||||
{"cf_overlap_col_name", "cf_2_overlap_value_key_2"}};
|
||||
WideColumns key_2_expected_columns_cfh_order_2_1{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_2"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_2"},
|
||||
{"cf_overlap_col_name", "cf_1_overlap_value_key_2"}};
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_2"}};
|
||||
|
||||
constexpr char key_3[] = "key_3";
|
||||
WideColumns key_3_columns_in_cf_1{
|
||||
{"cf_1_col_name_1", "cf_1_col_val_1_key_3"}};
|
||||
WideColumns key_3_columns_in_cf_3{
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_3"}};
|
||||
WideColumns key_3_expected_columns{
|
||||
{"cf_1_col_name_1", "cf_1_col_val_1_key_3"},
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_3"},
|
||||
};
|
||||
|
||||
constexpr char key_4[] = "key_4";
|
||||
WideColumns key_4_columns_in_cf_0{
|
||||
{"cf_0_col_name_1", "cf_0_col_val_1_key_4"}};
|
||||
WideColumns key_4_columns_in_cf_2{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_4"}};
|
||||
WideColumns key_4_expected_columns{
|
||||
{"cf_0_col_name_1", "cf_0_col_val_1_key_4"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_4"},
|
||||
};
|
||||
|
||||
// Use AttributeGroup PutEntity API to insert them together
|
||||
AttributeGroups key_1_attribute_groups{
|
||||
@@ -644,47 +318,31 @@ TEST_F(CoalescingIteratorTest, WideColumns) {
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_3, key_3_attribute_groups));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_4, key_4_attribute_groups));
|
||||
|
||||
// Keys should be returned in order regardless of cfh order
|
||||
// Test for iteration over CF default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3, key_4};
|
||||
|
||||
// Pick what DBIter would return for value() in the first CF that key exists
|
||||
// Since value for kDefaultWideColumnName only exists for key_1, rest will
|
||||
// return empty value after coalesced
|
||||
// return empty value
|
||||
std::vector<Slice> expected_values = {"cf_2_col_val_0_key_1", "", "", ""};
|
||||
|
||||
// Test for iteration over CF default->1->2->3
|
||||
{
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
|
||||
// Coalesced columns
|
||||
std::vector<WideColumns> expected_wide_columns_0_1_2_3 = {
|
||||
key_1_expected_columns_cfh_order_2_3,
|
||||
key_2_expected_columns_cfh_order_1_2, key_3_expected_columns,
|
||||
key_4_expected_columns};
|
||||
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys, expected_values,
|
||||
expected_wide_columns_0_1_2_3);
|
||||
}
|
||||
|
||||
// Test for iteration over CF 3->2->default->1
|
||||
{
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1 = {
|
||||
handles_[3], handles_[2], handles_[0], handles_[1]};
|
||||
|
||||
// Coalesced columns
|
||||
std::vector<WideColumns> expected_wide_columns_3_2_0_1 = {
|
||||
key_1_expected_columns_cfh_order_3_2,
|
||||
key_2_expected_columns_cfh_order_2_1, key_3_expected_columns,
|
||||
key_4_expected_columns};
|
||||
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys, expected_values,
|
||||
expected_wide_columns_3_2_0_1);
|
||||
}
|
||||
// Pick columns from the first CF that the key exists and value is stored as
|
||||
// wide column
|
||||
std::vector<WideColumns> expected_wide_columns = {
|
||||
{{kDefaultWideColumnName, "cf_2_col_val_0_key_1"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_1"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"}},
|
||||
{{"cf_1_col_name_1", "cf_1_col_val_1_key_2"}},
|
||||
{{"cf_1_col_name_1", "cf_1_col_val_1_key_3"}},
|
||||
{{"cf_0_col_name_1", "cf_0_col_val_1_key_4"}}};
|
||||
verifyMultiCfIterator(cfhs_order_0_1_2_3, expected_keys, expected_values,
|
||||
expected_wide_columns);
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, DifferentComparatorsInMultiCFs) {
|
||||
TEST_F(MultiCfIteratorTest, DifferentComparatorsInMultiCFs) {
|
||||
// This test creates two column families with two different comparators.
|
||||
// Attempting to create the CoalescingIterator should fail.
|
||||
// Attempting to create the MultiCFIterator should fail.
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -704,15 +362,15 @@ TEST_F(CoalescingIteratorTest, DifferentComparatorsInMultiCFs) {
|
||||
verifyExpectedKeys(handles_[1], {"key_3", "key_2", "key_1"});
|
||||
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
db_->NewMultiCfIterator(ReadOptions(), handles_);
|
||||
ASSERT_NOK(iter->status());
|
||||
ASSERT_TRUE(iter->status().IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
TEST_F(MultiCfIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
// This test creates two column families with the same custom test
|
||||
// comparators (but instantiated independently). Attempting to create the
|
||||
// CoalescingIterator should not fail.
|
||||
// MultiCFIterator should not fail.
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -752,12 +410,12 @@ TEST_F(CoalescingIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
std::vector<Slice> expected_keys = {
|
||||
"key_001_003", "key_001_002", "key_001_001", "key_002_003", "key_002_002",
|
||||
"key_002_001", "key_003_006", "key_003_005", "key_003_004"};
|
||||
std::vector<Slice> expected_values = {"value_1_1", "value_1_2", "value_1_3",
|
||||
std::vector<Slice> expected_values = {"value_0_1", "value_0_2", "value_0_3",
|
||||
"value_0_4", "value_0_5", "value_0_6",
|
||||
"value_1_4", "value_1_5", "value_1_6"};
|
||||
int i = 0;
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
db_->NewMultiCfIterator(ReadOptions(), handles_);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
ASSERT_EQ(expected_values[i], iter->value());
|
||||
@@ -766,59 +424,7 @@ TEST_F(CoalescingIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
class AttributeGroupIteratorTest : public DBTestBase {
|
||||
public:
|
||||
AttributeGroupIteratorTest()
|
||||
: DBTestBase("attribute_group_iterator_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
void verifyAttributeGroupIterator(
|
||||
const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
const std::vector<Slice>& expected_keys,
|
||||
const std::vector<AttributeGroups>& expected_attribute_groups,
|
||||
const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr) {
|
||||
int i = 0;
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = lower_bound;
|
||||
read_options.iterate_upper_bound = upper_bound;
|
||||
std::unique_ptr<AttributeGroupIterator> iter =
|
||||
db_->NewAttributeGroupIterator(read_options, cfhs);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
auto iterator_attribute_groups = iter->attribute_groups();
|
||||
ASSERT_EQ(expected_attribute_groups[i].size(),
|
||||
iterator_attribute_groups.size());
|
||||
for (size_t cfh_i = 0; cfh_i < iterator_attribute_groups.size();
|
||||
cfh_i++) {
|
||||
ASSERT_EQ(expected_attribute_groups[i][cfh_i].column_family(),
|
||||
iterator_attribute_groups[cfh_i].column_family());
|
||||
ASSERT_EQ(expected_attribute_groups[i][cfh_i].columns(),
|
||||
iterator_attribute_groups[cfh_i].columns());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
int rev_i = i - 1;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(expected_keys[rev_i], iter->key());
|
||||
auto iterator_attribute_groups = iter->attribute_groups();
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i].size(),
|
||||
iterator_attribute_groups.size());
|
||||
for (size_t cfh_i = 0; cfh_i < iterator_attribute_groups.size();
|
||||
cfh_i++) {
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i][cfh_i].column_family(),
|
||||
iterator_attribute_groups[cfh_i].column_family());
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i][cfh_i].columns(),
|
||||
iterator_attribute_groups[cfh_i].columns());
|
||||
}
|
||||
rev_i--;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AttributeGroupIteratorTest, IterateAttributeGroups) {
|
||||
TEST_F(MultiCfIteratorTest, DISABLED_IterateAttributeGroups) {
|
||||
// Set up the DB and Column Families
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
@@ -873,40 +479,13 @@ TEST_F(AttributeGroupIteratorTest, IterateAttributeGroups) {
|
||||
// Test for iteration over CF default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3, key_4};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_1_attribute_groups, key_2_attribute_groups, key_3_attribute_groups,
|
||||
key_4_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups);
|
||||
}
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_4");
|
||||
// Test for lower bound only
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_2, key_3, key_4};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_2_attribute_groups, key_3_attribute_groups, key_4_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, &lb);
|
||||
}
|
||||
// Test for upper bound only
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_1_attribute_groups, key_2_attribute_groups, key_3_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, nullptr, &ub);
|
||||
}
|
||||
// Test for lower and upper bound
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_2, key_3};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_2_attribute_groups, key_3_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, &lb, &ub);
|
||||
}
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3, key_4};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_1_attribute_groups, key_2_attribute_groups, key_3_attribute_groups,
|
||||
key_4_attribute_groups};
|
||||
verifyMultiCfIterator(
|
||||
cfhs_order_0_1_2_3, expected_keys, std::nullopt /* expected_values */,
|
||||
std::nullopt /* expected_wide_columns */, expected_attribute_groups);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+3
-24
@@ -2777,7 +2777,7 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
}
|
||||
}
|
||||
|
||||
if (!blob_ctxs.empty()) {
|
||||
if (s.ok() && !blob_ctxs.empty()) {
|
||||
MultiGetBlob(read_options, keys_with_blobs_range, blob_ctxs);
|
||||
}
|
||||
|
||||
@@ -6013,8 +6013,7 @@ Status VersionSet::GetCurrentManifestPath(const std::string& dbname,
|
||||
|
||||
Status VersionSet::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
std::string* db_id, bool no_error_if_files_missing, bool is_retry,
|
||||
Status* log_status) {
|
||||
std::string* db_id, bool no_error_if_files_missing) {
|
||||
const ReadOptions read_options(Env::IOActivity::kDBOpen);
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest
|
||||
// file
|
||||
@@ -6039,11 +6038,8 @@ Status VersionSet::Recover(
|
||||
}
|
||||
manifest_file_reader.reset(new SequentialFileReader(
|
||||
std::move(manifest_file), manifest_path,
|
||||
db_options_->log_readahead_size, io_tracer_, db_options_->listeners,
|
||||
/*rate_limiter=*/nullptr, is_retry));
|
||||
db_options_->log_readahead_size, io_tracer_, db_options_->listeners));
|
||||
}
|
||||
TEST_SYNC_POINT("VersionSet::Recover:StartManifestRead");
|
||||
|
||||
uint64_t current_manifest_file_size = 0;
|
||||
uint64_t log_number = 0;
|
||||
{
|
||||
@@ -6067,9 +6063,6 @@ Status VersionSet::Recover(
|
||||
if (s.ok()) {
|
||||
RecoverEpochNumbers();
|
||||
}
|
||||
if (log_status) {
|
||||
*log_status = log_read_status;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -7144,20 +7137,6 @@ Status VersionSet::GetMetadataForFile(uint64_t number, int* filelevel,
|
||||
}
|
||||
|
||||
void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
assert(metadata);
|
||||
size_t count = 0;
|
||||
for (auto cfd : *column_family_set_) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
count += cfd->current()->storage_info()->LevelFiles(level).size();
|
||||
}
|
||||
}
|
||||
metadata->reserve(count);
|
||||
for (auto cfd : *column_family_set_) {
|
||||
if (cfd->IsDropped() || !cfd->initialized()) {
|
||||
continue;
|
||||
|
||||
+1
-2
@@ -1258,8 +1258,7 @@ class VersionSet {
|
||||
// are not opened
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, std::string* db_id = nullptr,
|
||||
bool no_error_if_files_missing = false, bool is_retry = false,
|
||||
Status* log_status = nullptr);
|
||||
bool no_error_if_files_missing = false);
|
||||
|
||||
Status TryRecover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only,
|
||||
|
||||
@@ -1723,77 +1723,6 @@ TEST_F(DBWideBasicTest, PutEntitySerializationError) {
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
}
|
||||
|
||||
TEST_F(DBWideBasicTest, PinnableWideColumnsMove) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
constexpr char key1[] = "foo";
|
||||
constexpr char value[] = "bar";
|
||||
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), key1, value));
|
||||
|
||||
constexpr char key2[] = "baz";
|
||||
const WideColumns columns{{"quux", "corge"}};
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), db_->DefaultColumnFamily(), key2,
|
||||
columns));
|
||||
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
const auto test_move = [&](bool fill_cache) {
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = fill_cache;
|
||||
|
||||
{
|
||||
const WideColumns expected_columns{{kDefaultWideColumnName, value}};
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(read_options, db_->DefaultColumnFamily(), key1,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
|
||||
PinnableWideColumns move_target(std::move(result));
|
||||
ASSERT_EQ(move_target.columns(), expected_columns);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(read_options, db_->DefaultColumnFamily(), key1,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
|
||||
PinnableWideColumns move_target;
|
||||
move_target = std::move(result);
|
||||
ASSERT_EQ(move_target.columns(), expected_columns);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(read_options, db_->DefaultColumnFamily(), key2,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), columns);
|
||||
|
||||
PinnableWideColumns move_target(std::move(result));
|
||||
ASSERT_EQ(move_target.columns(), columns);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(read_options, db_->DefaultColumnFamily(), key2,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), columns);
|
||||
|
||||
PinnableWideColumns move_target;
|
||||
move_target = std::move(result);
|
||||
ASSERT_EQ(move_target.columns(), columns);
|
||||
}
|
||||
};
|
||||
|
||||
// Test with and without fill_cache to cover both the case when pointers are
|
||||
// invalidated during PinnableSlice's move and when they are not.
|
||||
test_move(/* fill_cache*/ false);
|
||||
test_move(/* fill_cache*/ true);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -11,11 +12,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const Slice kDefaultWideColumnName;
|
||||
|
||||
const WideColumns kNoWideColumns;
|
||||
const AttributeGroups kNoAttributeGroups;
|
||||
|
||||
Status PinnableWideColumns::CreateIndexForWideColumns() {
|
||||
columns_.clear();
|
||||
|
||||
Slice value_copy = value_;
|
||||
|
||||
return WideColumnSerialization::Deserialize(value_copy, columns_);
|
||||
}
|
||||
|
||||
|
||||
+13
-40
@@ -484,22 +484,13 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
return Status::Corruption("bad WriteBatch TimedPut");
|
||||
}
|
||||
FALLTHROUGH_INTENDED;
|
||||
case kTypeValuePreferredSeqno: {
|
||||
Slice packed_value;
|
||||
case kTypeValuePreferredSeqno:
|
||||
if (!GetLengthPrefixedSlice(input, key) ||
|
||||
!GetLengthPrefixedSlice(input, &packed_value)) {
|
||||
!GetLengthPrefixedSlice(input, value) ||
|
||||
!GetFixed64(input, write_unix_time)) {
|
||||
return Status::Corruption("bad WriteBatch TimedPut");
|
||||
}
|
||||
if (write_unix_time) {
|
||||
std::tie(*value, *write_unix_time) =
|
||||
ParsePackedValueWithWriteTime(packed_value);
|
||||
} else {
|
||||
// Caller doesn't want to unpack write_unix_time, so keep it packed in
|
||||
// the value.
|
||||
*value = packed_value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
}
|
||||
@@ -892,11 +883,12 @@ Status WriteBatchInternal::TimedPut(WriteBatch* b, uint32_t column_family_id,
|
||||
b->rep_.push_back(static_cast<char>(kTypeColumnFamilyValuePreferredSeqno));
|
||||
PutVarint32(&b->rep_, column_family_id);
|
||||
}
|
||||
std::string value_buf;
|
||||
Slice packed_value =
|
||||
PackValueAndWriteTime(value, write_unix_time, &value_buf);
|
||||
PutLengthPrefixedSlice(&b->rep_, key);
|
||||
PutLengthPrefixedSlice(&b->rep_, packed_value);
|
||||
PutLengthPrefixedSlice(&b->rep_, value);
|
||||
// For a kTypeValuePreferredSeqno entry, its write time is encoded separately
|
||||
// from value in an encoded WriteBatch. They are packed into one value Slice
|
||||
// once it's written to the database.
|
||||
PutFixed64(&b->rep_, write_unix_time);
|
||||
|
||||
b->content_flags_.store(b->content_flags_.load(std::memory_order_relaxed) |
|
||||
ContentFlags::HAS_TIMED_PUT,
|
||||
@@ -907,7 +899,7 @@ Status WriteBatchInternal::TimedPut(WriteBatch* b, uint32_t column_family_id,
|
||||
// `kTypeColumnFamilyValuePreferredSeqno` here.
|
||||
b->prot_info_->entries_.emplace_back(
|
||||
ProtectionInfo64()
|
||||
.ProtectKVO(key, packed_value, kTypeValuePreferredSeqno)
|
||||
.ProtectKVO(key, value, kTypeValuePreferredSeqno)
|
||||
.ProtectC(column_family_id));
|
||||
}
|
||||
return save.commit();
|
||||
@@ -1787,6 +1779,7 @@ Status WriteBatch::VerifyChecksum() const {
|
||||
Slice input(rep_.data() + WriteBatchInternal::kHeader,
|
||||
rep_.size() - WriteBatchInternal::kHeader);
|
||||
Slice key, value, blob, xid;
|
||||
uint64_t unix_write_time = 0;
|
||||
char tag = 0;
|
||||
uint32_t column_family = 0; // default
|
||||
Status s;
|
||||
@@ -1799,7 +1792,7 @@ Status WriteBatch::VerifyChecksum() const {
|
||||
value.clear();
|
||||
column_family = 0;
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob, &xid, /*write_unix_time=*/nullptr);
|
||||
&blob, &xid, &unix_write_time);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -2521,11 +2514,6 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
assert(ret_status.ok());
|
||||
|
||||
if (db_ != nullptr) {
|
||||
if (db_->immutable_db_options().row_cache) {
|
||||
ret_status.PermitUncheckedError();
|
||||
return Status::NotSupported(
|
||||
"DeleteRange is not compatible with row cache.");
|
||||
}
|
||||
auto cf_handle = cf_mems_->GetColumnFamilyHandle();
|
||||
if (cf_handle == nullptr) {
|
||||
cf_handle = db_->DefaultColumnFamily();
|
||||
@@ -3174,12 +3162,8 @@ class ProtectionInfoUpdater : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
Status TimedPutCF(uint32_t cf, const Slice& key, const Slice& val,
|
||||
uint64_t unix_write_time) override {
|
||||
std::string encoded_write_time;
|
||||
PutFixed64(&encoded_write_time, unix_write_time);
|
||||
std::array<Slice, 2> value_with_time{{val, encoded_write_time}};
|
||||
SliceParts packed_value(value_with_time.data(), 2);
|
||||
return UpdateProtInfo(cf, key, packed_value, kTypeValuePreferredSeqno);
|
||||
uint64_t /*unix_write_time*/) override {
|
||||
return UpdateProtInfo(cf, key, val, kTypeValuePreferredSeqno);
|
||||
}
|
||||
|
||||
Status PutEntityCF(uint32_t cf, const Slice& key,
|
||||
@@ -3238,17 +3222,6 @@ class ProtectionInfoUpdater : public WriteBatch::Handler {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status UpdateProtInfo(uint32_t cf, const Slice& key, const SliceParts& val,
|
||||
const ValueType op_type) {
|
||||
if (prot_info_) {
|
||||
prot_info_->entries_.emplace_back(
|
||||
ProtectionInfo64()
|
||||
.ProtectKVO(SliceParts(&key, 1), val, op_type)
|
||||
.ProtectC(cf));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// No copy or move.
|
||||
ProtectionInfoUpdater(const ProtectionInfoUpdater&) = delete;
|
||||
ProtectionInfoUpdater(ProtectionInfoUpdater&&) = delete;
|
||||
|
||||
@@ -179,8 +179,7 @@ TEST_P(WriteCallbackPTest, WriteWithCallbackTest) {
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
auto open_s = DBImpl::Open(db_options, dbname, column_families, &handles,
|
||||
&db, seq_per_batch_, true /* batch_per_txn */,
|
||||
false /* is_retry */, nullptr /* can_retry */);
|
||||
&db, seq_per_batch_, true /* batch_per_txn */);
|
||||
ASSERT_OK(open_s);
|
||||
assert(handles.size() == 1);
|
||||
delete handles[0];
|
||||
|
||||
@@ -42,7 +42,6 @@ class BatchedOpsStressTest : public StressTest {
|
||||
ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]];
|
||||
assert(cfh);
|
||||
|
||||
Status status;
|
||||
for (int i = 9; i >= 0; --i) {
|
||||
const std::string num = std::to_string(i);
|
||||
|
||||
@@ -52,37 +51,28 @@ class BatchedOpsStressTest : public StressTest {
|
||||
// batched, non-batched, CF consistency).
|
||||
const std::string k = num + key_body;
|
||||
const std::string v = value_body + num;
|
||||
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_timed_put_one_in > 0 &&
|
||||
((value_base + kLargePrimeForCommonFactorSkew) %
|
||||
FLAGS_use_timed_put_one_in) == 0) {
|
||||
uint64_t write_unix_time = GetWriteUnixTime(thread);
|
||||
status = batch.TimedPut(cfh, k, v, write_unix_time);
|
||||
batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_merge) {
|
||||
status = batch.Merge(cfh, k, v);
|
||||
batch.Merge(cfh, k, v);
|
||||
} else {
|
||||
status = batch.Put(cfh, k, v);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
batch.Put(cfh, k, v);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
status = db_->Write(write_opts, &batch);
|
||||
}
|
||||
const Status s = db_->Write(write_opts, &batch);
|
||||
|
||||
if (!status.ok()) {
|
||||
fprintf(stderr, "multiput error: %s\n", status.ToString().c_str());
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "multiput error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
} else {
|
||||
// we did 10 writes each of size sz + 1
|
||||
thread->stats.AddBytesForWrites(10, (sz + 1) * 10);
|
||||
}
|
||||
|
||||
return status;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Given a key K, this deletes ("0"+K), ("1"+K), ..., ("9"+K)
|
||||
|
||||
@@ -36,43 +36,31 @@ class CfConsistencyStressTest : public StressTest {
|
||||
|
||||
WriteBatch batch;
|
||||
|
||||
Status status;
|
||||
for (auto cf : rand_column_families) {
|
||||
ColumnFamilyHandle* const cfh = column_families_[cf];
|
||||
assert(cfh);
|
||||
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
status = batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_timed_put_one_in > 0 &&
|
||||
((value_base + kLargePrimeForCommonFactorSkew) %
|
||||
FLAGS_use_timed_put_one_in) == 0) {
|
||||
uint64_t write_unix_time = GetWriteUnixTime(thread);
|
||||
status = batch.TimedPut(cfh, k, v, write_unix_time);
|
||||
batch.PutEntity(cfh, k, GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_merge) {
|
||||
status = batch.Merge(cfh, k, v);
|
||||
batch.Merge(cfh, k, v);
|
||||
} else {
|
||||
status = batch.Put(cfh, k, v);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
batch.Put(cfh, k, v);
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
status = db_->Write(write_opts, &batch);
|
||||
}
|
||||
Status s = db_->Write(write_opts, &batch);
|
||||
|
||||
if (!status.ok()) {
|
||||
fprintf(stderr, "multi put or merge error: %s\n",
|
||||
status.ToString().c_str());
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "multi put or merge error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
} else {
|
||||
auto num = static_cast<long>(rand_column_families.size());
|
||||
thread->stats.AddBytesForWrites(num, (sz + 1) * num);
|
||||
}
|
||||
|
||||
return status;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestDelete(ThreadState* thread, WriteOptions& write_opts,
|
||||
|
||||
@@ -391,27 +391,6 @@ std::string GetNowNanos() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t GetWriteUnixTime(ThreadState* thread) {
|
||||
static uint64_t kPreserveSeconds =
|
||||
std::max(FLAGS_preserve_internal_time_seconds,
|
||||
FLAGS_preclude_last_level_data_seconds);
|
||||
static uint64_t kFallbackTime = std::numeric_limits<uint64_t>::max();
|
||||
int64_t write_time = 0;
|
||||
Status s = db_stress_env->GetCurrentTime(&write_time);
|
||||
uint32_t write_time_mode = thread->rand.Uniform(3);
|
||||
if (write_time_mode == 0 || !s.ok()) {
|
||||
return kFallbackTime;
|
||||
} else if (write_time_mode == 1) {
|
||||
uint64_t delta = kPreserveSeconds > 0
|
||||
? static_cast<uint64_t>(thread->rand.Uniform(
|
||||
static_cast<int>(kPreserveSeconds)))
|
||||
: 0;
|
||||
return static_cast<uint64_t>(write_time) - delta;
|
||||
} else {
|
||||
return static_cast<uint64_t>(write_time) - kPreserveSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class MyXXH64Checksum : public FileChecksumGenerator {
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "monitoring/histogram.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -137,8 +136,7 @@ DECLARE_int32(universal_min_merge_width);
|
||||
DECLARE_int32(universal_max_merge_width);
|
||||
DECLARE_int32(universal_max_size_amplification_percent);
|
||||
DECLARE_int32(clear_column_family_one_in);
|
||||
DECLARE_int32(get_live_files_apis_one_in);
|
||||
DECLARE_int32(get_all_column_family_metadata_one_in);
|
||||
DECLARE_int32(get_live_files_one_in);
|
||||
DECLARE_int32(get_sorted_wal_files_one_in);
|
||||
DECLARE_int32(get_current_wal_file_one_in);
|
||||
DECLARE_int32(set_options_one_in);
|
||||
@@ -157,7 +155,6 @@ DECLARE_int32(unpartitioned_pinning);
|
||||
DECLARE_string(cache_type);
|
||||
DECLARE_uint64(subcompactions);
|
||||
DECLARE_uint64(periodic_compaction_seconds);
|
||||
DECLARE_string(daily_offpeak_time_utc);
|
||||
DECLARE_uint64(compaction_ttl);
|
||||
DECLARE_bool(fifo_allow_compaction);
|
||||
DECLARE_bool(allow_concurrent_memtable_write);
|
||||
@@ -212,8 +209,6 @@ DECLARE_int32(compact_range_one_in);
|
||||
DECLARE_int32(mark_for_compaction_one_file_in);
|
||||
DECLARE_int32(flush_one_in);
|
||||
DECLARE_int32(pause_background_one_in);
|
||||
DECLARE_int32(disable_file_deletions_one_in);
|
||||
DECLARE_int32(disable_manual_compaction_one_in);
|
||||
DECLARE_int32(compact_range_width);
|
||||
DECLARE_int32(acquire_snapshot_one_in);
|
||||
DECLARE_bool(compare_full_db_state_snapshot);
|
||||
@@ -262,12 +257,8 @@ DECLARE_int32(verify_file_checksums_one_in);
|
||||
DECLARE_int32(verify_db_one_in);
|
||||
DECLARE_int32(continuous_verification_interval);
|
||||
DECLARE_int32(get_property_one_in);
|
||||
DECLARE_int32(get_properties_of_all_tables_one_in);
|
||||
DECLARE_string(file_checksum_impl);
|
||||
DECLARE_bool(verification_only);
|
||||
DECLARE_string(last_level_temperature);
|
||||
DECLARE_string(default_write_temperature);
|
||||
DECLARE_string(default_temperature);
|
||||
|
||||
// Options for transaction dbs.
|
||||
// Use TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
@@ -349,9 +340,9 @@ DECLARE_uint32(memtable_max_range_deletions);
|
||||
DECLARE_uint32(bottommost_file_compaction_delay);
|
||||
|
||||
// Tiered storage
|
||||
DECLARE_bool(enable_tiered_storage); // set last_level_temperature
|
||||
DECLARE_int64(preclude_last_level_data_seconds);
|
||||
DECLARE_int64(preserve_internal_time_seconds);
|
||||
DECLARE_uint32(use_timed_put_one_in);
|
||||
|
||||
DECLARE_int32(verify_iterator_with_expected_state_one_in);
|
||||
DECLARE_bool(preserve_unverified_changes);
|
||||
@@ -401,21 +392,10 @@ DECLARE_double(low_pri_pool_ratio);
|
||||
DECLARE_uint64(soft_pending_compaction_bytes_limit);
|
||||
DECLARE_uint64(hard_pending_compaction_bytes_limit);
|
||||
DECLARE_uint64(max_sequential_skip_in_iterations);
|
||||
DECLARE_bool(enable_sst_partitioner_factory);
|
||||
DECLARE_bool(enable_do_not_compress_roles);
|
||||
DECLARE_bool(block_align);
|
||||
DECLARE_uint32(lowest_used_cache_tier);
|
||||
DECLARE_bool(enable_custom_split_merge);
|
||||
DECLARE_uint32(adm_policy);
|
||||
DECLARE_bool(enable_memtable_insert_with_hint_prefix_extractor);
|
||||
DECLARE_bool(check_multiget_consistency);
|
||||
DECLARE_bool(check_multiget_entity_consistency);
|
||||
DECLARE_bool(inplace_update_support);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
constexpr int kValueMaxLen = 100;
|
||||
constexpr uint32_t kLargePrimeForCommonFactorSkew = 1872439133;
|
||||
|
||||
// wrapped posix environment
|
||||
extern ROCKSDB_NAMESPACE::Env* db_stress_env;
|
||||
@@ -507,28 +487,6 @@ inline std::string ChecksumTypeToString(ROCKSDB_NAMESPACE::ChecksumType ctype) {
|
||||
return iter->first;
|
||||
}
|
||||
|
||||
inline enum ROCKSDB_NAMESPACE::Temperature StringToTemperature(
|
||||
const char* ctype) {
|
||||
assert(ctype);
|
||||
auto iter = std::find_if(
|
||||
ROCKSDB_NAMESPACE::temperature_to_string.begin(),
|
||||
ROCKSDB_NAMESPACE::temperature_to_string.end(),
|
||||
[&](const std::pair<ROCKSDB_NAMESPACE::Temperature, std::string>&
|
||||
temp_and_string_val) {
|
||||
return ctype == temp_and_string_val.second;
|
||||
});
|
||||
assert(iter != ROCKSDB_NAMESPACE::temperature_to_string.end());
|
||||
return iter->first;
|
||||
}
|
||||
|
||||
inline std::string TemperatureToString(
|
||||
ROCKSDB_NAMESPACE::Temperature temperature) {
|
||||
auto iter =
|
||||
ROCKSDB_NAMESPACE::OptionsHelper::temperature_to_string.find(temperature);
|
||||
assert(iter != ROCKSDB_NAMESPACE::OptionsHelper::temperature_to_string.end());
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> SplitString(std::string src) {
|
||||
std::vector<std::string> ret;
|
||||
if (src.empty()) {
|
||||
@@ -768,8 +726,6 @@ int64_t GetOneHotKeyID(double rand_seed, int64_t max_key);
|
||||
|
||||
std::string GetNowNanos();
|
||||
|
||||
uint64_t GetWriteUnixTime(ThreadState* thread);
|
||||
|
||||
std::shared_ptr<FileChecksumGenFactory> GetFileChecksumImpl(
|
||||
const std::string& name);
|
||||
|
||||
|
||||
@@ -311,17 +311,9 @@ DEFINE_int32(clear_column_family_one_in, 1000000,
|
||||
"it again. If N == 0, never drop/create column families. "
|
||||
"When test_batches_snapshots is true, this flag has no effect");
|
||||
|
||||
DEFINE_int32(
|
||||
get_live_files_apis_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetLiveFiles(), GetLiveFilesMetaData() and "
|
||||
"GetLiveFilesChecksumInfo() to verify if it returns "
|
||||
"OK or violate any internal assertion. If N == 0, do not call the "
|
||||
"interface.");
|
||||
|
||||
DEFINE_int32(
|
||||
get_all_column_family_metadata_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetAllColumnFamilyMetaData to verify if it "
|
||||
"violates any internal assertion. If N == 0, do not call the interface.");
|
||||
DEFINE_int32(get_live_files_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetLiveFiles to verify if it returns "
|
||||
"correctly. If N == 0, do not call the interface.");
|
||||
|
||||
DEFINE_int32(
|
||||
get_sorted_wal_files_one_in, 1000000,
|
||||
@@ -403,8 +395,6 @@ DEFINE_uint64(subcompactions, 1,
|
||||
|
||||
DEFINE_uint64(periodic_compaction_seconds, 1000,
|
||||
"Files older than this value will be picked up for compaction.");
|
||||
DEFINE_string(daily_offpeak_time_utc, "",
|
||||
"If set, process periodic compactions during this period only");
|
||||
|
||||
DEFINE_uint64(compaction_ttl, 1000,
|
||||
"Files older than TTL will be compacted to the next level.");
|
||||
@@ -526,6 +516,8 @@ DEFINE_int32(prepopulate_blob_cache, 0,
|
||||
"[Integrated BlobDB] Pre-populate hot/warm blobs in blob cache. 0 "
|
||||
"to disable and 1 to insert during flush.");
|
||||
|
||||
DEFINE_bool(enable_tiered_storage, false, "Set last_level_temperature");
|
||||
|
||||
DEFINE_int64(preclude_last_level_data_seconds, 0,
|
||||
"Preclude data from the last level. Used with tiered storage "
|
||||
"feature to preclude new data from comacting to the last level.");
|
||||
@@ -534,10 +526,6 @@ DEFINE_int64(
|
||||
preserve_internal_time_seconds, 0,
|
||||
"Preserve internal time information which is attached to each SST.");
|
||||
|
||||
DEFINE_uint32(use_timed_put_one_in, 0,
|
||||
"If greater than zero, TimedPut is used per every N write ops on "
|
||||
"on average.");
|
||||
|
||||
static const bool FLAGS_subcompactions_dummy __attribute__((__unused__)) =
|
||||
RegisterFlagValidator(&FLAGS_subcompactions, &ValidateUint32Range);
|
||||
|
||||
@@ -770,15 +758,6 @@ DEFINE_int32(pause_background_one_in, 0,
|
||||
"If non-zero, then PauseBackgroundWork()+Continue will be called "
|
||||
"once for every N ops on average. 0 disables.");
|
||||
|
||||
DEFINE_int32(disable_file_deletions_one_in, 0,
|
||||
"If non-zero, then DisableFileDeletions()+Enable will be called "
|
||||
"once for every N ops on average. 0 disables.");
|
||||
|
||||
DEFINE_int32(
|
||||
disable_manual_compaction_one_in, 0,
|
||||
"If non-zero, then DisableManualCompaction()+Enable will be called "
|
||||
"once for every N ops on average. 0 disables.");
|
||||
|
||||
DEFINE_int32(compact_range_width, 10000,
|
||||
"The width of the ranges passed to CompactRange().");
|
||||
|
||||
@@ -1002,11 +981,6 @@ DEFINE_int32(get_property_one_in, 1000,
|
||||
" properties for every N ops on average. 0 indicates that"
|
||||
" GetProperty() will be not be called.");
|
||||
|
||||
DEFINE_int32(get_properties_of_all_tables_one_in, 1000,
|
||||
"If non-zero, then DB::GetPropertiesOfAllTables() will be called "
|
||||
"for every N ops on average. 0 indicates that"
|
||||
" it will be not be called.");
|
||||
|
||||
DEFINE_bool(sync_fault_injection, false,
|
||||
"If true, FaultInjectionTestFS will be used for write operations, "
|
||||
"and unsynced data in DB will lost after crash. In such a case we "
|
||||
@@ -1330,59 +1304,4 @@ DEFINE_uint64(soft_pending_compaction_bytes_limit,
|
||||
DEFINE_uint64(hard_pending_compaction_bytes_limit,
|
||||
ROCKSDB_NAMESPACE::Options().hard_pending_compaction_bytes_limit,
|
||||
"Options.hard_pending_compaction_bytes_limit");
|
||||
|
||||
DEFINE_bool(enable_sst_partitioner_factory, false,
|
||||
"If true, set Options.sst_partitioner_factory to "
|
||||
"SstPartitionerFixedPrefixFactory with prefix length equal to 1");
|
||||
|
||||
DEFINE_bool(
|
||||
enable_do_not_compress_roles, false,
|
||||
"If true, set CompressedSecondaryCacheOptions.do_not_compress_roles to "
|
||||
"include all cache roles");
|
||||
|
||||
DEFINE_bool(block_align,
|
||||
ROCKSDB_NAMESPACE::BlockBasedTableOptions().block_align,
|
||||
"BlockBasedTableOptions.block_align");
|
||||
|
||||
DEFINE_uint32(
|
||||
lowest_used_cache_tier,
|
||||
static_cast<uint32_t>(ROCKSDB_NAMESPACE::Options().lowest_used_cache_tier),
|
||||
"Options.lowest_used_cache_tier");
|
||||
|
||||
DEFINE_bool(enable_custom_split_merge,
|
||||
ROCKSDB_NAMESPACE::CompressedSecondaryCacheOptions()
|
||||
.enable_custom_split_merge,
|
||||
"CompressedSecondaryCacheOptions.enable_custom_split_merge");
|
||||
|
||||
DEFINE_uint32(
|
||||
adm_policy,
|
||||
static_cast<uint32_t>(ROCKSDB_NAMESPACE::TieredCacheOptions().adm_policy),
|
||||
"TieredCacheOptions.adm_policy");
|
||||
|
||||
DEFINE_string(last_level_temperature, "kUnknown",
|
||||
"Options.last_level_temperature");
|
||||
|
||||
DEFINE_string(default_write_temperature, "kUnknown",
|
||||
"Options.default_write_temperature");
|
||||
|
||||
DEFINE_string(default_temperature, "kUnknown", "Options.default_temperature");
|
||||
|
||||
DEFINE_bool(enable_memtable_insert_with_hint_prefix_extractor,
|
||||
ROCKSDB_NAMESPACE::Options()
|
||||
.memtable_insert_with_hint_prefix_extractor != nullptr,
|
||||
"If true and FLAGS_prefix_size > 0, set "
|
||||
"Options.memtable_insert_with_hint_prefix_extractor to "
|
||||
"be Options.prefix_extractor");
|
||||
|
||||
DEFINE_bool(check_multiget_consistency, true,
|
||||
"If true, check consistency of MultiGet result by comparing it "
|
||||
"with Get's under a snapshot");
|
||||
|
||||
DEFINE_bool(check_multiget_entity_consistency, true,
|
||||
"If true, check consistency of MultiGetEntity result by comparing "
|
||||
"it GetEntity's under a snapshot");
|
||||
|
||||
DEFINE_bool(inplace_update_support,
|
||||
ROCKSDB_NAMESPACE::Options().inplace_update_support,
|
||||
"Options.inplace_update_support");
|
||||
#endif // GFLAGS
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <thread>
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "util/compression.h"
|
||||
#ifdef GFLAGS
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
@@ -143,10 +142,6 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = FLAGS_compressed_secondary_cache_size;
|
||||
opts.compress_format_version = FLAGS_compress_format_version;
|
||||
if (FLAGS_enable_do_not_compress_roles) {
|
||||
opts.do_not_compress_roles = {CacheEntryRoleSet::All()};
|
||||
}
|
||||
opts.enable_custom_split_merge = FLAGS_enable_custom_split_merge;
|
||||
secondary_cache = NewCompressedSecondaryCache(opts);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(stderr, "Failed to allocate compressed secondary cache\n");
|
||||
@@ -188,15 +183,6 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
tiered_opts.cache_type = PrimaryCacheType::kCacheTypeHCC;
|
||||
tiered_opts.total_capacity = cache_size;
|
||||
tiered_opts.compressed_secondary_ratio = 0.5;
|
||||
tiered_opts.adm_policy =
|
||||
static_cast<TieredAdmissionPolicy>(FLAGS_adm_policy);
|
||||
if (tiered_opts.adm_policy ==
|
||||
TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
CompressedSecondaryCacheOptions nvm_sec_cache_opts;
|
||||
nvm_sec_cache_opts.capacity = cache_size;
|
||||
tiered_opts.nvm_sec_cache =
|
||||
NewCompressedSecondaryCache(nvm_sec_cache_opts);
|
||||
}
|
||||
block_cache = NewTieredCache(tiered_opts);
|
||||
} else {
|
||||
opts.secondary_cache = std::move(secondary_cache);
|
||||
@@ -217,15 +203,6 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
tiered_opts.cache_type = PrimaryCacheType::kCacheTypeLRU;
|
||||
tiered_opts.total_capacity = cache_size;
|
||||
tiered_opts.compressed_secondary_ratio = 0.5;
|
||||
tiered_opts.adm_policy =
|
||||
static_cast<TieredAdmissionPolicy>(FLAGS_adm_policy);
|
||||
if (tiered_opts.adm_policy ==
|
||||
TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
CompressedSecondaryCacheOptions nvm_sec_cache_opts;
|
||||
nvm_sec_cache_opts.capacity = cache_size;
|
||||
tiered_opts.nvm_sec_cache =
|
||||
NewCompressedSecondaryCache(nvm_sec_cache_opts);
|
||||
}
|
||||
block_cache = NewTieredCache(tiered_opts);
|
||||
} else {
|
||||
opts.secondary_cache = std::move(secondary_cache);
|
||||
@@ -1013,20 +990,10 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
}
|
||||
|
||||
// Verify GetLiveFiles with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_live_files_apis_one_in) &&
|
||||
if (thread->rand.OneInOpt(FLAGS_get_live_files_one_in) &&
|
||||
!FLAGS_write_fault_one_in) {
|
||||
Status s_1 = VerifyGetLiveFiles();
|
||||
ProcessStatus(shared, "VerifyGetLiveFiles", s_1);
|
||||
Status s_2 = VerifyGetLiveFilesMetaData();
|
||||
ProcessStatus(shared, "VerifyGetLiveFilesMetaData", s_2);
|
||||
Status s_3 = VerifyGetLiveFilesStorageInfo();
|
||||
ProcessStatus(shared, "VerifyGetLiveFilesStorageInfo", s_3);
|
||||
}
|
||||
|
||||
// Verify GetAllColumnFamilyMetaData with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_all_column_family_metadata_one_in)) {
|
||||
Status status = VerifyGetAllColumnFamilyMetaData();
|
||||
ProcessStatus(shared, "VerifyGetAllColumnFamilyMetaData", status);
|
||||
Status status = VerifyGetLiveFiles();
|
||||
ProcessStatus(shared, "VerifyGetLiveFiles", status);
|
||||
}
|
||||
|
||||
// Verify GetSortedWalFiles with a 1 in N chance.
|
||||
@@ -1046,16 +1013,6 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
ProcessStatus(shared, "Pause/ContinueBackgroundWork", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_disable_file_deletions_one_in)) {
|
||||
Status status = TestDisableFileDeletions(thread);
|
||||
ProcessStatus(shared, "TestDisableFileDeletions", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_disable_manual_compaction_one_in)) {
|
||||
Status status = TestDisableManualCompaction(thread);
|
||||
ProcessStatus(shared, "TestDisableManualCompaction", status);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_verify_checksum_one_in)) {
|
||||
ThreadStatusUtil::SetEnableTracking(FLAGS_enable_thread_tracking);
|
||||
ThreadStatusUtil::SetThreadOperation(
|
||||
@@ -1078,11 +1035,6 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
TestGetProperty(thread);
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_get_properties_of_all_tables_one_in)) {
|
||||
Status status = TestGetPropertiesOfAllTables();
|
||||
ProcessStatus(shared, "TestGetPropertiesOfAllTables", status);
|
||||
}
|
||||
|
||||
std::vector<int64_t> rand_keys = GenerateKeys(rand_key);
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_ingest_external_file_one_in)) {
|
||||
@@ -1497,34 +1449,13 @@ Status StressTest::TestIterate(ThreadState* thread,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Test the return status of GetLiveFiles()
|
||||
// Test the return status of GetLiveFiles.
|
||||
Status StressTest::VerifyGetLiveFiles() const {
|
||||
std::vector<std::string> live_file;
|
||||
uint64_t manifest_size = 0;
|
||||
return db_->GetLiveFiles(live_file, &manifest_size);
|
||||
}
|
||||
|
||||
// Test VerifyGetLiveFilesMetaData()
|
||||
Status StressTest::VerifyGetLiveFilesMetaData() const {
|
||||
std::vector<LiveFileMetaData> live_file_metadata;
|
||||
db_->GetLiveFilesMetaData(&live_file_metadata);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Test the return status of GetLiveFilesStorageInfo()
|
||||
Status StressTest::VerifyGetLiveFilesStorageInfo() const {
|
||||
std::vector<LiveFileStorageInfo> live_file_storage_info;
|
||||
return db_->GetLiveFilesStorageInfo(LiveFilesStorageInfoOptions(),
|
||||
&live_file_storage_info);
|
||||
}
|
||||
|
||||
// Test GetAllColumnFamilyMetaData()
|
||||
Status StressTest::VerifyGetAllColumnFamilyMetaData() const {
|
||||
std::vector<ColumnFamilyMetaData> all_cf_metadata;
|
||||
db_->GetAllColumnFamilyMetaData(&all_cf_metadata);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Test the return status of GetSortedWalFiles.
|
||||
Status StressTest::VerifyGetSortedWalFiles() const {
|
||||
VectorLogPtr log_ptr;
|
||||
@@ -1787,7 +1718,6 @@ Status StressTest::TestBackupRestore(
|
||||
FLAGS_backup_max_size * 1000000 /* rate_bytes_per_sec */,
|
||||
1 /* refill_period_us */));
|
||||
}
|
||||
backup_opts.current_temperatures_override_manifest = thread->rand.OneIn(2);
|
||||
std::ostringstream backup_opt_oss;
|
||||
backup_opt_oss << "share_table_files: " << backup_opts.share_table_files
|
||||
<< ", share_files_with_checksum: "
|
||||
@@ -1800,9 +1730,7 @@ Status StressTest::TestBackupRestore(
|
||||
<< ", backup_rate_limiter: "
|
||||
<< backup_opts.backup_rate_limiter.get()
|
||||
<< ", restore_rate_limiter: "
|
||||
<< backup_opts.restore_rate_limiter.get()
|
||||
<< ", current_temperatures_override_manifest: "
|
||||
<< backup_opts.current_temperatures_override_manifest;
|
||||
<< backup_opts.restore_rate_limiter.get();
|
||||
|
||||
std::ostringstream create_backup_opt_oss;
|
||||
std::ostringstream restore_opts_oss;
|
||||
@@ -2353,11 +2281,6 @@ void StressTest::TestGetProperty(ThreadState* thread) const {
|
||||
}
|
||||
}
|
||||
|
||||
Status StressTest::TestGetPropertiesOfAllTables() const {
|
||||
TablePropertiesCollection props;
|
||||
return db_->GetPropertiesOfAllTables(&props);
|
||||
}
|
||||
|
||||
void StressTest::TestCompactFiles(ThreadState* thread,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyMetaData cf_meta_data;
|
||||
@@ -2455,28 +2378,6 @@ Status StressTest::TestPauseBackground(ThreadState* thread) {
|
||||
return db_->ContinueBackgroundWork();
|
||||
}
|
||||
|
||||
Status StressTest::TestDisableFileDeletions(ThreadState* thread) {
|
||||
Status status = db_->DisableFileDeletions();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
// Similar to TestPauseBackground()
|
||||
int pwr2_micros =
|
||||
std::min(thread->rand.Uniform(25), thread->rand.Uniform(25));
|
||||
clock_->SleepForMicroseconds(1 << pwr2_micros);
|
||||
return db_->EnableFileDeletions();
|
||||
}
|
||||
|
||||
Status StressTest::TestDisableManualCompaction(ThreadState* thread) {
|
||||
db_->DisableManualCompaction();
|
||||
// Similar to TestPauseBackground()
|
||||
int pwr2_micros =
|
||||
std::min(thread->rand.Uniform(25), thread->rand.Uniform(25));
|
||||
clock_->SleepForMicroseconds(1 << pwr2_micros);
|
||||
db_->EnableManualCompaction();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void StressTest::TestAcquireSnapshot(ThreadState* thread,
|
||||
int rand_column_family,
|
||||
const std::string& keystr, uint64_t i) {
|
||||
@@ -2636,8 +2537,7 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
bool non_ok_status_allowed =
|
||||
status.IsManualCompactionPaused() ||
|
||||
(status.getState() && std::strstr(status.getState(), "injected")) ||
|
||||
status.IsAborted() || status.IsInvalidArgument() ||
|
||||
status.IsNotSupported();
|
||||
status.IsInvalidArgument() || status.IsNotSupported();
|
||||
fprintf(non_ok_status_allowed ? stdout : stderr,
|
||||
"Unable to perform CompactRange(): %s under specified "
|
||||
"CompactRangeOptions: %s (Empty string or "
|
||||
@@ -2854,8 +2754,6 @@ void StressTest::PrintEnv() const {
|
||||
#endif
|
||||
fprintf(stdout, "Periodic Compaction Secs : %" PRIu64 "\n",
|
||||
FLAGS_periodic_compaction_seconds);
|
||||
fprintf(stdout, "Daily Offpeak UTC : %s\n",
|
||||
FLAGS_daily_offpeak_time_utc.c_str());
|
||||
fprintf(stdout, "Compaction TTL : %" PRIu64 "\n",
|
||||
FLAGS_compaction_ttl);
|
||||
const char* compaction_pri = "";
|
||||
@@ -3516,7 +3414,6 @@ void InitializeOptionsFromFlags(
|
||||
block_based_options.index_shortening =
|
||||
static_cast<BlockBasedTableOptions::IndexShorteningMode>(
|
||||
FLAGS_index_shortening);
|
||||
block_based_options.block_align = FLAGS_block_align;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
options.db_write_buffer_size = FLAGS_db_write_buffer_size;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
@@ -3549,10 +3446,6 @@ void InitializeOptionsFromFlags(
|
||||
options.num_levels = FLAGS_num_levels;
|
||||
if (FLAGS_prefix_size >= 0) {
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(FLAGS_prefix_size));
|
||||
if (FLAGS_enable_memtable_insert_with_hint_prefix_extractor) {
|
||||
options.memtable_insert_with_hint_prefix_extractor =
|
||||
options.prefix_extractor;
|
||||
}
|
||||
}
|
||||
options.max_open_files = FLAGS_open_files;
|
||||
options.statistics = dbstats;
|
||||
@@ -3604,7 +3497,6 @@ void InitializeOptionsFromFlags(
|
||||
options.experimental_mempurge_threshold =
|
||||
FLAGS_experimental_mempurge_threshold;
|
||||
options.periodic_compaction_seconds = FLAGS_periodic_compaction_seconds;
|
||||
options.daily_offpeak_time_utc = FLAGS_daily_offpeak_time_utc;
|
||||
options.stats_dump_period_sec =
|
||||
static_cast<unsigned int>(FLAGS_stats_dump_period_sec);
|
||||
options.ttl = FLAGS_compaction_ttl;
|
||||
@@ -3680,13 +3572,9 @@ void InitializeOptionsFromFlags(
|
||||
options.wal_compression =
|
||||
StringToCompressionType(FLAGS_wal_compression.c_str());
|
||||
|
||||
options.last_level_temperature =
|
||||
StringToTemperature(FLAGS_last_level_temperature.c_str());
|
||||
options.default_write_temperature =
|
||||
StringToTemperature(FLAGS_default_write_temperature.c_str());
|
||||
options.default_temperature =
|
||||
StringToTemperature(FLAGS_default_temperature.c_str());
|
||||
|
||||
if (FLAGS_enable_tiered_storage) {
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
}
|
||||
options.preclude_last_level_data_seconds =
|
||||
FLAGS_preclude_last_level_data_seconds;
|
||||
options.preserve_internal_time_seconds = FLAGS_preserve_internal_time_seconds;
|
||||
@@ -3763,13 +3651,6 @@ void InitializeOptionsFromFlags(
|
||||
FLAGS_hard_pending_compaction_bytes_limit;
|
||||
options.max_sequential_skip_in_iterations =
|
||||
FLAGS_max_sequential_skip_in_iterations;
|
||||
if (FLAGS_enable_sst_partitioner_factory) {
|
||||
options.sst_partitioner_factory = std::shared_ptr<SstPartitionerFactory>(
|
||||
NewSstPartitionerFixedPrefixFactory(1));
|
||||
}
|
||||
options.lowest_used_cache_tier =
|
||||
static_cast<CacheTier>(FLAGS_lowest_used_cache_tier);
|
||||
options.inplace_update_support = FLAGS_inplace_update_support;
|
||||
}
|
||||
|
||||
void InitializeOptionsGeneral(
|
||||
|
||||
@@ -206,24 +206,14 @@ class StressTest {
|
||||
|
||||
Status TestPauseBackground(ThreadState* thread);
|
||||
|
||||
Status TestDisableFileDeletions(ThreadState* thread);
|
||||
|
||||
Status TestDisableManualCompaction(ThreadState* thread);
|
||||
|
||||
void TestAcquireSnapshot(ThreadState* thread, int rand_column_family,
|
||||
const std::string& keystr, uint64_t i);
|
||||
|
||||
Status MaybeReleaseSnapshots(ThreadState* thread, uint64_t i);
|
||||
|
||||
Status VerifyGetLiveFiles() const;
|
||||
Status VerifyGetLiveFilesMetaData() const;
|
||||
Status VerifyGetLiveFilesStorageInfo() const;
|
||||
Status VerifyGetAllColumnFamilyMetaData() const;
|
||||
|
||||
Status VerifyGetSortedWalFiles() const;
|
||||
Status VerifyGetCurrentWalFile() const;
|
||||
void TestGetProperty(ThreadState* thread) const;
|
||||
Status TestGetPropertiesOfAllTables() const;
|
||||
|
||||
virtual Status TestApproximateSize(
|
||||
ThreadState* thread, uint64_t iteration,
|
||||
|
||||
@@ -249,17 +249,14 @@ int db_stress_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if ((FLAGS_enable_compaction_filter || FLAGS_inplace_update_support) &&
|
||||
if (FLAGS_enable_compaction_filter &&
|
||||
(FLAGS_acquire_snapshot_one_in > 0 || FLAGS_compact_range_one_in > 0 ||
|
||||
FLAGS_iterpercent > 0 || FLAGS_test_batches_snapshots ||
|
||||
FLAGS_test_cf_consistency || FLAGS_check_multiget_consistency ||
|
||||
FLAGS_check_multiget_entity_consistency)) {
|
||||
FLAGS_test_cf_consistency)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Error: acquire_snapshot_one_in, compact_range_one_in, iterpercent, "
|
||||
"test_batches_snapshots, test_cf_consistency, "
|
||||
"check_multiget_consistency, check_multiget_entity_consistency must "
|
||||
"all be 0 when using compaction filter or inplace update support\n");
|
||||
"test_batches_snapshots must all be 0 when using compaction filter\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_test_multi_ops_txns) {
|
||||
|
||||
@@ -437,28 +437,6 @@ class ExpectedStateTraceRecordHandler : public TraceRecord::Handler,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status TimedPutCF(uint32_t column_family_id, const Slice& key_with_ts,
|
||||
const Slice& value, uint64_t write_unix_time) override {
|
||||
Slice key =
|
||||
StripTimestampFromUserKey(key_with_ts, FLAGS_user_timestamp_size);
|
||||
uint64_t key_id;
|
||||
if (!GetIntVal(key.ToString(), &key_id)) {
|
||||
return Status::Corruption("unable to parse key", key.ToString());
|
||||
}
|
||||
uint32_t value_base = GetValueBase(value);
|
||||
|
||||
bool should_buffer_write = !(buffered_writes_ == nullptr);
|
||||
if (should_buffer_write) {
|
||||
return WriteBatchInternal::TimedPut(buffered_writes_.get(),
|
||||
column_family_id, key, value,
|
||||
write_unix_time);
|
||||
}
|
||||
|
||||
state_->SyncPut(column_family_id, static_cast<int64_t>(key_id), value_base);
|
||||
++num_write_ops_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PutEntityCF(uint32_t column_family_id, const Slice& key_with_ts,
|
||||
const Slice& entity) override {
|
||||
Slice key =
|
||||
|
||||
@@ -580,8 +580,13 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
int column_family = rand_column_families[0];
|
||||
ColumnFamilyHandle* cfh = column_families_[column_family];
|
||||
int error_count = 0;
|
||||
|
||||
bool do_consistency_check = FLAGS_check_multiget_consistency;
|
||||
// Do a consistency check between Get and MultiGet. Don't do it too
|
||||
// often as it will slow db_stress down
|
||||
//
|
||||
// CompactionFilter can make snapshot non-repeatable by removing keys
|
||||
// protected by snapshot
|
||||
bool do_consistency_check =
|
||||
!FLAGS_enable_compaction_filter && thread->rand.OneIn(4);
|
||||
|
||||
ReadOptions readoptionscopy = read_opts;
|
||||
|
||||
@@ -1070,8 +1075,10 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
fault_fs_guard->DisableErrorInjection();
|
||||
}
|
||||
|
||||
const bool check_get_entity =
|
||||
!error_count && FLAGS_check_multiget_entity_consistency;
|
||||
// CompactionFilter can make snapshot non-repeatable by removing keys
|
||||
// protected by snapshot
|
||||
const bool check_get_entity = !FLAGS_enable_compaction_filter &&
|
||||
!error_count && thread->rand.OneIn(4);
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
const Status& s = statuses[i];
|
||||
@@ -1304,15 +1311,6 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
s = db_->PutEntity(write_opts, cfh, k,
|
||||
GenerateWideColumns(value_base, v));
|
||||
} else if (FLAGS_use_timed_put_one_in > 0 &&
|
||||
((value_base + kLargePrimeForCommonFactorSkew) %
|
||||
FLAGS_use_timed_put_one_in) == 0) {
|
||||
WriteBatch wb;
|
||||
uint64_t write_unix_time = GetWriteUnixTime(thread);
|
||||
s = wb.TimedPut(cfh, k, v, write_unix_time);
|
||||
if (s.ok()) {
|
||||
s = db_->Write(write_opts, &wb);
|
||||
}
|
||||
} else if (FLAGS_use_merge) {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
|
||||
Vendored
-9
@@ -825,15 +825,6 @@ WritableFile::~WritableFile() = default;
|
||||
|
||||
MemoryMappedFileBuffer::~MemoryMappedFileBuffer() = default;
|
||||
|
||||
// This const variable can be used in public headers without introducing the
|
||||
// possibility of ODR violations due to varying macro definitions.
|
||||
const InfoLogLevel Logger::kDefaultLogLevel =
|
||||
#ifdef NDEBUG
|
||||
INFO_LEVEL;
|
||||
#else
|
||||
DEBUG_LEVEL;
|
||||
#endif // NDEBUG
|
||||
|
||||
Logger::~Logger() = default;
|
||||
|
||||
Status Logger::Close() {
|
||||
|
||||
Vendored
-331
@@ -1,331 +0,0 @@
|
||||
// Copyright (c) 2024-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "env/fs_on_demand.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "file/filename.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// Check if the input path is under orig (typically the local directory), and if
|
||||
// so, change it to the equivalent path under replace (typically the remote
|
||||
// directory). For example, if orig is "/data/follower", replace is
|
||||
// "/data/leader", and the given path is "/data/follower/000010.sst", on return
|
||||
// the path would be changed to
|
||||
// "/data/leader/000010.sst".
|
||||
// Return value is true if the path was modified, false otherwise
|
||||
bool OnDemandFileSystem::CheckPathAndAdjust(const std::string& orig,
|
||||
const std::string& replace,
|
||||
std::string& path) {
|
||||
size_t pos = path.find(orig);
|
||||
if (pos > 0) {
|
||||
return false;
|
||||
}
|
||||
path.replace(pos, orig.length(), replace);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnDemandFileSystem::LookupFileType(const std::string& name,
|
||||
FileType* type) {
|
||||
std::size_t found = name.find_last_of('/');
|
||||
std::string file_name = name.substr(found);
|
||||
uint64_t number = 0;
|
||||
return ParseFileName(file_name, &number, type);
|
||||
}
|
||||
|
||||
// RocksDB opens non-SST files for reading in sequential file mode. This
|
||||
// includes CURRENT, OPTIONS, MANIFEST etc. For these files, we open them
|
||||
// in place in the source directory. For files that are appendable or
|
||||
// can be renamed, which is MANIFEST and CURRENT files, we wrap the
|
||||
// underlying FSSequentialFile with another class that checks when EOF
|
||||
// has been reached and re-opens the file to see the latest data. On some
|
||||
// distributed file systems, this is necessary.
|
||||
IOStatus OnDemandFileSystem::NewSequentialFile(
|
||||
const std::string& fname, const FileOptions& file_opts,
|
||||
std::unique_ptr<FSSequentialFile>* result, IODebugContext* dbg) {
|
||||
FileType type;
|
||||
static std::unordered_set<FileType> valid_types(
|
||||
{kWalFile, kDescriptorFile, kCurrentFile, kIdentityFile, kOptionsFile});
|
||||
if (!LookupFileType(fname, &type) ||
|
||||
(valid_types.find(type) == valid_types.end())) {
|
||||
return IOStatus::NotSupported();
|
||||
}
|
||||
|
||||
IOStatus s;
|
||||
std::string rname = fname;
|
||||
if (CheckPathAndAdjust(local_path_, remote_path_, rname)) {
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rname);
|
||||
|
||||
std::unique_ptr<FSSequentialFile> inner_file;
|
||||
s = target()->NewSequentialFile(rname, file_opts, &inner_file, dbg);
|
||||
if (s.ok() && type == kDescriptorFile) {
|
||||
result->reset(new OnDemandSequentialFile(std::move(inner_file), this,
|
||||
file_opts, rname));
|
||||
} else {
|
||||
*result = std::move(inner_file);
|
||||
}
|
||||
} else {
|
||||
s = target()->NewSequentialFile(fname, file_opts, result, dbg);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// This is only supported for SST files. If the file is present locally,
|
||||
// i.e in the destination dir, we just open it and return. If its in the
|
||||
// remote, i.e source dir, we link it locally and open the link.
|
||||
// TODO: Add support for blob files belonging to the new BlobDB
|
||||
IOStatus OnDemandFileSystem::NewRandomAccessFile(
|
||||
const std::string& fname, const FileOptions& file_opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result, IODebugContext* dbg) {
|
||||
FileType type;
|
||||
if (!LookupFileType(fname, &type) || type != kTableFile) {
|
||||
return IOStatus::NotSupported();
|
||||
}
|
||||
|
||||
IOStatus s = target()->FileExists(fname, file_opts.io_options, nullptr);
|
||||
if (s.IsNotFound() || s.IsPathNotFound()) {
|
||||
std::string rname = fname;
|
||||
if (CheckPathAndAdjust(local_path_, remote_path_, rname)) {
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rname);
|
||||
|
||||
s = target()->LinkFile(rname, fname, IOOptions(), nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.ok() ? target()->NewRandomAccessFile(fname, file_opts, result, dbg)
|
||||
: s;
|
||||
}
|
||||
|
||||
// We don't expect to create any writable file other than info LOG files.
|
||||
IOStatus OnDemandFileSystem::NewWritableFile(
|
||||
const std::string& fname, const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
|
||||
FileType type;
|
||||
if (!LookupFileType(fname, &type) || type != kInfoLogFile) {
|
||||
return IOStatus::NotSupported();
|
||||
}
|
||||
|
||||
std::string rname = fname;
|
||||
if (CheckPathAndAdjust(local_path_, remote_path_, rname)) {
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rname);
|
||||
|
||||
IOStatus s = target()->FileExists(rname, file_opts.io_options, dbg);
|
||||
if (s.ok()) {
|
||||
return IOStatus::InvalidArgument(
|
||||
"Writing to a file present in the remote directory not supoprted");
|
||||
}
|
||||
}
|
||||
|
||||
return target()->NewWritableFile(fname, file_opts, result, dbg);
|
||||
}
|
||||
|
||||
// Currently not supported, as there's no need for RocksDB to create a
|
||||
// directory object for a DB in read-only mode.
|
||||
IOStatus OnDemandFileSystem::NewDirectory(
|
||||
const std::string& /*name*/, const IOOptions& /*io_opts*/,
|
||||
std::unique_ptr<FSDirectory>* /*result*/, IODebugContext* /*dbg*/) {
|
||||
return IOStatus::NotSupported();
|
||||
}
|
||||
|
||||
// Check if the given file exists, either locally or remote. If the file is an
|
||||
// SST file, then link it locally. We assume if the file existence is being
|
||||
// checked, its for verification purposes, for example while replaying the
|
||||
// MANIFEST. The file will be opened for reading some time in the future.
|
||||
IOStatus OnDemandFileSystem::FileExists(const std::string& fname,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
IOStatus s = target()->FileExists(fname, options, dbg);
|
||||
if (!s.IsNotFound() && !s.IsPathNotFound()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string rname = fname;
|
||||
if (CheckPathAndAdjust(local_path_, remote_path_, rname)) {
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rname);
|
||||
|
||||
FileType type;
|
||||
if (LookupFileType(fname, &type) && type == kTableFile) {
|
||||
s = target()->LinkFile(rname, fname, options, dbg);
|
||||
} else {
|
||||
s = target()->FileExists(rname, options, dbg);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Doa listing of both the local and remote directories and merge the two.
|
||||
IOStatus OnDemandFileSystem::GetChildren(const std::string& dir,
|
||||
const IOOptions& options,
|
||||
std::vector<std::string>* result,
|
||||
IODebugContext* dbg) {
|
||||
std::string rdir = dir;
|
||||
IOStatus s = target()->GetChildren(dir, options, result, dbg);
|
||||
if (!s.ok() || !CheckPathAndAdjust(local_path_, remote_path_, rdir)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<std::string> rchildren;
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rdir);
|
||||
s = target()->GetChildren(rdir, options, &rchildren, dbg);
|
||||
if (s.ok()) {
|
||||
std::for_each(rchildren.begin(), rchildren.end(), [&](std::string& name) {
|
||||
// Adjust name
|
||||
(void)CheckPathAndAdjust(remote_path_, local_path_, name);
|
||||
});
|
||||
std::sort(result->begin(), result->end());
|
||||
std::sort(rchildren.begin(), rchildren.end());
|
||||
|
||||
std::vector<std::string> output;
|
||||
output.reserve(result->size() + rchildren.size());
|
||||
std::set_union(result->begin(), result->end(), rchildren.begin(),
|
||||
rchildren.end(), std::back_inserter(output));
|
||||
*result = std::move(output);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Doa listing of both the local and remote directories and merge the two.
|
||||
IOStatus OnDemandFileSystem::GetChildrenFileAttributes(
|
||||
const std::string& dir, const IOOptions& options,
|
||||
std::vector<FileAttributes>* result, IODebugContext* dbg) {
|
||||
std::string rdir = dir;
|
||||
IOStatus s = target()->GetChildrenFileAttributes(dir, options, result, dbg);
|
||||
if (!s.ok() || !CheckPathAndAdjust(local_path_, remote_path_, rdir)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<FileAttributes> rchildren;
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rdir);
|
||||
s = target()->GetChildrenFileAttributes(rdir, options, &rchildren, dbg);
|
||||
if (s.ok()) {
|
||||
struct FileAttributeSorter {
|
||||
bool operator()(const FileAttributes& lhs, const FileAttributes& rhs) {
|
||||
return lhs.name < rhs.name;
|
||||
}
|
||||
} file_attr_sorter;
|
||||
|
||||
std::for_each(
|
||||
rchildren.begin(), rchildren.end(), [&](FileAttributes& file) {
|
||||
// Adjust name
|
||||
(void)CheckPathAndAdjust(remote_path_, local_path_, file.name);
|
||||
});
|
||||
std::sort(result->begin(), result->end(), file_attr_sorter);
|
||||
std::sort(rchildren.begin(), rchildren.end(), file_attr_sorter);
|
||||
|
||||
std::vector<FileAttributes> output;
|
||||
output.reserve(result->size() + rchildren.size());
|
||||
std::set_union(rchildren.begin(), rchildren.end(), result->begin(),
|
||||
result->end(), std::back_inserter(output), file_attr_sorter);
|
||||
*result = std::move(output);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus OnDemandFileSystem::GetFileSize(const std::string& fname,
|
||||
const IOOptions& options,
|
||||
uint64_t* file_size,
|
||||
IODebugContext* dbg) {
|
||||
uint64_t local_size = 0;
|
||||
IOStatus s = target()->GetFileSize(fname, options, &local_size, dbg);
|
||||
if (!s.ok() && !s.IsNotFound() && !s.IsPathNotFound()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (s.IsNotFound() || s.IsPathNotFound()) {
|
||||
std::string rname = fname;
|
||||
if (CheckPathAndAdjust(local_path_, remote_path_, rname)) {
|
||||
// First clear any local directory cache as it may be out of date
|
||||
target()->DiscardCacheForDirectory(rname);
|
||||
|
||||
FileType type;
|
||||
if (LookupFileType(fname, &type) && type == kTableFile) {
|
||||
s = target()->LinkFile(rname, fname, options, dbg);
|
||||
if (s.ok()) {
|
||||
s = target()->GetFileSize(fname, options, &local_size, dbg);
|
||||
}
|
||||
} else {
|
||||
s = target()->GetFileSize(rname, options, &local_size, dbg);
|
||||
}
|
||||
}
|
||||
}
|
||||
*file_size = local_size;
|
||||
return s;
|
||||
}
|
||||
|
||||
// An implementation of Read that tracks whether we've reached EOF. If so,
|
||||
// re-open the file to try to read past the previous EOF offset. After
|
||||
// re-opening, positing it back to the last read offset.
|
||||
IOStatus OnDemandSequentialFile::Read(size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) {
|
||||
IOStatus s;
|
||||
if (eof_) {
|
||||
// Reopen the file. With some distributed file systems, this is required
|
||||
// in order to get the new size
|
||||
file_.reset();
|
||||
s = fs_->NewSequentialFile(path_, file_opts_, &file_, dbg);
|
||||
if (!s.ok()) {
|
||||
return IOStatus::IOError("While opening file after relinking, got error ",
|
||||
s.ToString());
|
||||
}
|
||||
s = file_->Skip(offset_);
|
||||
if (!s.ok()) {
|
||||
return IOStatus::IOError(
|
||||
"While seeking to offset" + std::to_string(offset_) + "got error",
|
||||
s.ToString());
|
||||
}
|
||||
eof_ = false;
|
||||
}
|
||||
|
||||
s = file_->Read(n, options, result, scratch, dbg);
|
||||
if (s.ok()) {
|
||||
offset_ += result->size();
|
||||
if (result->size() < n) {
|
||||
// We reached EOF. Mark it so we know to relink next time
|
||||
eof_ = true;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus OnDemandSequentialFile::Skip(uint64_t n) {
|
||||
IOStatus s = file_->Skip(n);
|
||||
if (s.ok()) {
|
||||
offset_ += n;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool OnDemandSequentialFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
size_t OnDemandSequentialFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
Temperature OnDemandSequentialFile::GetTemperature() const {
|
||||
return file_->GetTemperature();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSystem> NewOnDemandFileSystem(
|
||||
const std::shared_ptr<FileSystem>& fs, std::string src_path,
|
||||
std::string dest_path) {
|
||||
return std::make_shared<OnDemandFileSystem>(fs, src_path, dest_path);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-139
@@ -1,139 +0,0 @@
|
||||
// Copyright (c) 2024-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/file_system.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// A FileSystem that links files to a local (destination) directory from a
|
||||
// corresponding remote (source) directory on demand. The decision to link
|
||||
// depends on the file type, with appendable or rename-able files, such as,
|
||||
// descriptors, logs, CURRENT, being read in place in the remote directory,
|
||||
// and SST files being linked. In the future, files read in place may be
|
||||
// mirrored to the local directory, so the local dir has a complete database
|
||||
// for troubleshooting purposes.
|
||||
|
||||
class OnDemandFileSystem : public FileSystemWrapper {
|
||||
public:
|
||||
OnDemandFileSystem(const std::shared_ptr<FileSystem>& target,
|
||||
const std::string& remote_path,
|
||||
const std::string& local_path)
|
||||
: FileSystemWrapper(target),
|
||||
remote_path_(remote_path),
|
||||
local_path_(local_path) {}
|
||||
|
||||
const char* Name() const override { return "OnDemandFileSystem"; }
|
||||
|
||||
IOStatus NewSequentialFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSSequentialFile>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus NewWritableFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus ReuseWritableFile(const std::string& /*fname*/,
|
||||
const std::string& /*old_fname*/,
|
||||
const FileOptions& /*fopts*/,
|
||||
std::unique_ptr<FSWritableFile>* /*result*/,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
return IOStatus::NotSupported("ReuseWritableFile");
|
||||
}
|
||||
|
||||
IOStatus NewDirectory(const std::string& name, const IOOptions& io_opts,
|
||||
std::unique_ptr<FSDirectory>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus FileExists(const std::string& fname, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus GetChildren(const std::string& dir, const IOOptions& options,
|
||||
std::vector<std::string>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus GetChildrenFileAttributes(const std::string& dir,
|
||||
const IOOptions& options,
|
||||
std::vector<FileAttributes>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
|
||||
uint64_t* file_size, IODebugContext* dbg) override;
|
||||
|
||||
private:
|
||||
bool CheckPathAndAdjust(const std::string& orig, const std::string& replace,
|
||||
std::string& path);
|
||||
bool LookupFileType(const std::string& name, FileType* type);
|
||||
|
||||
const std::string remote_path_;
|
||||
const std::string local_path_;
|
||||
};
|
||||
|
||||
// A wrapper class around an FSSequentialFile object. Its mainly
|
||||
// intended to be used for appendable files like MANIFEST and logs.
|
||||
// Beneath the covers, it tracks when EOF is reached, and reopens
|
||||
// the file in order to read the latest appended data. This is
|
||||
// necessary on some distributed file systems as they may have
|
||||
// stale metadata about the file.
|
||||
// TODO: Mirror the data read to a local file for troubleshooting
|
||||
// purposes, as well as recovery in case the source dir is
|
||||
// deleted.
|
||||
class OnDemandSequentialFile : public FSSequentialFile {
|
||||
public:
|
||||
OnDemandSequentialFile(std::unique_ptr<FSSequentialFile>&& file,
|
||||
OnDemandFileSystem* fs, const FileOptions& file_opts,
|
||||
const std::string& path)
|
||||
: file_(std::move(file)),
|
||||
fs_(fs),
|
||||
file_opts_(file_opts),
|
||||
path_(path),
|
||||
eof_(false),
|
||||
offset_(0) {}
|
||||
|
||||
virtual ~OnDemandSequentialFile() {}
|
||||
|
||||
IOStatus Read(size_t n, const IOOptions& options, Slice* result,
|
||||
char* scratch, IODebugContext* dbg) override;
|
||||
|
||||
IOStatus Skip(uint64_t n) override;
|
||||
|
||||
bool use_direct_io() const override;
|
||||
|
||||
size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
IOStatus InvalidateCache(size_t /*offset*/, size_t /*length*/) override {
|
||||
return IOStatus::NotSupported("InvalidateCache not supported.");
|
||||
}
|
||||
|
||||
IOStatus PositionedRead(uint64_t /*offset*/, size_t /*n*/,
|
||||
const IOOptions& /*options*/, Slice* /*result*/,
|
||||
char* /*scratch*/, IODebugContext* /*dbg*/) override {
|
||||
return IOStatus::NotSupported("PositionedRead");
|
||||
}
|
||||
|
||||
Temperature GetTemperature() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FSSequentialFile> file_;
|
||||
OnDemandFileSystem* fs_;
|
||||
const FileOptions file_opts_;
|
||||
const std::string path_;
|
||||
bool eof_;
|
||||
uint64_t offset_;
|
||||
};
|
||||
|
||||
std::shared_ptr<FileSystem> NewOnDemandFileSystem(
|
||||
const std::shared_ptr<FileSystem>& fs, std::string remote_path,
|
||||
std::string local_path);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -60,10 +60,10 @@ DeleteScheduler::~DeleteScheduler() {
|
||||
Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
const std::string& dir_to_sync,
|
||||
const bool force_bg) {
|
||||
uint64_t total_size = sst_file_manager_->GetTotalSize();
|
||||
if (rate_bytes_per_sec_.load() <= 0 ||
|
||||
(!force_bg &&
|
||||
total_trash_size_.load() > total_size * max_trash_db_ratio_.load())) {
|
||||
total_trash_size_.load() >
|
||||
sst_file_manager_->GetTotalSize() * max_trash_db_ratio_.load())) {
|
||||
// Rate limiting is disabled or trash size makes up more than
|
||||
// max_trash_db_ratio_ (default 25%) of the total DB size
|
||||
TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
|
||||
@@ -72,11 +72,9 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
s = sst_file_manager_->OnDeleteFile(file_path);
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Deleted file %s immediately, rate_bytes_per_sec %" PRIi64
|
||||
", total_trash_size %" PRIu64 ", total_size %" PRIi64
|
||||
", max_trash_db_ratio %lf",
|
||||
", total_trash_size %" PRIu64 " max_trash_db_ratio %lf",
|
||||
file_path.c_str(), rate_bytes_per_sec_.load(),
|
||||
total_trash_size_.load(), total_size,
|
||||
max_trash_db_ratio_.load());
|
||||
total_trash_size_.load(), max_trash_db_ratio_.load());
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
|
||||
}
|
||||
|
||||
@@ -46,11 +46,6 @@ inline IOStatus CreateFile(const std::shared_ptr<FileSystem>& fs,
|
||||
return CreateFile(fs.get(), destination, contents, use_fsync);
|
||||
}
|
||||
|
||||
// Delete a DB file, if this file is a SST file or Blob file and SstFileManager
|
||||
// is used, it should have already been tracked by SstFileManager via its
|
||||
// `OnFileAdd` API before passing to this API to be deleted, to ensure
|
||||
// SstFileManager and its DeleteScheduler are tracking DB size and trash size
|
||||
// properly.
|
||||
Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
const std::string& fname, const std::string& path_to_sync,
|
||||
const bool force_bg, const bool force_fg);
|
||||
|
||||
@@ -41,7 +41,6 @@ IOStatus SequentialFileReader::Read(size_t n, Slice* result, char* scratch,
|
||||
IOStatus io_s;
|
||||
IOOptions io_opts;
|
||||
io_opts.rate_limiter_priority = rate_limiter_priority;
|
||||
io_opts.verify_and_reconstruct_read = verify_and_reconstruct_read_;
|
||||
if (use_direct_io()) {
|
||||
//
|
||||
// |-offset_advance-|---bytes returned--|
|
||||
|
||||
@@ -56,7 +56,6 @@ class SequentialFileReader {
|
||||
std::atomic<size_t> offset_{0}; // read offset
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_{};
|
||||
RateLimiter* rate_limiter_;
|
||||
bool verify_and_reconstruct_read_;
|
||||
|
||||
public:
|
||||
explicit SequentialFileReader(
|
||||
@@ -64,13 +63,11 @@ class SequentialFileReader {
|
||||
const std::shared_ptr<IOTracer>& io_tracer = nullptr,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners = {},
|
||||
RateLimiter* rate_limiter =
|
||||
nullptr, // TODO: migrate call sites to provide rate limiter
|
||||
bool verify_and_reconstruct_read = false)
|
||||
nullptr) // TODO: migrate call sites to provide rate limiter
|
||||
: file_name_(_file_name),
|
||||
file_(std::move(_file), io_tracer, _file_name),
|
||||
listeners_(),
|
||||
rate_limiter_(rate_limiter),
|
||||
verify_and_reconstruct_read_(verify_and_reconstruct_read) {
|
||||
rate_limiter_(rate_limiter) {
|
||||
AddFileIOListeners(listeners);
|
||||
}
|
||||
|
||||
@@ -80,14 +77,12 @@ class SequentialFileReader {
|
||||
const std::shared_ptr<IOTracer>& io_tracer = nullptr,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners = {},
|
||||
RateLimiter* rate_limiter =
|
||||
nullptr, // TODO: migrate call sites to provide rate limiter
|
||||
bool verify_and_reconstruct_read = false)
|
||||
nullptr) // TODO: migrate call sites to provide rate limiter
|
||||
: file_name_(_file_name),
|
||||
file_(NewReadaheadSequentialFile(std::move(_file), _readahead_size),
|
||||
io_tracer, _file_name),
|
||||
listeners_(),
|
||||
rate_limiter_(rate_limiter),
|
||||
verify_and_reconstruct_read_(verify_and_reconstruct_read) {
|
||||
rate_limiter_(rate_limiter) {
|
||||
AddFileIOListeners(listeners);
|
||||
}
|
||||
static IOStatus Create(const std::shared_ptr<FileSystem>& fs,
|
||||
|
||||
@@ -30,10 +30,6 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
double max_trash_db_ratio,
|
||||
uint64_t bytes_max_delete_chunk);
|
||||
|
||||
// No copy
|
||||
SstFileManagerImpl(const SstFileManagerImpl& sfm) = delete;
|
||||
SstFileManagerImpl& operator=(const SstFileManagerImpl& sfm) = delete;
|
||||
|
||||
~SstFileManagerImpl();
|
||||
|
||||
// DB will call OnAddFile whenever a new sst/blob file is added.
|
||||
|
||||
@@ -205,6 +205,7 @@ IOStatus WritableFileWriter::Pad(const IOOptions& opts,
|
||||
assert(pad_bytes < kDefaultPageSize);
|
||||
size_t left = pad_bytes;
|
||||
size_t cap = buf_.Capacity() - buf_.CurrentSize();
|
||||
size_t pad_start = buf_.CurrentSize();
|
||||
|
||||
// Assume pad_bytes is small compared to buf_ capacity. So we always
|
||||
// use buf_ rather than write directly to file in certain cases like
|
||||
@@ -213,16 +214,6 @@ IOStatus WritableFileWriter::Pad(const IOOptions& opts,
|
||||
size_t append_bytes = std::min(cap, left);
|
||||
buf_.PadWith(append_bytes, 0);
|
||||
left -= append_bytes;
|
||||
|
||||
Slice data(buf_.BufferStart() + buf_.CurrentSize() - append_bytes,
|
||||
append_bytes);
|
||||
UpdateFileChecksum(data);
|
||||
if (perform_data_verification_) {
|
||||
buffered_data_crc32c_checksum_ = crc32c::Extend(
|
||||
buffered_data_crc32c_checksum_,
|
||||
buf_.BufferStart() + buf_.CurrentSize() - append_bytes, append_bytes);
|
||||
}
|
||||
|
||||
if (left > 0) {
|
||||
IOStatus s = Flush(io_options);
|
||||
if (!s.ok()) {
|
||||
@@ -235,7 +226,11 @@ IOStatus WritableFileWriter::Pad(const IOOptions& opts,
|
||||
pending_sync_ = true;
|
||||
uint64_t cur_size = filesize_.load(std::memory_order_acquire);
|
||||
filesize_.store(cur_size + pad_bytes, std::memory_order_release);
|
||||
|
||||
if (perform_data_verification_) {
|
||||
buffered_data_crc32c_checksum_ =
|
||||
crc32c::Extend(buffered_data_crc32c_checksum_,
|
||||
buf_.BufferStart() + pad_start, pad_bytes);
|
||||
}
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
|
||||
@@ -229,18 +229,11 @@ struct AdvancedColumnFamilyOptions {
|
||||
// if it is not explicitly set by the user. Otherwise, the default is 0.
|
||||
int64_t max_write_buffer_size_to_maintain = 0;
|
||||
|
||||
// Allows thread-safe inplace updates.
|
||||
//
|
||||
// If this is true, there is no way to
|
||||
// Allows thread-safe inplace updates. If this is true, there is no way to
|
||||
// achieve point-in-time consistency using snapshot or iterator (assuming
|
||||
// concurrent updates). Hence iterator and multi-get will return results
|
||||
// which are not consistent as of any point-in-time.
|
||||
//
|
||||
// Backward iteration on memtables will not work either.
|
||||
//
|
||||
// It is intended to work or be compatible with a limited set of features:
|
||||
// (1) Non-snapshot Get()
|
||||
//
|
||||
// If inplace_callback function is not set,
|
||||
// Put(key, new_value) will update inplace the existing_value iff
|
||||
// * key exists in current memtable
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/iterator_base.h"
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class ColumnFamilyHandle;
|
||||
|
||||
// Class representing attribute group. Attribute group is a logical grouping of
|
||||
// wide-column entities by leveraging Column Families.
|
||||
// Used in Write Path
|
||||
class AttributeGroup {
|
||||
public:
|
||||
explicit AttributeGroup(ColumnFamilyHandle* column_family,
|
||||
const WideColumns& columns)
|
||||
: column_family_(column_family), columns_(columns) {}
|
||||
|
||||
ColumnFamilyHandle* column_family() const { return column_family_; }
|
||||
const WideColumns& columns() const { return columns_; }
|
||||
WideColumns& columns() { return columns_; }
|
||||
|
||||
private:
|
||||
ColumnFamilyHandle* column_family_;
|
||||
WideColumns columns_;
|
||||
};
|
||||
|
||||
inline bool operator==(const AttributeGroup& lhs, const AttributeGroup& rhs) {
|
||||
return lhs.column_family() == rhs.column_family() &&
|
||||
lhs.columns() == rhs.columns();
|
||||
}
|
||||
|
||||
// A collection of Attribute Groups.
|
||||
using AttributeGroups = std::vector<AttributeGroup>;
|
||||
|
||||
// An empty set of Attribute Groups.
|
||||
extern const AttributeGroups kNoAttributeGroups;
|
||||
|
||||
// Used in Read Path. Wide-columns returned from the query are pinnable.
|
||||
class PinnableAttributeGroup {
|
||||
public:
|
||||
explicit PinnableAttributeGroup(ColumnFamilyHandle* column_family)
|
||||
: column_family_(column_family), status_(Status::OK()) {}
|
||||
|
||||
ColumnFamilyHandle* column_family() const { return column_family_; }
|
||||
const Status& status() const { return status_; }
|
||||
const WideColumns& columns() const { return columns_.columns(); }
|
||||
|
||||
void SetStatus(const Status& status);
|
||||
void SetColumns(PinnableWideColumns&& columns);
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
ColumnFamilyHandle* column_family_;
|
||||
Status status_;
|
||||
PinnableWideColumns columns_;
|
||||
};
|
||||
|
||||
inline void PinnableAttributeGroup::SetStatus(const Status& status) {
|
||||
status_ = status;
|
||||
}
|
||||
inline void PinnableAttributeGroup::SetColumns(PinnableWideColumns&& columns) {
|
||||
columns_ = std::move(columns);
|
||||
}
|
||||
|
||||
inline void PinnableAttributeGroup::Reset() {
|
||||
SetStatus(Status::OK());
|
||||
columns_.Reset();
|
||||
}
|
||||
|
||||
// A collection of Pinnable Attribute Groups.
|
||||
using PinnableAttributeGroups = std::vector<PinnableAttributeGroup>;
|
||||
|
||||
// Used in Iterator Path. Uses pointers to the columns to avoid having to copy
|
||||
// all WideColumns objs during iteration.
|
||||
class IteratorAttributeGroup {
|
||||
public:
|
||||
explicit IteratorAttributeGroup(ColumnFamilyHandle* column_family,
|
||||
const WideColumns* columns)
|
||||
: column_family_(column_family), columns_(columns) {}
|
||||
ColumnFamilyHandle* column_family() const { return column_family_; }
|
||||
const WideColumns& columns() const { return *columns_; }
|
||||
|
||||
private:
|
||||
ColumnFamilyHandle* column_family_;
|
||||
const WideColumns* columns_;
|
||||
};
|
||||
|
||||
using IteratorAttributeGroups = std::vector<IteratorAttributeGroup>;
|
||||
|
||||
extern const IteratorAttributeGroups kNoIteratorAttributeGroups;
|
||||
|
||||
// EXPERIMENTAL
|
||||
// A cross-column-family iterator that collects and returns attribute groups for
|
||||
// each key in order provided by comparator
|
||||
class AttributeGroupIterator : public IteratorBase {
|
||||
public:
|
||||
AttributeGroupIterator() {}
|
||||
~AttributeGroupIterator() override {}
|
||||
|
||||
// No copy allowed
|
||||
AttributeGroupIterator(const AttributeGroupIterator&) = delete;
|
||||
AttributeGroupIterator& operator=(const AttributeGroupIterator&) = delete;
|
||||
|
||||
virtual const IteratorAttributeGroups& attribute_groups() const = 0;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -523,11 +523,6 @@ enum TieredAdmissionPolicy {
|
||||
// compressed secondary, and a compressed local flash (non-volatile) cache.
|
||||
// Each tier is managed as an independent queue.
|
||||
kAdmPolicyThreeQueue,
|
||||
// Allow all blocks evicted from the primary block cache into the secondary
|
||||
// cache. This may increase CPU overhead due to more blocks being admitted
|
||||
// and compressed, but may increase the compressed secondary cache hit rate
|
||||
// for some workloads
|
||||
kAdmPolicyAllowAll,
|
||||
kAdmPolicyMax,
|
||||
};
|
||||
|
||||
|
||||
@@ -120,28 +120,6 @@ class Comparator : public Customizable, public CompareInterface {
|
||||
|
||||
inline size_t timestamp_size() const { return timestamp_size_; }
|
||||
|
||||
// Return what this Comparator considers as the maximum timestamp.
|
||||
// The default implementation only works for when `timestamp_size_` is 0,
|
||||
// subclasses for which this is not the case needs to override this function.
|
||||
virtual Slice GetMaxTimestamp() const {
|
||||
if (timestamp_size_ == 0) {
|
||||
return Slice();
|
||||
}
|
||||
assert(false);
|
||||
return Slice();
|
||||
}
|
||||
|
||||
// Return what this Comparator considers as the min timestamp.
|
||||
// The default implementation only works for when `timestamp_size_` is 0,
|
||||
// subclasses for which this is not the case needs to override this function.
|
||||
virtual Slice GetMinTimestamp() const {
|
||||
if (timestamp_size_ == 0) {
|
||||
return Slice();
|
||||
}
|
||||
assert(false);
|
||||
return Slice();
|
||||
}
|
||||
|
||||
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const {
|
||||
return CompareWithoutTimestamp(a, /*a_has_ts=*/true, b, /*b_has_ts=*/true);
|
||||
}
|
||||
|
||||
+5
-47
@@ -17,7 +17,6 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/attribute_groups.h"
|
||||
#include "rocksdb/block_cache_trace_writer.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/listener.h"
|
||||
@@ -298,29 +297,6 @@ class DB {
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr);
|
||||
|
||||
// EXPERIMENTAL
|
||||
|
||||
// Open a database as a follower. The difference between this and opening
|
||||
// as secondary is that the follower database has its own directory with
|
||||
// links to the actual files, and can tolarate obsolete file deletions by
|
||||
// the leader to its own database. Another difference is the follower
|
||||
// tries to keep up with the leader by periodically tailing the leader's
|
||||
// MANIFEST, and (in the future) memtable updates, rather than relying on
|
||||
// the user to manually call TryCatchupWithPrimary().
|
||||
|
||||
// Open as a follower with the default column family
|
||||
static Status OpenAsFollower(const Options& options, const std::string& name,
|
||||
const std::string& leader_path,
|
||||
std::unique_ptr<DB>* dbptr);
|
||||
|
||||
// Open as a follower with multiple column families
|
||||
static Status OpenAsFollower(
|
||||
const DBOptions& db_options, const std::string& name,
|
||||
const std::string& leader_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, std::unique_ptr<DB>* dbptr);
|
||||
// End EXPERIMENTAL
|
||||
|
||||
// Open DB and run the compaction.
|
||||
// It's a read-only operation, the result won't be installed to the DB, it
|
||||
// will be output to the `output_directory`. The API should only be used with
|
||||
@@ -541,8 +517,6 @@ class DB {
|
||||
// 2) Limiting the maximum number of open files in the presence of range
|
||||
// tombstones can degrade read performance. To avoid this problem, set
|
||||
// max_open_files to -1 whenever possible.
|
||||
// 3) Incompatible with row_cache, will return Status::NotSupported() if
|
||||
// row_cache is configured.
|
||||
virtual Status DeleteRange(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key);
|
||||
@@ -997,28 +971,12 @@ class DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) = 0;
|
||||
|
||||
// EXPERIMENTAL
|
||||
// UNDER CONSTRUCTION - DO NOT USE
|
||||
// Return a cross-column-family iterator from a consistent database state.
|
||||
//
|
||||
// If a key exists in more than one column family, value() will be determined
|
||||
// by the wide column value of kDefaultColumnName after coalesced as described
|
||||
// below.
|
||||
//
|
||||
// Each wide column will be independently shadowed by the CFs.
|
||||
// For example, if CF1 has "key_1" ==> {"col_1": "foo",
|
||||
// "col_2", "baz"} and CF2 has "key_1" ==> {"col_2": "quux", "col_3", "bla"},
|
||||
// and when the iterator is at key_1, columns() will return
|
||||
// {"col_1": "foo", "col_2", "quux", "col_3", "bla"}
|
||||
// In this example, value() will be empty, because none of them have values
|
||||
// for kDefaultColumnName
|
||||
virtual std::unique_ptr<Iterator> NewCoalescingIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) = 0;
|
||||
|
||||
// EXPERIMENTAL
|
||||
// A cross-column-family iterator that collects and returns attribute groups
|
||||
// for each key in order provided by comparator
|
||||
virtual std::unique_ptr<AttributeGroupIterator> NewAttributeGroupIterator(
|
||||
// When the same key is present in multiple column families, the iterator
|
||||
// selects the value or columns from the first column family containing the
|
||||
// key, in the order specified by the `column_families` parameter.
|
||||
virtual std::unique_ptr<Iterator> NewMultiCfIterator(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) = 0;
|
||||
|
||||
|
||||
@@ -1209,10 +1209,6 @@ class Logger {
|
||||
public:
|
||||
static constexpr size_t kDoNotSupportGetLogFileSize = SIZE_MAX;
|
||||
|
||||
// Set to INFO_LEVEL when RocksDB is compiled in release mode, and
|
||||
// DEBUG_LEVEL when compiled in debug mode. See DBOptions::info_log_level.
|
||||
static const InfoLogLevel kDefaultLogLevel;
|
||||
|
||||
explicit Logger(const InfoLogLevel log_level = InfoLogLevel::INFO_LEVEL)
|
||||
: closed_(false), log_level_(log_level) {}
|
||||
// No copying allowed
|
||||
|
||||
@@ -702,16 +702,6 @@ class FileSystem : public Customizable {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
// EXPERIMENTAL
|
||||
// Discard any directory metadata cached in memory for the specified
|
||||
// directory and its descendants. Useful for distributed file systems
|
||||
// where the local cache may be out of sync with the actual directory state.
|
||||
//
|
||||
// The implementation is not required to be thread safe. Its the caller's
|
||||
// responsibility to ensure that no directory operations happen
|
||||
// concurrently.
|
||||
virtual void DiscardCacheForDirectory(const std::string& /*path*/) {}
|
||||
|
||||
// Indicates to upper layers which FileSystem operations mentioned in
|
||||
// FSSupportedOps are supported by underlying FileSystem. Each bit in
|
||||
// supported_ops argument represent corresponding FSSupportedOps operation.
|
||||
@@ -1634,10 +1624,6 @@ class FileSystemWrapper : public FileSystem {
|
||||
return target_->AbortIO(io_handles);
|
||||
}
|
||||
|
||||
void DiscardCacheForDirectory(const std::string& path) override {
|
||||
target_->DiscardCacheForDirectory(path);
|
||||
}
|
||||
|
||||
void SupportedOps(int64_t& supported_ops) override {
|
||||
return target_->SupportedOps(supported_ops);
|
||||
}
|
||||
|
||||
@@ -20,19 +20,66 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/iterator_base.h"
|
||||
#include "rocksdb/cleanable.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Iterator : public IteratorBase {
|
||||
class Iterator : public Cleanable {
|
||||
public:
|
||||
Iterator() {}
|
||||
// No copying allowed
|
||||
Iterator(const Iterator&) = delete;
|
||||
void operator=(const Iterator&) = delete;
|
||||
|
||||
virtual ~Iterator() override {}
|
||||
virtual ~Iterator() {}
|
||||
|
||||
// An iterator is either positioned at a key/value pair, or
|
||||
// not valid. This method returns true iff the iterator is valid.
|
||||
// Always returns false if !status().ok().
|
||||
virtual bool Valid() const = 0;
|
||||
|
||||
// Position at the first key in the source. The iterator is Valid()
|
||||
// after this call iff the source is not empty.
|
||||
virtual void SeekToFirst() = 0;
|
||||
|
||||
// Position at the last key in the source. The iterator is
|
||||
// Valid() after this call iff the source is not empty.
|
||||
virtual void SeekToLast() = 0;
|
||||
|
||||
// Position at the first key in the source that at or past target.
|
||||
// The iterator is Valid() after this call iff the source contains
|
||||
// an entry that comes at or past target.
|
||||
// All Seek*() methods clear any error status() that the iterator had prior to
|
||||
// the call; after the seek, status() indicates only the error (if any) that
|
||||
// happened during the seek, not any past errors.
|
||||
// Target does not contain timestamp.
|
||||
virtual void Seek(const Slice& target) = 0;
|
||||
|
||||
// Position at the last key in the source that at or before target.
|
||||
// The iterator is Valid() after this call iff the source contains
|
||||
// an entry that comes at or before target.
|
||||
// Target does not contain timestamp.
|
||||
virtual void SeekForPrev(const Slice& target) = 0;
|
||||
|
||||
// Moves to the next entry in the source. After this call, Valid() is
|
||||
// true iff the iterator was not positioned at the last entry in the source.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() = 0;
|
||||
|
||||
// Moves to the previous entry in the source. After this call, Valid() is
|
||||
// true iff the iterator was not positioned at the first entry in source.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() = 0;
|
||||
|
||||
// Return the key for the current entry. The underlying storage for
|
||||
// the returned slice is valid only until the next modification of the
|
||||
// iterator (i.e. the next SeekToFirst/SeekToLast/Seek/SeekForPrev/Next/Prev
|
||||
// operation).
|
||||
// REQUIRES: Valid()
|
||||
virtual Slice key() const = 0;
|
||||
|
||||
// Return the value for the current entry. If the entry is a plain key-value,
|
||||
// return the value as-is; if it is a wide-column entity, return the value of
|
||||
@@ -55,6 +102,11 @@ class Iterator : public IteratorBase {
|
||||
return kNoWideColumns;
|
||||
}
|
||||
|
||||
// If an error has occurred, return it. Else return an ok status.
|
||||
// If non-blocking IO is requested and this operation cannot be
|
||||
// satisfied without doing some IO, then this returns Status::Incomplete().
|
||||
virtual Status status() const = 0;
|
||||
|
||||
// If supported, the DB state that the iterator reads from is updated to
|
||||
// the latest state. The iterator will be invalidated after the call.
|
||||
// Regardless of whether the iterator was created/refreshed previously
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/cleanable.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class IteratorBase : public Cleanable {
|
||||
public:
|
||||
IteratorBase() {}
|
||||
// No copying allowed
|
||||
IteratorBase(const IteratorBase&) = delete;
|
||||
void operator=(const IteratorBase&) = delete;
|
||||
|
||||
virtual ~IteratorBase() {}
|
||||
|
||||
// An iterator is either positioned at a key/value pair, or
|
||||
// not valid. This method returns true iff the iterator is valid.
|
||||
// Always returns false if !status().ok().
|
||||
virtual bool Valid() const = 0;
|
||||
|
||||
// Position at the first key in the source. The iterator is Valid()
|
||||
// after this call iff the source is not empty.
|
||||
virtual void SeekToFirst() = 0;
|
||||
|
||||
// Position at the last key in the source. The iterator is
|
||||
// Valid() after this call iff the source is not empty.
|
||||
virtual void SeekToLast() = 0;
|
||||
|
||||
// Position at the first key in the source that at or past target.
|
||||
// The iterator is Valid() after this call iff the source contains
|
||||
// an entry that comes at or past target.
|
||||
// All Seek*() methods clear any error status() that the iterator had prior to
|
||||
// the call; after the seek, status() indicates only the error (if any) that
|
||||
// happened during the seek, not any past errors.
|
||||
// Target does not contain timestamp.
|
||||
virtual void Seek(const Slice& target) = 0;
|
||||
|
||||
// Position at the last key in the source that at or before target.
|
||||
// The iterator is Valid() after this call iff the source contains
|
||||
// an entry that comes at or before target.
|
||||
// Target does not contain timestamp.
|
||||
virtual void SeekForPrev(const Slice& target) = 0;
|
||||
|
||||
// Moves to the next entry in the source. After this call, Valid() is
|
||||
// true iff the iterator was not positioned at the last entry in the source.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() = 0;
|
||||
|
||||
// Moves to the previous entry in the source. After this call, Valid() is
|
||||
// true iff the iterator was not positioned at the first entry in source.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() = 0;
|
||||
|
||||
// Return the key for the current entry. The underlying storage for
|
||||
// the returned slice is valid only until the next modification of the
|
||||
// iterator (i.e. the next SeekToFirst/SeekToLast/Seek/SeekForPrev/Next/Prev
|
||||
// operation).
|
||||
// REQUIRES: Valid()
|
||||
virtual Slice key() const = 0;
|
||||
|
||||
// If an error has occurred, return it. Else return an ok status.
|
||||
// If non-blocking IO is requested and this operation cannot be
|
||||
// satisfied without doing some IO, then this returns Status::Incomplete().
|
||||
virtual Status status() const = 0;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -643,10 +643,11 @@ struct DBOptions {
|
||||
// Default: nullptr
|
||||
std::shared_ptr<Logger> info_log = nullptr;
|
||||
|
||||
// Minimum level for sending log messages to info_log. The default is
|
||||
// INFO_LEVEL when RocksDB is compiled in release mode, and DEBUG_LEVEL
|
||||
// when it is compiled in debug mode.
|
||||
InfoLogLevel info_log_level = Logger::kDefaultLogLevel;
|
||||
#ifdef NDEBUG
|
||||
InfoLogLevel info_log_level = INFO_LEVEL;
|
||||
#else
|
||||
InfoLogLevel info_log_level = DEBUG_LEVEL;
|
||||
#endif // NDEBUG
|
||||
|
||||
// Number of open files that can be used by the DB. You may need to
|
||||
// increase this if your database has a large working set. Value -1 means
|
||||
@@ -1052,9 +1053,6 @@ struct DBOptions {
|
||||
uint64_t bytes_per_sync = 0;
|
||||
|
||||
// Same as bytes_per_sync, but applies to WAL files
|
||||
// This does not gaurantee the WALs are synced in the order of creation. New
|
||||
// WAL can be synced while an older WAL doesn't. Therefore upon system crash,
|
||||
// this hole in the WAL data can create partial data loss.
|
||||
//
|
||||
// Default: 0, turned off
|
||||
//
|
||||
@@ -1222,8 +1220,6 @@ struct DBOptions {
|
||||
bool allow_2pc = false;
|
||||
|
||||
// A global cache for table-level rows.
|
||||
// Used to speed up Get() queries.
|
||||
// NOTE: does not work with DeleteRange() yet.
|
||||
// Default: nullptr (disabled)
|
||||
std::shared_ptr<RowCache> row_cache = nullptr;
|
||||
|
||||
@@ -1488,30 +1484,6 @@ struct DBOptions {
|
||||
// use "0:00-23:59". To make an entire day have no offpeak period, leave
|
||||
// this field blank. Default: Empty string (no offpeak).
|
||||
std::string daily_offpeak_time_utc = "";
|
||||
|
||||
// EXPERIMENTAL
|
||||
|
||||
// When a RocksDB database is opened in follower mode, this option
|
||||
// is set by the user to request the frequency of the follower
|
||||
// attempting to refresh its view of the leader. RocksDB may choose to
|
||||
// trigger catch ups more frequently if it detects any changes in the
|
||||
// database state.
|
||||
// Default every 10s.
|
||||
uint64_t follower_refresh_catchup_period_ms = 10000;
|
||||
|
||||
// For a given catch up attempt, this option specifies the number of times
|
||||
// to tail the MANIFEST and try to install a new, consistent version before
|
||||
// giving up. Though it should be extremely rare, the catch up may fail if
|
||||
// the leader is mutating the LSM at a very high rate and the follower is
|
||||
// unable to get a consistent view.
|
||||
// Default to 10 attempts
|
||||
uint64_t follower_catchup_retry_count = 10;
|
||||
|
||||
// Time to wait between consecutive catch up attempts
|
||||
// Default 100ms
|
||||
uint64_t follower_catchup_retry_wait_ms = 100;
|
||||
|
||||
// End EXPERIMENTAL
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
@@ -1936,29 +1908,20 @@ Status CreateLoggerFromOptions(const std::string& dbname,
|
||||
|
||||
// CompactionOptions are used in CompactFiles() call.
|
||||
struct CompactionOptions {
|
||||
// DEPRECATED: this option is unsafe because it allows the user to set any
|
||||
// `CompressionType` while always using `CompressionOptions` from the
|
||||
// `ColumnFamilyOptions`. As a result the `CompressionType` and
|
||||
// `CompressionOptions` can easily be inconsistent.
|
||||
//
|
||||
// Compaction output compression type
|
||||
//
|
||||
// Default: `kDisableCompressionOption`
|
||||
//
|
||||
// Default: snappy
|
||||
// If set to `kDisableCompressionOption`, RocksDB will choose compression type
|
||||
// according to the `ColumnFamilyOptions`. RocksDB takes into account the
|
||||
// output level in case the `ColumnFamilyOptions` has level-specific settings.
|
||||
// according to the `ColumnFamilyOptions`, taking into account the output
|
||||
// level if `compression_per_level` is specified.
|
||||
CompressionType compression;
|
||||
|
||||
// Compaction will create files of size `output_file_size_limit`.
|
||||
// Default: MAX, which means that compaction will create a single file
|
||||
uint64_t output_file_size_limit;
|
||||
|
||||
// If > 0, it will replace the option in the DBOptions for this compaction.
|
||||
uint32_t max_subcompactions;
|
||||
|
||||
CompactionOptions()
|
||||
: compression(kDisableCompressionOption),
|
||||
: compression(kSnappyCompression),
|
||||
output_file_size_limit(std::numeric_limits<uint64_t>::max()),
|
||||
max_subcompactions(0) {}
|
||||
};
|
||||
@@ -2246,9 +2209,6 @@ struct WaitForCompactOptions {
|
||||
// A boolean to flush all column families before starting to wait.
|
||||
bool flush = false;
|
||||
|
||||
// A boolean to wait for purge to complete
|
||||
bool wait_for_purge = false;
|
||||
|
||||
// A boolean to call Close() after waiting is done. By the time Close() is
|
||||
// called here, there should be no background jobs in progress and no new
|
||||
// background jobs should be added. DB may not have been closed if Close()
|
||||
|
||||
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user