mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccfb1998b1 | ||
|
|
ab03f7fb53 | ||
|
|
fb6fa230c7 | ||
|
|
8d913b5b75 | ||
|
|
13d5230e5d | ||
|
|
ca3418ca3d | ||
|
|
daa63be3d6 | ||
|
|
aaa570b21f | ||
|
|
f172876964 | ||
|
|
a2f796ff8f | ||
|
|
83a2e71b9a | ||
|
|
96315e9aa9 | ||
|
|
becf8684e3 | ||
|
|
67bfca5117 | ||
|
|
defd97bc9d | ||
|
|
273b3eadf0 | ||
|
|
295326b6ee | ||
|
|
4d3518951a | ||
|
|
75a1230ce8 | ||
|
|
21da4ba4aa | ||
|
|
b99baa5ae7 | ||
|
|
f63428bcc7 | ||
|
|
d458331ee9 | ||
|
|
c21fe1a47f | ||
|
|
b65e29a4a9 | ||
|
|
6727f0f58a | ||
|
|
5c456c4c08 | ||
|
|
112bf15dca | ||
|
|
16c21afc06 | ||
|
|
b32d899482 | ||
|
|
d33d25f903 | ||
|
|
b15f8c7f0e | ||
|
|
b26b395e0a | ||
|
|
719c96125c | ||
|
|
36b061a6c7 | ||
|
|
d12aaf23ca | ||
|
|
9d5c8c89a1 | ||
|
|
2e8a1a14ef | ||
|
|
9245550e8b | ||
|
|
5e203c76a2 | ||
|
|
8be824e316 | ||
|
|
005256bcc8 | ||
|
|
8767267315 | ||
|
|
319374ae67 | ||
|
|
2595476541 | ||
|
|
55877d8893 | ||
|
|
9058fd037c | ||
|
|
24d86f7b41 | ||
|
|
408e8d4c85 | ||
|
|
d94c2adc28 | ||
|
|
9883b5f497 | ||
|
|
05c9c9aeed | ||
|
|
086849aa4f | ||
|
|
f456a7213f | ||
|
|
15d9988ab2 | ||
|
|
349b1ec08f | ||
|
|
c064ac3bc5 | ||
|
|
4384dd5eee | ||
|
|
0fca5e31b4 | ||
|
|
de6d0e5ec3 | ||
|
|
93b163d1a2 | ||
|
|
21db55f816 | ||
|
|
6870cc1187 | ||
|
|
9e4ee7f0c6 | ||
|
|
5aa675457e | ||
|
|
4ff35afb42 | ||
|
|
755010f8d3 | ||
|
|
0e3e43f4d1 | ||
|
|
b800b5eb6a | ||
|
|
72438a6788 | ||
|
|
3db030d7ee | ||
|
|
cec28aa90f | ||
|
|
37b81bd28f | ||
|
|
6997dd909c | ||
|
|
d6f265f9d6 | ||
|
|
ebe2116240 | ||
|
|
5ecb92760a | ||
|
|
b837d41ab1 | ||
|
|
2e1b3f921f | ||
|
|
62b62cf135 | ||
|
|
a97a1f3247 | ||
|
|
f471e56190 | ||
|
|
110ce5f4a3 | ||
|
|
b6c3495a71 | ||
|
|
1f589a3f73 | ||
|
|
de16611a50 | ||
|
|
0bb939611d | ||
|
|
84296bc248 | ||
|
|
093f4ef82c | ||
|
|
9eebaf11cb | ||
|
|
69ad597b46 | ||
|
|
c7e94bc878 | ||
|
|
22fe23edc8 | ||
|
|
8c1558a3e0 | ||
|
|
0b10e7dbba | ||
|
|
5bb7f95ed6 | ||
|
|
aec15eebec | ||
|
|
a31fe52173 | ||
|
|
0d93c8a6ca | ||
|
|
41c6b4549a | ||
|
|
58bc4db456 | ||
|
|
8bf1f6f87f | ||
|
|
a06a7fdc88 | ||
|
|
6f79496475 | ||
|
|
e0ddbee76f | ||
|
|
ff204d8ecd | ||
|
|
56f7ef50d7 | ||
|
|
e3f5125ff1 | ||
|
|
748f74aca3 | ||
|
|
39455974cb | ||
|
|
fa4ffc816c | ||
|
|
13549817af | ||
|
|
5c391c7713 |
No files matched your search
@@ -59,12 +59,15 @@ jobs:
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: clang-13
|
||||
CXX: clang++-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- uses: "./.github/actions/build-folly"
|
||||
- run: CC=clang-13 CXX=clang++-13 LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-valgrind:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
|
||||
+74
@@ -1,6 +1,80 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.6.2 (10/31/2024)
|
||||
### Bug Fixes
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
|
||||
## 9.6.1 (08/24/2024)
|
||||
### Bug Fixes
|
||||
* Fix correctness of MultiGet across column families with user timestamp.
|
||||
|
||||
## 9.6.0 (08/19/2024)
|
||||
### New Features
|
||||
* *Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
* New option `BlockBasedTableOptions::decouple_partitioned_filters` should improve efficiency in serving read queries because filter and index partitions can consistently target the configured `metadata_block_size`. This option is currently opt-in.
|
||||
* Introduce a new mutable CF option `paranoid_memory_checks`. It enables additional validation on data integrity during reads/scanning. Currently, skip list based memtable will validate key ordering during look up and scans.
|
||||
|
||||
### Public API Changes
|
||||
* Add ticker stats to count file read retries due to checksum mismatch
|
||||
* Adds optional installation callback function for remote compaction
|
||||
|
||||
### Behavior Changes
|
||||
* There may be less intra-L0 compaction triggered by total L0 size being too small. We now use compensated file size (tombstones are assigned some value size) when calculating L0 size and reduce the threshold for L0 size limit. This is to avoid accumulating too much data/tombstones in L0.
|
||||
|
||||
### Bug Fixes
|
||||
* *Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
* Fixed a bug where we set unprep_seqs_ even when WriteImpl() fails. This was caught by stress test write fault injection in WriteImpl(). This may have incorrectly caused iteration creation failure for unvalidated writes or returned wrong result for WriteUnpreparedTxn::GetUnpreparedSequenceNumbers().
|
||||
* Fixed a bug where successful write right after error recovery for last failed write finishes causes duplicate WAL entries
|
||||
* Fixed a data race involving the background error status in `unordered_write` mode.
|
||||
* *Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
* Fix a bug where per kv checksum corruption may be ignored in MultiGet().
|
||||
* Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
|
||||
|
||||
## 9.5.0 (07/19/2024)
|
||||
### Public API Changes
|
||||
* Introduced new C API function rocksdb_writebatch_iterate_cf for column family-aware iteration over the contents of a WriteBatch
|
||||
* Add support to ingest SST files generated by a DB instead of SstFileWriter. This can be enabled with experimental option `IngestExternalFileOptions::allow_db_generated_files`.
|
||||
|
||||
### Behavior Changes
|
||||
* When calculating total log size for the `log_size_for_flush` argument in `CreateCheckpoint` API, the size of the archived log will not be included to avoid unnecessary flush
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a major bug in which an iterator using prefix filtering and SeekForPrev might miss data when the DB is using `whole_key_filtering=false` and `partition_filters=true`.
|
||||
* Fixed a bug where `OnErrorRecoveryBegin()` is not called before auto recovery starts.
|
||||
* Fixed a bug where event listener reads ErrorHandler's `bg_error_` member without holding db mutex(#12803).
|
||||
* Fixed a bug in handling MANIFEST write error that caused the latest valid MANIFEST file to get deleted, resulting in the DB being unopenable.
|
||||
* Fixed a race between error recovery due to manifest sync or write failure and external SST file ingestion. Both attempt to write a new manifest file, which causes an assertion failure.
|
||||
|
||||
### Performance Improvements
|
||||
* Fix an issue where compactions were opening table files and reading table properties while holding db mutex_.
|
||||
* Reduce unnecessary filesystem queries and DB mutex acquires in creating backups and checkpoints.
|
||||
|
||||
## 9.4.0 (06/23/2024)
|
||||
### New Features
|
||||
* Added a `CompactForTieringCollectorFactory` to auto trigger compaction for tiering use case.
|
||||
* Optimistic transactions and pessimistic transactions with the WriteCommitted policy now support the `GetEntityForUpdate` API.
|
||||
* Added a new "count" command to the ldb repl shell. By default, it prints a count of keys in the database from start to end. The options --from=<key> and/or --to=<key> can be specified to limit the range.
|
||||
* Add `rocksdb_writebatch_update_timestamps`, `rocksdb_writebatch_wi_update_timestamps` in C API.
|
||||
* Add `rocksdb_iter_refresh` in C API.
|
||||
* Add `rocksdb_writebatch_create_with_params`, `rocksdb_writebatch_wi_create_with_params` to create WB and WBWI with all options in C API
|
||||
|
||||
### Public API Changes
|
||||
* Deprecated names `LogFile` and `VectorLogPtr` in favor of new names `WalFile` and `VectorWalPtr`.
|
||||
* Introduce a new universal compaction option CompactionOptionsUniversal::max_read_amp which allows user to define the limit on the number of sorted runs separately from the trigger for compaction (`level0_file_num_compaction_trigger`) #12477.
|
||||
|
||||
### Behavior Changes
|
||||
* Inactive WALs are immediately closed upon being fully sync-ed rather than in a background thread. This is to ensure LinkFile() is not called on files still open for write, which might not be supported by some FileSystem implementations. This should not be a performance issue, but an opt-out is available with with new DB option `background_close_inactive_wals`.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a rare case in which a hard-linked WAL in a Checkpoint is not fully synced (so might lose data on power loss).
|
||||
* Fixed the output of the `ldb dump_wal` command for `PutEntity` records so it prints the key and correctly resets the hexadecimal formatting flag after printing the wide-column entity.
|
||||
* Fixed an issue where `PutEntity` records were handled incorrectly while rebuilding transactions during recovery.
|
||||
* Various read operations could ignore various ReadOptions that might be relevant. Fixed many such cases, which can result in behavior change but a better reflection of specified options.
|
||||
|
||||
### Performance Improvements
|
||||
* Improved write throughput to memtable when there's a large number of concurrent writers and allow_concurrent_memtable_write=true(#12545)
|
||||
|
||||
## 9.3.0 (05/17/2024)
|
||||
### New Features
|
||||
* Optimistic transactions and pessimistic transactions with the WriteCommitted policy now support the `GetEntity` API.
|
||||
|
||||
@@ -2139,8 +2139,8 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_VER ?= 1.2.1
|
||||
SNAPPY_SHA256 ?= 736aeb64d86566d2236ddffa2865ee5d7a82d26c9016b36218fcc27ea4f09f86
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.4
|
||||
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
|
||||
@@ -2237,7 +2237,7 @@ libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DSNAPPY_BUILD_BENCHMARKS=OFF -DSNAPPY_BUILD_TESTS=OFF --compile-no-warning-as-error ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
lz4-$(LZ4_VER).tar.gz:
|
||||
@@ -2484,13 +2484,11 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard 17054c85c2dee0e84cbee7ef28d7bae7d0b1630e
|
||||
cd third-party/folly && git reset --hard c48fdd205c1c291651749d532b8055fe822bba25
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
|
||||
@# NOTE: this hack is only needed if building target build_folly in the future
|
||||
perl -pi -e 's,https://github.com/jedisct1/libsodium/releases/download/1.0.17/libsodium-1.0.17.tar.gz,https://download.libsodium.org/libsodium/releases/old/unsupported/libsodium-1.0.17.tar.gz,' third-party/folly/build/fbcode_builder/manifests/libsodium
|
||||
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
|
||||
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
|
||||
|
||||
|
||||
@@ -138,10 +138,10 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
|
||||
reinit_internal_iter();
|
||||
break;
|
||||
} else {
|
||||
delete *memtable_range_tombstone_iter_;
|
||||
*memtable_range_tombstone_iter_ = new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(t),
|
||||
&cfd->internal_comparator(), nullptr, nullptr);
|
||||
*memtable_range_tombstone_iter_ =
|
||||
std::make_unique<TruncatedRangeDelIterator>(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(t),
|
||||
&cfd->internal_comparator(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
db_impl->ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
|
||||
@@ -55,7 +55,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
db_iter_->SetIter(iter);
|
||||
}
|
||||
|
||||
void SetMemtableRangetombstoneIter(TruncatedRangeDelIterator** iter) {
|
||||
void SetMemtableRangetombstoneIter(
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* iter) {
|
||||
memtable_range_tombstone_iter_ = iter;
|
||||
}
|
||||
|
||||
@@ -110,7 +111,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
bool allow_refresh_ = true;
|
||||
// If this is nullptr, it means the mutable memtable does not contain range
|
||||
// tombstone when added under this DBIter.
|
||||
TruncatedRangeDelIterator** memtable_range_tombstone_iter_ = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* memtable_range_tombstone_iter_ =
|
||||
nullptr;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
|
||||
@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileCache::Evict(uint64_t blob_file_number) {
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -36,6 +36,15 @@ class BlobFileCache {
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
// Called when a blob file is obsolete to ensure it is removed from the cache
|
||||
// to avoid effectively leaking the open file and assicated memory
|
||||
void Evict(uint64_t blob_file_number);
|
||||
|
||||
// Used to identify cache entries for blob files (not normally useful)
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
return CacheInterface::GetBasicHelper();
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
|
||||
@@ -532,6 +532,42 @@ struct rocksdb_universal_compaction_options_t {
|
||||
ROCKSDB_NAMESPACE::CompactionOptionsUniversal* rep;
|
||||
};
|
||||
|
||||
struct rocksdb_callback_logger_t : public Logger {
|
||||
static const ssize_t STACK_BUFSZ = 512;
|
||||
rocksdb_callback_logger_t(InfoLogLevel log_level,
|
||||
void (*logv_cb)(void*, unsigned, char*, size_t),
|
||||
void* priv)
|
||||
: Logger(log_level), logv_cb_(logv_cb), priv_(priv) {}
|
||||
|
||||
using Logger::Logv;
|
||||
void Logv(const InfoLogLevel level, const char* fmt, va_list ap0) override {
|
||||
char stack_buf[STACK_BUFSZ];
|
||||
char* alloc_buf = nullptr;
|
||||
char* buf = stack_buf;
|
||||
int len = 0;
|
||||
va_list ap1;
|
||||
if (!logv_cb_) return;
|
||||
va_copy(ap1, ap0);
|
||||
len = vsnprintf(buf, STACK_BUFSZ, fmt, ap0);
|
||||
if (len <= 0)
|
||||
goto cleanup;
|
||||
else if (len >= STACK_BUFSZ) {
|
||||
buf = alloc_buf = reinterpret_cast<char*>(malloc(len + 1));
|
||||
if (!buf) goto cleanup;
|
||||
len = vsnprintf(buf, len + 1, fmt, ap1);
|
||||
if (len <= 0) goto cleanup;
|
||||
}
|
||||
logv_cb_(priv_, unsigned(level), buf, size_t(len));
|
||||
cleanup:
|
||||
va_end(ap1);
|
||||
free(alloc_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
void (*logv_cb_)(void*, unsigned, char*, size_t) = nullptr;
|
||||
void* priv_ = nullptr;
|
||||
};
|
||||
|
||||
static bool SaveError(char** errptr, const Status& s) {
|
||||
assert(errptr != nullptr);
|
||||
if (s.ok()) {
|
||||
@@ -1802,6 +1838,26 @@ void rocksdb_approximate_sizes_cf(
|
||||
delete[] ranges;
|
||||
}
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_approximate_sizes_cf_with_flags(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
int num_ranges, const char* const* range_start_key,
|
||||
const size_t* range_start_key_len, const char* const* range_limit_key,
|
||||
const size_t* range_limit_key_len, uint8_t include_flags, uint64_t* sizes,
|
||||
char** errptr) {
|
||||
Range* ranges = new Range[num_ranges];
|
||||
for (int i = 0; i < num_ranges; i++) {
|
||||
ranges[i].start = Slice(range_start_key[i], range_start_key_len[i]);
|
||||
ranges[i].limit = Slice(range_limit_key[i], range_limit_key_len[i]);
|
||||
}
|
||||
Status s = db->rep->GetApproximateSizes(
|
||||
column_family->rep, ranges, num_ranges, sizes,
|
||||
static_cast<DB::SizeApproximationFlags>(include_flags));
|
||||
if (!s.ok()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
delete[] ranges;
|
||||
}
|
||||
|
||||
void rocksdb_delete_file(rocksdb_t* db, const char* name) {
|
||||
db->rep->DeleteFile(name);
|
||||
}
|
||||
@@ -2230,6 +2286,32 @@ class H : public WriteBatch::Handler {
|
||||
}
|
||||
};
|
||||
|
||||
class HCF : public WriteBatch::Handler {
|
||||
public:
|
||||
void* state_;
|
||||
void (*put_cf_)(void*, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen);
|
||||
void (*deleted_cf_)(void*, uint32_t cfid, const char* k, size_t klen);
|
||||
void (*merge_cf_)(void*, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen);
|
||||
Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
(*put_cf_)(state_, column_family_id, key.data(), key.size(), value.data(),
|
||||
value.size());
|
||||
return Status::OK();
|
||||
}
|
||||
Status DeleteCF(uint32_t column_family_id, const Slice& key) override {
|
||||
(*deleted_cf_)(state_, column_family_id, key.data(), key.size());
|
||||
return Status::OK();
|
||||
}
|
||||
Status MergeCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) override {
|
||||
(*merge_cf_)(state_, column_family_id, key.data(), key.size(), value.data(),
|
||||
value.size());
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
void rocksdb_writebatch_iterate(rocksdb_writebatch_t* b, void* state,
|
||||
void (*put)(void*, const char* k, size_t klen,
|
||||
const char* v, size_t vlen),
|
||||
@@ -2242,6 +2324,21 @@ void rocksdb_writebatch_iterate(rocksdb_writebatch_t* b, void* state,
|
||||
b->rep.Iterate(&handler);
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_iterate_cf(
|
||||
rocksdb_writebatch_t* b, void* state,
|
||||
void (*put_cf)(void*, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen),
|
||||
void (*deleted_cf)(void*, uint32_t cfid, const char* k, size_t klen),
|
||||
void (*merge_cf)(void*, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen)) {
|
||||
HCF handler;
|
||||
handler.state_ = state;
|
||||
handler.put_cf_ = put_cf;
|
||||
handler.deleted_cf_ = deleted_cf;
|
||||
handler.merge_cf_ = merge_cf;
|
||||
b->rep.Iterate(&handler);
|
||||
}
|
||||
|
||||
const char* rocksdb_writebatch_data(rocksdb_writebatch_t* b, size_t* size) {
|
||||
*size = b->rep.GetDataSize();
|
||||
return b->rep.Data().c_str();
|
||||
@@ -3037,6 +3134,15 @@ rocksdb_logger_t* rocksdb_logger_create_stderr_logger(int log_level,
|
||||
return logger;
|
||||
}
|
||||
|
||||
rocksdb_logger_t* rocksdb_logger_create_callback_logger(
|
||||
int log_level, void (*callback)(void*, unsigned, char*, size_t),
|
||||
void* priv) {
|
||||
rocksdb_logger_t* logger = new rocksdb_logger_t;
|
||||
logger->rep = std::make_shared<rocksdb_callback_logger_t>(
|
||||
static_cast<InfoLogLevel>(log_level), callback, priv);
|
||||
return logger;
|
||||
}
|
||||
|
||||
void rocksdb_logger_destroy(rocksdb_logger_t* logger) { delete logger; }
|
||||
|
||||
void rocksdb_options_set_env(rocksdb_options_t* opt, rocksdb_env_t* env) {
|
||||
|
||||
+88
@@ -199,6 +199,79 @@ static void CheckDel(void* ptr, const char* k, size_t klen) {
|
||||
(*state)++;
|
||||
}
|
||||
|
||||
// Callback from rocksdb_writebatch_iterate_cf()
|
||||
static void CheckPutCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen) {
|
||||
int* state = (int*)ptr;
|
||||
switch (*state) {
|
||||
case 0:
|
||||
CheckEqual("bar", k, klen);
|
||||
CheckEqual("b", v, vlen);
|
||||
CheckCondition(cfid == 1);
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("box", k, klen);
|
||||
CheckEqual("c", v, vlen);
|
||||
CheckCondition(cfid == 1);
|
||||
break;
|
||||
case 4:
|
||||
CheckEqual("foo", k, klen);
|
||||
CheckEqual("f", v, vlen);
|
||||
CheckCondition(cfid == 0);
|
||||
break;
|
||||
case 6:
|
||||
CheckEqual("baz", k, klen);
|
||||
CheckEqual("a", v, vlen);
|
||||
CheckCondition(cfid == 0);
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
|
||||
// Callback from rocksdb_writebatch_iterate_cf()
|
||||
static void CheckDelCF(void* ptr, uint32_t cfid, const char* k, size_t klen) {
|
||||
int* state = (int*)ptr;
|
||||
switch (*state) {
|
||||
case 2:
|
||||
CheckEqual("bar", k, klen);
|
||||
CheckCondition(cfid == 1);
|
||||
break;
|
||||
case 5:
|
||||
CheckEqual("foo", k, klen);
|
||||
CheckCondition(cfid == 0);
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
|
||||
// Callback from rocksdb_writebatch_iterate_cf()
|
||||
static void CheckMergeCF(void* ptr, uint32_t cfid, const char* k, size_t klen,
|
||||
const char* v, size_t vlen) {
|
||||
int* state = (int*)ptr;
|
||||
switch (*state) {
|
||||
case 3:
|
||||
CheckEqual("box", k, klen);
|
||||
CheckEqual("cc", v, vlen);
|
||||
CheckCondition(cfid == 1);
|
||||
break;
|
||||
case 7:
|
||||
CheckEqual("baz", k, klen);
|
||||
CheckEqual("aa", v, vlen);
|
||||
CheckCondition(cfid == 0);
|
||||
break;
|
||||
default:
|
||||
CheckCondition(false);
|
||||
break;
|
||||
}
|
||||
(*state)++;
|
||||
}
|
||||
|
||||
static void CmpDestroy(void* arg) { (void)arg; }
|
||||
|
||||
static int CmpCompare(void* arg, const char* a, size_t alen, const char* b,
|
||||
@@ -1688,6 +1761,21 @@ int main(int argc, char** argv) {
|
||||
CheckPinGetCF(db, roptions, handles[1], "bar", NULL);
|
||||
CheckPinGetCF(db, roptions, handles[1], "box", "c");
|
||||
CheckPinGetCF(db, roptions, handles[1], "buff", "rocksdb");
|
||||
rocksdb_writebatch_clear(wb);
|
||||
// Test WriteBatch iteration with Column Family
|
||||
int pos = 0;
|
||||
rocksdb_writebatch_put_cf(wb, handles[1], "bar", 3, "b", 1);
|
||||
rocksdb_writebatch_put_cf(wb, handles[1], "box", 3, "c", 1);
|
||||
rocksdb_writebatch_delete_cf(wb, handles[1], "bar", 3);
|
||||
rocksdb_writebatch_merge_cf(wb, handles[1], "box", 3, "cc", 2);
|
||||
rocksdb_writebatch_put(wb, "foo", 3, "f", 1);
|
||||
rocksdb_writebatch_delete(wb, "foo", 3);
|
||||
rocksdb_writebatch_put(wb, "baz", 3, "a", 1);
|
||||
rocksdb_writebatch_merge(wb, "baz", 3, "aa", 2);
|
||||
rocksdb_writebatch_iterate_cf(wb, &pos, CheckPutCF, CheckDelCF,
|
||||
CheckMergeCF);
|
||||
CheckCondition(pos == 8);
|
||||
rocksdb_writebatch_clear(wb);
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
|
||||
rocksdb_flush_wal(db, 1, &err);
|
||||
|
||||
@@ -401,6 +401,7 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
BlobSource* blob_source() const { return blob_source_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
|
||||
@@ -3067,12 +3067,20 @@ TEST_P(ColumnFamilyTest, CompactionSpeedupForMarkedFiles) {
|
||||
WaitForCompaction();
|
||||
AssertFilesPerLevel("0,1", 0 /* cf */);
|
||||
|
||||
// We should calculate the limit by obtaining the number of env background
|
||||
// threads, because the current test case will share the same env
|
||||
// with another case that may have already increased the number of
|
||||
// background threads which is larger than kParallelismLimit
|
||||
const auto limit = env_->GetBackgroundThreads(Env::Priority::LOW);
|
||||
|
||||
// Block the compaction thread pool so marked files accumulate in L0.
|
||||
test::SleepingBackgroundTask sleeping_tasks[kParallelismLimit];
|
||||
for (int i = 0; i < kParallelismLimit; i++) {
|
||||
std::vector<std::shared_ptr<test::SleepingBackgroundTask>> sleeping_tasks;
|
||||
for (int i = 0; i < limit; i++) {
|
||||
sleeping_tasks.emplace_back(
|
||||
std::make_shared<test::SleepingBackgroundTask>());
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_tasks[i], Env::Priority::LOW);
|
||||
sleeping_tasks[i].WaitUntilSleeping();
|
||||
sleeping_tasks[i].get(), Env::Priority::LOW);
|
||||
sleeping_tasks[i]->WaitUntilSleeping();
|
||||
}
|
||||
|
||||
// Zero marked upper-level files. No speedup.
|
||||
@@ -3091,9 +3099,9 @@ TEST_P(ColumnFamilyTest, CompactionSpeedupForMarkedFiles) {
|
||||
ASSERT_EQ(kParallelismLimit, dbfull()->TEST_BGCompactionsAllowed());
|
||||
AssertFilesPerLevel("2,1", 0 /* cf */);
|
||||
|
||||
for (int i = 0; i < kParallelismLimit; i++) {
|
||||
sleeping_tasks[i].WakeUp();
|
||||
sleeping_tasks[i].WaitUntilDone();
|
||||
for (int i = 0; i < limit; i++) {
|
||||
sleeping_tasks[i]->WakeUp();
|
||||
sleeping_tasks[i]->WaitUntilDone();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,9 @@ uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
void Compaction::SetInputVersion(Version* _input_version) {
|
||||
// TODO(hx235): consider making this function part of the construction so we
|
||||
// don't forget to call it
|
||||
void Compaction::FinalizeInputInfo(Version* _input_version) {
|
||||
input_version_ = _input_version;
|
||||
cfd_ = input_version_->cfd();
|
||||
|
||||
@@ -864,6 +866,11 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cfd_->ioptions()->table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Round-Robin pri under leveled compaction allows subcompactions by default
|
||||
// and the number of subcompactions can be larger than max_subcompactions_
|
||||
if (cfd_->ioptions()->compaction_pri == kRoundRobin &&
|
||||
|
||||
@@ -289,14 +289,7 @@ class Compaction {
|
||||
// is the sum of all input file sizes.
|
||||
uint64_t OutputFilePreallocationSize() const;
|
||||
|
||||
// TODO(hx235): eventually we should consider `InitInputTableProperties()`'s
|
||||
// status and fail the compaction if needed
|
||||
// TODO(hx235): consider making this function part of the construction so we
|
||||
// don't forget to call it
|
||||
void FinalizeInputInfo(Version* input_version) {
|
||||
SetInputVersion(input_version);
|
||||
InitInputTableProperties().PermitUncheckedError();
|
||||
}
|
||||
void FinalizeInputInfo(Version* input_version);
|
||||
|
||||
struct InputLevelSummaryBuffer {
|
||||
char buffer[128];
|
||||
@@ -333,6 +326,16 @@ class Compaction {
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs);
|
||||
|
||||
// TODO(hx235): eventually we should consider `InitInputTableProperties()`'s
|
||||
// status and fail the compaction if needed
|
||||
//
|
||||
// May open and read table files for table property.
|
||||
// Should not be called while holding mutex_.
|
||||
const TablePropertiesCollection& GetOrInitInputTableProperties() {
|
||||
InitInputTableProperties().PermitUncheckedError();
|
||||
return input_table_properties_;
|
||||
}
|
||||
|
||||
const TablePropertiesCollection& GetInputTableProperties() const {
|
||||
return input_table_properties_;
|
||||
}
|
||||
@@ -433,7 +436,6 @@ class Compaction {
|
||||
const int output_level);
|
||||
|
||||
private:
|
||||
void SetInputVersion(Version* input_version);
|
||||
|
||||
Status InitInputTableProperties();
|
||||
|
||||
|
||||
@@ -468,6 +468,11 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
read_options.rate_limiter_priority = GetRateLimiterPriority();
|
||||
auto* c = compact_->compaction;
|
||||
if (c->immutable_options()->table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->max_subcompactions() <= 1 &&
|
||||
!(c->immutable_options()->compaction_pri == kRoundRobin &&
|
||||
c->immutable_options()->compaction_style == kCompactionStyleLevel)) {
|
||||
@@ -621,6 +626,7 @@ Status CompactionJob::Run() {
|
||||
const size_t num_threads = compact_->sub_compact_states.size();
|
||||
assert(num_threads > 0);
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
compact_->compaction->GetOrInitInputTableProperties();
|
||||
|
||||
// Launch a thread for each of subcompactions 1...num_threads-1
|
||||
std::vector<port::Thread> thread_pool;
|
||||
|
||||
@@ -552,7 +552,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -575,7 +576,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
|
||||
@@ -925,11 +925,15 @@ bool LevelCompactionBuilder::PickSizeBasedIntraL0Compaction() {
|
||||
}
|
||||
uint64_t l0_size = 0;
|
||||
for (const auto& file : l0_files) {
|
||||
l0_size += file->fd.GetFileSize();
|
||||
assert(file->compensated_file_size >= file->fd.GetFileSize());
|
||||
// Compact down L0s with more deletions.
|
||||
l0_size += file->compensated_file_size;
|
||||
}
|
||||
const uint64_t min_lbase_size =
|
||||
l0_size * static_cast<uint64_t>(std::max(
|
||||
10.0, mutable_cf_options_.max_bytes_for_level_multiplier));
|
||||
|
||||
// Avoid L0->Lbase compactions that are inefficient for write-amp.
|
||||
const double kMultiplier =
|
||||
std::max(10.0, mutable_cf_options_.max_bytes_for_level_multiplier) * 2;
|
||||
const uint64_t min_lbase_size = MultiplyCheckOverflow(l0_size, kMultiplier);
|
||||
assert(min_lbase_size >= l0_size);
|
||||
const std::vector<FileMetaData*>& lbase_files =
|
||||
vstorage_->LevelFiles(/*level=*/base_level);
|
||||
|
||||
@@ -214,7 +214,10 @@ class CompactionPickerTest : public CompactionPickerTestBase {
|
||||
explicit CompactionPickerTest()
|
||||
: CompactionPickerTestBase(BytewiseComparator()) {}
|
||||
|
||||
~CompactionPickerTest() override = default;
|
||||
~CompactionPickerTest() override {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
};
|
||||
|
||||
class CompactionPickerU64TsTest : public CompactionPickerTestBase {
|
||||
@@ -4284,27 +4287,28 @@ TEST_F(CompactionPickerTest, IntraL0WhenL0IsSmall) {
|
||||
SCOPED_TRACE("lbase_size_multiplier=" +
|
||||
std::to_string(lbase_size_multiplier));
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// When L0 size is <= Lbase size / max_bytes_for_level_multiplier,
|
||||
// When L0 size is <= Lbase size / max_bytes_for_level_multiplier / 2,
|
||||
// intra-L0 compaction is picked. Otherwise, L0->L1
|
||||
// compaction is picked.
|
||||
// compensated_file_size will be used to compute total l0 size.
|
||||
Add(/*level=*/0, /*file_number=*/1U, /*smallest=*/"100",
|
||||
/*largest=*/"200", /*file_size=*/1000, /*path_id=*/0,
|
||||
/*largest=*/"200", /*file_size=*/10, /*path_id=*/0,
|
||||
/*smallest_seq=*/10, /*largest_seq=*/11,
|
||||
/*compensated_file_size=*/1000);
|
||||
Add(/*level=*/0, /*file_number=*/2U, /*smallest=*/"100",
|
||||
/*largest=*/"100", /*file_size=*/1000, /*path_id=*/0,
|
||||
/*largest=*/"100", /*file_size=*/10, /*path_id=*/0,
|
||||
/*smallest_seq=*/20, /*largest_seq=*/21,
|
||||
/*compensated_file_size=*/1000);
|
||||
Add(/*level=*/0, /*file_number=*/3U, /*smallest=*/"100",
|
||||
/*largest=*/"200", /*file_size=*/1000, /*path_id=*/0,
|
||||
/*largest=*/"200", /*file_size=*/10, /*path_id=*/0,
|
||||
/*smallest_seq=*/30, /*largest_seq=*/31,
|
||||
/*compensated_file_size=*/1000);
|
||||
Add(/*level=*/0, /*file_number=*/4U, /*smallest=*/"100",
|
||||
/*largest=*/"200", /*file_size=*/1000, /*path_id=*/0,
|
||||
/*largest=*/"200", /*file_size=*/10, /*path_id=*/0,
|
||||
/*smallest_seq=*/40, /*largest_seq=*/41,
|
||||
/*compensated_file_size=*/1000);
|
||||
const uint64_t l0_size = 4000;
|
||||
const uint64_t lbase_size = l0_size * lbase_size_multiplier;
|
||||
const uint64_t lbase_size = l0_size * lbase_size_multiplier * 2;
|
||||
Add(/*level=*/1, /*file_number=*/5U, /*smallest=*/"100",
|
||||
/*largest=*/"200", /*file_size=*/lbase_size, /*path_id=*/0,
|
||||
/*smallest_seq=*/0, /*largest_seq=*/0,
|
||||
|
||||
@@ -140,9 +140,13 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
// CompactionServiceJobStatus::kSuccess was returned, but somehow we failed to
|
||||
// read the result. Consider this as an installation failure
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
sub_compact->status = compaction_result.status;
|
||||
@@ -154,18 +158,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
is_first_one = false;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Receive remote compaction result, output path: "
|
||||
"%s, files: %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction_result.output_path.c_str(),
|
||||
output_files_oss.str().c_str());
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Received remote compaction result, output path: "
|
||||
"%s, files: %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction_result.output_path.c_str(), output_files_oss.str().c_str());
|
||||
|
||||
// Installation Starts
|
||||
for (const auto& file : compaction_result.output_files) {
|
||||
uint64_t file_num = versions_->NewFileNumber();
|
||||
auto src_file = compaction_result.output_path + "/" + file.file_name;
|
||||
@@ -174,6 +174,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
s = fs_->RenameFile(src_file, tgt_file, IOOptions(), nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
@@ -182,6 +184,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
|
||||
@@ -206,6 +210,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kSuccess);
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
|
||||
@@ -275,6 +281,8 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
c->GetOrInitInputTableProperties();
|
||||
|
||||
// Pick the only sub-compaction we should have
|
||||
assert(compact_->sub_compact_states.size() == 1);
|
||||
SubcompactionState* sub_compact = compact_->sub_compact_states.data();
|
||||
|
||||
@@ -108,6 +108,11 @@ class MyTestCompactionService : public CompactionService {
|
||||
}
|
||||
}
|
||||
|
||||
void OnInstallation(const std::string& /*scheduled_job_id*/,
|
||||
CompactionServiceJobStatus status) override {
|
||||
final_updated_status_ = status;
|
||||
}
|
||||
|
||||
int GetCompactionNum() { return compaction_num_.load(); }
|
||||
|
||||
CompactionServiceJobInfo GetCompactionInfoForStart() { return start_info_; }
|
||||
@@ -136,6 +141,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
|
||||
void SetCanceled(bool canceled) { canceled_ = canceled; }
|
||||
|
||||
CompactionServiceJobStatus GetFinalCompactionServiceJobStatus() {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
@@ -158,6 +167,8 @@ class MyTestCompactionService : public CompactionService {
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
table_properties_collector_factories_;
|
||||
std::atomic_bool canceled_{false};
|
||||
std::atomic<CompactionServiceJobStatus> final_updated_status_{
|
||||
CompactionServiceJobStatus::kUseLocal};
|
||||
};
|
||||
|
||||
class CompactionServiceTest : public DBTestBase {
|
||||
@@ -255,6 +266,8 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
ASSERT_EQ(CompactionServiceJobStatus::kSuccess,
|
||||
my_cs->GetFinalCompactionServiceJobStatus());
|
||||
|
||||
// make sure the compaction statistics is only recorded on the remote side
|
||||
ASSERT_GE(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES), 1);
|
||||
@@ -437,6 +450,8 @@ TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
Slice end(end_str);
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_FALSE(s.ok());
|
||||
ASSERT_EQ(CompactionServiceJobStatus::kFailure,
|
||||
my_cs->GetFinalCompactionServiceJobStatus());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, SubCompaction) {
|
||||
|
||||
@@ -3407,6 +3407,46 @@ class TableFileListener : public EventListener {
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> cf_to_paths_;
|
||||
};
|
||||
|
||||
class FlushTableFileListener : public EventListener {
|
||||
public:
|
||||
void OnTableFileCreated(const TableFileCreationInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
if (info.reason != TableFileCreationReason::kFlush) {
|
||||
return;
|
||||
}
|
||||
cf_to_flushed_files_[info.cf_name].push_back(info.file_path);
|
||||
}
|
||||
std::vector<std::string>& GetFlushedFiles(const std::string& cf_name) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
return cf_to_flushed_files_[cf_name];
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<std::string, std::vector<std::string>>
|
||||
cf_to_flushed_files_;
|
||||
};
|
||||
|
||||
class FlushBlobFileListener : public EventListener {
|
||||
public:
|
||||
void OnBlobFileCreated(const BlobFileCreationInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
if (info.reason != BlobFileCreationReason::kFlush) {
|
||||
return;
|
||||
}
|
||||
cf_to_flushed_blobs_files_[info.cf_name].push_back(info.file_path);
|
||||
}
|
||||
std::vector<std::string>& GetFlushedBlobFiles(const std::string& cf_name) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
return cf_to_flushed_blobs_files_[cf_name];
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<std::string, std::vector<std::string>>
|
||||
cf_to_flushed_blobs_files_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(DBBasicTest, LastSstFileNotInManifest) {
|
||||
@@ -3512,6 +3552,121 @@ TEST_F(DBBasicTest, RecoverWithMissingFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
// Param 0: whether to enable blob DB.
|
||||
// Param 1: when blob DB is enabled, whether to also delete the missing L0
|
||||
// file's associated blob file.
|
||||
class BestEffortsRecoverIncompleteVersionTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
BestEffortsRecoverIncompleteVersionTest()
|
||||
: DBTestBase("best_efforts_recover_incomplete_version_test",
|
||||
/*env_do_fsync=*/false) {}
|
||||
};
|
||||
|
||||
TEST_P(BestEffortsRecoverIncompleteVersionTest, Basic) {
|
||||
Options options = CurrentOptions();
|
||||
options.enable_blob_files = std::get<0>(GetParam());
|
||||
bool delete_blob_file_too = std::get<1>(GetParam());
|
||||
DestroyAndReopen(options);
|
||||
FlushTableFileListener* flush_table_listener = new FlushTableFileListener();
|
||||
FlushBlobFileListener* flush_blob_listener = new FlushBlobFileListener();
|
||||
// Disable auto compaction to simplify SST file name tracking.
|
||||
options.disable_auto_compactions = true;
|
||||
options.listeners.emplace_back(flush_table_listener);
|
||||
options.listeners.emplace_back(flush_blob_listener);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<std::string> all_cf_names = {kDefaultColumnFamilyName, "pikachu",
|
||||
"eevee"};
|
||||
int num_cfs = static_cast<int>(handles_.size());
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
std::string start = "a";
|
||||
Slice start_slice = start;
|
||||
std::string end = "d";
|
||||
Slice end_slice = end;
|
||||
for (int cf = 0; cf != num_cfs; ++cf) {
|
||||
ASSERT_OK(Put(cf, "a", "a_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
// Compact file to L1 to avoid trivial file move in the next compaction
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[cf],
|
||||
&start_slice, &end_slice));
|
||||
ASSERT_OK(Put(cf, "a", "a_value_new"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(Put(cf, "b", "b_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(Put(cf, "f", "f_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[cf],
|
||||
&start_slice, &end_slice));
|
||||
}
|
||||
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
// Delete the most recent L0 file which is before a compaction.
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
std::vector<std::string>& files =
|
||||
flush_table_listener->GetFlushedFiles(all_cf_names[i]);
|
||||
ASSERT_EQ(4, files.size());
|
||||
ASSERT_OK(env_->DeleteFile(files[files.size() - 1]));
|
||||
if (options.enable_blob_files) {
|
||||
std::vector<std::string>& blob_files =
|
||||
flush_blob_listener->GetFlushedBlobFiles(all_cf_names[i]);
|
||||
ASSERT_EQ(4, blob_files.size());
|
||||
if (delete_blob_file_too) {
|
||||
ASSERT_OK(env_->DeleteFile(blob_files[files.size() - 1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies(all_cf_names, options);
|
||||
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(handles_[i]);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
// The L0 file flushed right before the last compaction is missing.
|
||||
ASSERT_EQ(0, vstorage->LevelFiles(0).size());
|
||||
// Only the output of the last compaction is available.
|
||||
ASSERT_EQ(1, vstorage->LevelFiles(1).size());
|
||||
}
|
||||
// Verify data
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts, handles_[i]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
ASSERT_EQ("a_value_new", iter->value());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("b", iter->key());
|
||||
ASSERT_EQ("b_value", iter->value());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Write more data.
|
||||
for (int cf = 0; cf < num_cfs; ++cf) {
|
||||
ASSERT_OK(Put(cf, "g", "g_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[cf], nullptr,
|
||||
nullptr));
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), handles_[cf], "g", &value));
|
||||
ASSERT_EQ("g_value", value);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BestEffortsRecoverIncompleteVersionTest,
|
||||
BestEffortsRecoverIncompleteVersionTest,
|
||||
testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
|
||||
TEST_F(DBBasicTest, BestEffortsRecoveryTryMultipleManifests) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
+613
-446
File diff suppressed because it is too large.
Load diff
+19
-3
@@ -21,6 +21,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/metadata.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
@@ -92,6 +93,11 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorWalPtr& files) {
|
||||
return GetSortedWalFilesImpl(files,
|
||||
/*need_seqnos*/ true);
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
|
||||
// Record tracked WALs as a (minimum) cross-check for directory scan
|
||||
std::vector<uint64_t> required_by_manifest;
|
||||
|
||||
@@ -117,7 +123,10 @@ Status DBImpl::GetSortedWalFiles(VectorWalPtr& files) {
|
||||
}
|
||||
}
|
||||
|
||||
Status s = wal_manager_.GetSortedWalFiles(files);
|
||||
// NOTE: need to include archived WALs because needed WALs might have been
|
||||
// archived since getting required_by_manifest set
|
||||
Status s = wal_manager_.GetSortedWalFiles(files, need_seqnos,
|
||||
/*include_archived*/ true);
|
||||
|
||||
// DisableFileDeletions / EnableFileDeletions not supported in read-only DB
|
||||
if (deletions_disabled.ok()) {
|
||||
@@ -206,7 +215,12 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
} else if (opts.wal_size_for_flush > 0) {
|
||||
// FIXME: avoid querying the filesystem for current WAL state
|
||||
// If the outstanding WAL files are small, we skip the flush.
|
||||
s = GetSortedWalFiles(live_wal_files);
|
||||
// Don't take archived log size into account when calculating wal
|
||||
// size for flush, and don't need to verify consistency with manifest
|
||||
// here & now.
|
||||
s = wal_manager_.GetSortedWalFiles(live_wal_files,
|
||||
/* need_seqnos */ false,
|
||||
/*include_archived*/ false);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -217,6 +231,7 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
// We may be able to cover 2PC case too.
|
||||
uint64_t total_wal_size = 0;
|
||||
for (auto& wal : live_wal_files) {
|
||||
assert(wal->Type() == kAliveLogFile);
|
||||
total_wal_size += wal->SizeFileBytes();
|
||||
}
|
||||
if (total_wal_size < opts.wal_size_for_flush) {
|
||||
@@ -428,7 +443,8 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
// WAL files.
|
||||
if (s.ok()) {
|
||||
// FIXME: avoid querying the filesystem for current WAL state
|
||||
s = GetSortedWalFiles(live_wal_files);
|
||||
s = GetSortedWalFilesImpl(live_wal_files,
|
||||
/* need_seqnos */ false);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -289,10 +289,12 @@ TEST_F(DBFollowerTest, RetryCatchup) {
|
||||
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
|
||||
{"DBImpl::BackgroundCompaction:Start",
|
||||
"DBImplFollower::TryCatchupWithLeader:Begin2"},
|
||||
{"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1",
|
||||
{"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin1",
|
||||
"DBImpl::BackgroundCompaction:BeforeCompaction"},
|
||||
{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles",
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2"},
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin2"},
|
||||
{"DBImplFollower::TryCatchupWithLeader:End", "Follower::WaitForCatchup"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -335,10 +337,12 @@ TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImplFollower::TryCatchupWithLeader:Begin1", "Leader::Start"},
|
||||
{"Leader::Flushed", "DBImplFollower::TryCatchupWithLeader:Begin2"},
|
||||
{"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1",
|
||||
{"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin1",
|
||||
"Leader::Done"},
|
||||
{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles",
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2"},
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin2"},
|
||||
{"DBImplFollower::TryCatchupWithLeader:End",
|
||||
"Follower::WaitForCatchup:1"},
|
||||
});
|
||||
|
||||
+70
-36
@@ -17,6 +17,7 @@
|
||||
#include <cstdio>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
@@ -391,8 +392,8 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
if (!s.ok()) {
|
||||
io_s = versions_->io_status();
|
||||
if (!io_s.ok()) {
|
||||
s = error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
error_handler_.SetBGError(io_s,
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,8 +654,9 @@ Status DBImpl::CloseHelper() {
|
||||
// We need to release them before the block cache is destroyed. The block
|
||||
// cache may be destroyed inside versions_.reset(), when column family data
|
||||
// list is destroyed, so leaving handles in table cache after
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table cache.
|
||||
// versions_.reset() may cause issues. Here we clean all unreferenced handles
|
||||
// in table cache, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// Now we assume all user queries have finished, so only version set itself
|
||||
// can possibly hold the blocks from block cache. After releasing unreferenced
|
||||
// handles here, only handles held by version set left and inside
|
||||
@@ -662,6 +664,9 @@ Status DBImpl::CloseHelper() {
|
||||
// time a handle is released, we erase it from the cache too. By doing that,
|
||||
// we can guarantee that after versions_.reset(), table cache is empty
|
||||
// so the cache can be safely destroyed.
|
||||
#ifndef NDEBUG
|
||||
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
|
||||
#endif // !NDEBUG
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
for (auto& txn_entry : recovered_transactions_) {
|
||||
@@ -916,8 +921,8 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker(const ReadOptions& read_options,
|
||||
read_options, write_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
s = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1724,8 +1729,8 @@ Status DBImpl::ApplyWALToManifest(const ReadOptions& read_options,
|
||||
read_options, write_options, synced_wals, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!status.ok() && versions_->io_status().IsIOError()) {
|
||||
status = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -2064,19 +2069,19 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
read_options, super_version->GetSeqnoToTimeMapping(), arena);
|
||||
Status s;
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
TruncatedRangeDelIterator* mem_tombstone_iter = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
|
||||
auto range_del_iter = super_version->mem->NewRangeTombstoneIterator(
|
||||
read_options, sequence, false /* immutable_memtable */);
|
||||
if (range_del_iter == nullptr || range_del_iter->empty()) {
|
||||
delete range_del_iter;
|
||||
} else {
|
||||
mem_tombstone_iter = new TruncatedRangeDelIterator(
|
||||
mem_tombstone_iter = std::make_unique<TruncatedRangeDelIterator>(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&cfd->ioptions()->internal_comparator, nullptr /* smallest */,
|
||||
nullptr /* largest */);
|
||||
}
|
||||
merge_iter_builder.AddPointAndTombstoneIterator(mem_iter,
|
||||
mem_tombstone_iter);
|
||||
merge_iter_builder.AddPointAndTombstoneIterator(
|
||||
mem_iter, std::move(mem_tombstone_iter));
|
||||
} else {
|
||||
merge_iter_builder.AddIterator(mem_iter);
|
||||
}
|
||||
@@ -2475,7 +2480,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
}
|
||||
if (!done && !s.ok() && !s.IsMergeInProgress()) {
|
||||
if (!s.ok() && !s.IsMergeInProgress() && !s.IsNotFound()) {
|
||||
assert(done);
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
return s;
|
||||
}
|
||||
@@ -3141,10 +3147,11 @@ Status DBImpl::MultiGetImpl(
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
assert(sorted_keys);
|
||||
assert(start_key + num_keys <= sorted_keys->size());
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
for (auto* kctx : *sorted_keys) {
|
||||
assert(kctx);
|
||||
for (size_t i = start_key; i < start_key + num_keys; ++i) {
|
||||
KeyContext* kctx = (*sorted_keys)[i];
|
||||
if (kctx->timestamp) {
|
||||
kctx->timestamp->clear();
|
||||
}
|
||||
@@ -3207,6 +3214,8 @@ Status DBImpl::MultiGetImpl(
|
||||
s = Status::Aborted();
|
||||
break;
|
||||
}
|
||||
// This could be a long-running operation
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
@@ -5240,6 +5249,14 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
Env* env = soptions.env;
|
||||
std::vector<std::string> filenames;
|
||||
bool wal_in_db_path = soptions.IsWalDirSameAsDBPath();
|
||||
auto sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
options.sst_file_manager.get());
|
||||
// Allocate a separate trash bucket to be used by all the to be deleted
|
||||
// files, so we can later wait for this bucket to be empty before return.
|
||||
std::optional<int32_t> bucket;
|
||||
if (sfm) {
|
||||
bucket = sfm->NewTrashBucket();
|
||||
}
|
||||
|
||||
// Reset the logger because it holds a handle to the
|
||||
// log file and prevents cleanup and directory removal
|
||||
@@ -5251,6 +5268,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
/*IODebugContext*=*/nullptr)
|
||||
.PermitUncheckedError();
|
||||
|
||||
std::set<std::string> paths_to_delete;
|
||||
FileLock* lock;
|
||||
const std::string lockname = LockFileName(dbname);
|
||||
Status result = env->LockFile(lockname, &lock);
|
||||
@@ -5267,10 +5285,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
del = DestroyDB(path_to_delete, options);
|
||||
} else if (type == kTableFile || type == kWalFile ||
|
||||
type == kBlobFile) {
|
||||
del = DeleteDBFile(
|
||||
&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/(type == kWalFile) ? !wal_in_db_path : false);
|
||||
del = DeleteUnaccountedDBFile(&soptions, path_to_delete, dbname,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/false, bucket);
|
||||
} else {
|
||||
del = env->DeleteFile(path_to_delete);
|
||||
}
|
||||
@@ -5279,6 +5296,7 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
}
|
||||
}
|
||||
}
|
||||
paths_to_delete.insert(dbname);
|
||||
|
||||
std::set<std::string> paths;
|
||||
for (const DbPath& db_path : options.db_paths) {
|
||||
@@ -5300,18 +5318,19 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
(type == kTableFile ||
|
||||
type == kBlobFile)) { // Lock file will be deleted at end
|
||||
std::string file_path = path + "/" + fname;
|
||||
Status del = DeleteDBFile(&soptions, file_path, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/false);
|
||||
Status del = DeleteUnaccountedDBFile(&soptions, file_path, dbname,
|
||||
/*force_bg=*/false,
|
||||
/*force_fg=*/false, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Should we return an error if we cannot delete the directory?
|
||||
env->DeleteDir(path).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
paths_to_delete.merge(paths);
|
||||
|
||||
std::vector<std::string> walDirFiles;
|
||||
std::string archivedir = ArchivalDirectory(dbname);
|
||||
bool wal_dir_exists = false;
|
||||
@@ -5335,46 +5354,49 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
// Delete archival files.
|
||||
for (const auto& file : archiveFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, archivedir + "/" + file, archivedir,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path);
|
||||
Status del = DeleteUnaccountedDBFile(
|
||||
&soptions, archivedir + "/" + file, archivedir,
|
||||
/*force_bg=*/false, /*force_fg=*/!wal_in_db_path, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ignore error in case dir contains other files
|
||||
env->DeleteDir(archivedir).PermitUncheckedError();
|
||||
paths_to_delete.insert(archivedir);
|
||||
}
|
||||
|
||||
// Delete log files in the WAL dir
|
||||
if (wal_dir_exists) {
|
||||
for (const auto& file : walDirFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kWalFile) {
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number),
|
||||
soptions.wal_dir, /*force_bg=*/false,
|
||||
/*force_fg=*/!wal_in_db_path);
|
||||
Status del = DeleteUnaccountedDBFile(
|
||||
&soptions, LogFileName(soptions.wal_dir, number),
|
||||
soptions.wal_dir, /*force_bg=*/false,
|
||||
/*force_fg=*/!wal_in_db_path, bucket);
|
||||
if (!del.ok() && result.ok()) {
|
||||
result = del;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ignore error in case dir contains other files
|
||||
env->DeleteDir(soptions.wal_dir).PermitUncheckedError();
|
||||
paths_to_delete.insert(soptions.wal_dir);
|
||||
}
|
||||
|
||||
// Ignore error since state is already gone
|
||||
env->UnlockFile(lock).PermitUncheckedError();
|
||||
env->DeleteFile(lockname).PermitUncheckedError();
|
||||
|
||||
// Make sure trash files are all cleared before return.
|
||||
if (sfm && bucket.has_value()) {
|
||||
sfm->WaitForEmptyTrashBucket(bucket.value());
|
||||
}
|
||||
// sst_file_manager holds a ref to the logger. Make sure the logger is
|
||||
// gone before trying to remove the directory.
|
||||
soptions.sst_file_manager.reset();
|
||||
|
||||
// Ignore error in case dir contains other files
|
||||
env->DeleteDir(dbname).PermitUncheckedError();
|
||||
;
|
||||
for (const auto& path_to_delete : paths_to_delete) {
|
||||
env->DeleteDir(path_to_delete).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -5814,6 +5836,18 @@ Status DBImpl::IngestExternalFiles(
|
||||
"timestamps enabled doesn't support ingest behind.");
|
||||
}
|
||||
}
|
||||
if (ingest_opts.allow_db_generated_files) {
|
||||
if (ingest_opts.write_global_seqno) {
|
||||
return Status::NotSupported(
|
||||
"write_global_seqno is deprecated and does not work with "
|
||||
"allow_db_generated_files.");
|
||||
}
|
||||
if (ingest_opts.move_files) {
|
||||
return Status::NotSupported(
|
||||
"Options move_files and allow_db_generated_files are not "
|
||||
"compatible.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (yanqin) maybe handle the case in which column_families have
|
||||
|
||||
+19
-2
@@ -508,6 +508,8 @@ class DBImpl : public DB {
|
||||
Status GetLiveFiles(std::vector<std::string>&, uint64_t* manifest_file_size,
|
||||
bool flush_memtable = true) override;
|
||||
Status GetSortedWalFiles(VectorWalPtr& files) override;
|
||||
Status GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos);
|
||||
|
||||
// Get the known flushed sizes of WALs that might still be written to
|
||||
// or have pending sync.
|
||||
// NOTE: unlike alive_log_files_, this function includes WALs that might
|
||||
@@ -1224,6 +1226,8 @@ class DBImpl : public DB {
|
||||
return logs_.back().number;
|
||||
}
|
||||
|
||||
void TEST_DeleteObsoleteFiles();
|
||||
|
||||
const std::unordered_set<uint64_t>& TEST_GetFilesGrabbedForPurge() const {
|
||||
return files_grabbed_for_purge_;
|
||||
}
|
||||
@@ -1233,9 +1237,14 @@ class DBImpl : public DB {
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// In certain configurations, verify that the table/blob file cache only
|
||||
// contains entries for live files, to check for effective leaks of open
|
||||
// files. This can only be called when purging of obsolete files has
|
||||
// "settled," such as during parts of DB Close().
|
||||
void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
@@ -1886,6 +1895,10 @@ class DBImpl : public DB {
|
||||
Env::Priority compaction_pri_;
|
||||
};
|
||||
|
||||
static bool IsRecoveryFlush(FlushReason flush_reason) {
|
||||
return flush_reason == FlushReason::kErrorRecoveryRetryFlush ||
|
||||
flush_reason == FlushReason::kErrorRecovery;
|
||||
}
|
||||
// Initialize the built-in column family for persistent stats. Depending on
|
||||
// whether on-disk persistent stats have been enabled before, it may either
|
||||
// create a new column family and column family handle or just a column family
|
||||
@@ -2030,7 +2043,8 @@ class DBImpl : public DB {
|
||||
// REQUIRES: mutex held
|
||||
void SelectColumnFamiliesForAtomicFlush(
|
||||
autovector<ColumnFamilyData*>* selected_cfds,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {});
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds = {},
|
||||
FlushReason flush_reason = FlushReason::kOthers);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
@@ -2346,6 +2360,9 @@ class DBImpl : public DB {
|
||||
bool HaveManualCompaction(ColumnFamilyData* cfd);
|
||||
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
|
||||
void UpdateDeletionCompactionStats(const std::unique_ptr<Compaction>& c);
|
||||
|
||||
// May open and read table files for table property.
|
||||
// Should not be called while holding mutex_.
|
||||
void BuildCompactionJobInfo(const ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& compaction_job_stats,
|
||||
|
||||
@@ -1595,10 +1595,12 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
mutex_.Unlock();
|
||||
if (compaction_job_info != nullptr) {
|
||||
BuildCompactionJobInfo(cfd, c.get(), s, compaction_job_stats,
|
||||
job_context->job_id, compaction_job_info);
|
||||
}
|
||||
mutex_.Lock();
|
||||
|
||||
if (status.ok()) {
|
||||
// Done
|
||||
@@ -2304,13 +2306,15 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load()) {
|
||||
if (!cfd->mem()->IsEmpty() || !cached_recoverable_state_empty_.load() ||
|
||||
IsRecoveryFlush(flush_reason)) {
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
}
|
||||
const uint64_t flush_memtable_id = std::numeric_limits<uint64_t>::max();
|
||||
if (s.ok()) {
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
!cached_recoverable_state_empty_.load() ||
|
||||
IsRecoveryFlush(flush_reason)) {
|
||||
FlushRequest req{flush_reason, {{cfd, flush_memtable_id}}};
|
||||
flush_reqs.emplace_back(std::move(req));
|
||||
memtable_ids_to_wait.emplace_back(
|
||||
@@ -2487,7 +2491,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
WaitForPendingWrites();
|
||||
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds, candidate_cfds);
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds, candidate_cfds, flush_reason);
|
||||
|
||||
// Unref the newly generated candidate cfds (when not provided) in
|
||||
// `candidate_cfds`
|
||||
@@ -2498,7 +2502,8 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
|
||||
for (auto cfd : cfds) {
|
||||
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load()) {
|
||||
if (cfd->mem()->IsEmpty() && cached_recoverable_state_empty_.load() &&
|
||||
!IsRecoveryFlush(flush_reason)) {
|
||||
continue;
|
||||
}
|
||||
cfd->Ref();
|
||||
@@ -4161,7 +4166,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
compaction_job_info->base_input_level = c->start_level();
|
||||
compaction_job_info->output_level = c->output_level();
|
||||
compaction_job_info->stats = compaction_job_stats;
|
||||
const auto& input_table_properties = c->GetInputTableProperties();
|
||||
const auto& input_table_properties = c->GetOrInitInputTableProperties();
|
||||
const auto& output_table_properties = c->GetOutputTableProperties();
|
||||
compaction_job_info->table_properties.insert(input_table_properties.begin(),
|
||||
input_table_properties.end());
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -314,9 +315,58 @@ const autovector<uint64_t>& DBImpl::TEST_GetFilesToQuarantine() const {
|
||||
return error_handler_.GetFilesToQuarantine();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_DeleteObsoleteFiles() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
DeleteObsoleteFiles();
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
bool db_mutex_already_held) const {
|
||||
// This check is somewhat expensive and obscure to make a part of every
|
||||
// unit test in every build variety. Thus, we only enable it for ASAN builds.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<InstrumentedMutexLock> l;
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
} else {
|
||||
l.emplace(&mutex_);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> live_files;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// Sneakily add both SST and blob files to the same list
|
||||
cfd->current()->AddLiveFiles(&live_files, &live_files);
|
||||
}
|
||||
std::sort(live_files.begin(), live_files.end());
|
||||
|
||||
auto fn = [&live_files](const Slice& key, Cache::ObjectPtr, size_t,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (helper != BlobFileCache::GetHelper()) {
|
||||
// Skip non-blob files for now
|
||||
// FIXME: diagnose and fix the leaks of obsolete SST files revealed in
|
||||
// unit tests.
|
||||
return;
|
||||
}
|
||||
// See TableCache and BlobFileCache
|
||||
assert(key.size() == sizeof(uint64_t));
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in sorted live_files
|
||||
assert(
|
||||
std::binary_search(live_files.begin(), live_files.end(), file_number));
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // NDEBUG
|
||||
@@ -970,7 +970,9 @@ Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
}
|
||||
// Persist it to IDENTITY file if allowed
|
||||
if (!read_only) {
|
||||
s = SetIdentityFile(write_options, env_, dbname_, db_id_);
|
||||
s = SetIdentityFile(write_options, env_, dbname_,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
db_id_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,8 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
VersionEdit new_db;
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
Status s = SetIdentityFile(write_options, env_, dbname_);
|
||||
Status s = SetIdentityFile(write_options, env_, dbname_,
|
||||
immutable_db_options_.metadata_write_temperature);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -319,6 +320,12 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.metadata_write_temperature !=
|
||||
Temperature::kUnknown) {
|
||||
file_options.temperature =
|
||||
immutable_db_options_.metadata_write_temperature;
|
||||
}
|
||||
s = NewWritableFile(fs_.get(), manifest, &file, file_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -344,6 +351,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
if (s.ok()) {
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(write_options, fs_.get(), dbname_, 1,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
directories_.GetDbDir());
|
||||
if (new_filenames) {
|
||||
new_filenames->emplace_back(
|
||||
@@ -530,6 +538,12 @@ Status DBImpl::Recover(
|
||||
/*no_error_if_files_missing=*/false, is_retry,
|
||||
&desc_status);
|
||||
desc_status.PermitUncheckedError();
|
||||
if (is_retry) {
|
||||
RecordTick(stats_, FILE_READ_CORRUPTION_RETRY_COUNT);
|
||||
if (desc_status.ok()) {
|
||||
RecordTick(stats_, FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT);
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -640,7 +654,7 @@ Status DBImpl::Recover(
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction,
|
||||
f->temperature, // this can be different from
|
||||
// `last_level_temperature`
|
||||
// `last_level_temperature`
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
@@ -1930,6 +1944,10 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
FileOptions opt_file_options =
|
||||
fs_->OptimizeForLogWrite(file_options_, db_options);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.wal_write_temperature != Temperature::kUnknown) {
|
||||
opt_file_options.temperature = immutable_db_options_.wal_write_temperature;
|
||||
}
|
||||
std::string wal_dir = immutable_db_options_.GetWalDir();
|
||||
std::string log_fname = LogFileName(wal_dir, log_file_num);
|
||||
|
||||
|
||||
+52
-48
@@ -556,6 +556,35 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
|
||||
if (log_context.need_log_sync) {
|
||||
VersionEdit synced_wals;
|
||||
log_write_mutex_.Lock();
|
||||
if (status.ok()) {
|
||||
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
|
||||
&synced_wals);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
status = ApplyWALToManifest(read_options, write_options, &synced_wals);
|
||||
}
|
||||
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hence provide a simple implementation that is not necessarily
|
||||
// efficient.
|
||||
if (status.ok() && two_write_queues_) {
|
||||
if (manual_wal_flush_) {
|
||||
status = FlushWAL(true);
|
||||
} else {
|
||||
status = SyncWAL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreReleaseCallback is called after WAL write and before memtable write
|
||||
if (status.ok()) {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
@@ -639,34 +668,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
assert(pre_release_cb_status.ok());
|
||||
}
|
||||
|
||||
if (log_context.need_log_sync) {
|
||||
VersionEdit synced_wals;
|
||||
log_write_mutex_.Lock();
|
||||
if (status.ok()) {
|
||||
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
|
||||
&synced_wals);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
status = ApplyWALToManifest(read_options, write_options, &synced_wals);
|
||||
}
|
||||
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hence provide a simple implementation that is not necessarily efficient.
|
||||
if (two_write_queues_) {
|
||||
if (manual_wal_flush_) {
|
||||
status = FlushWAL(true);
|
||||
} else {
|
||||
status = SyncWAL();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool should_exit_batch_group = true;
|
||||
if (in_parallel_group) {
|
||||
// CompleteParallelWorker returns true if this thread should
|
||||
@@ -968,21 +969,17 @@ Status DBImpl::WriteImplWALOnly(
|
||||
assert(w.state == WriteThread::STATE_GROUP_LEADER);
|
||||
|
||||
if (publish_last_seq == kDoPublishLastSeq) {
|
||||
Status status;
|
||||
|
||||
// Currently we only use kDoPublishLastSeq in unordered_write
|
||||
assert(immutable_db_options_.unordered_write);
|
||||
WriteContext write_context;
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
// TODO(myabandeh): Make preliminary checks thread-safe so we could do them
|
||||
// without paying the cost of obtaining the mutex.
|
||||
if (status.ok()) {
|
||||
LogContext log_context;
|
||||
status = PreprocessWrite(write_options, &log_context, &write_context);
|
||||
WriteStatusCheckOnLocked(status);
|
||||
}
|
||||
LogContext log_context;
|
||||
WriteContext write_context;
|
||||
Status status =
|
||||
PreprocessWrite(write_options, &log_context, &write_context);
|
||||
WriteStatusCheckOnLocked(status);
|
||||
|
||||
if (!status.ok()) {
|
||||
WriteThread::WriteGroup write_group;
|
||||
write_thread->EnterAsBatchGroupLeader(&w, &write_group);
|
||||
@@ -1204,8 +1201,7 @@ void DBImpl::MemTableInsertStatusCheck(const Status& status) {
|
||||
mutex_.Lock();
|
||||
assert(!error_handler_.IsBGWorkStopped());
|
||||
// Maybe change the return status to void?
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable)
|
||||
.PermitUncheckedError();
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
@@ -1411,6 +1407,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
total_log_size_ += log_entry.size();
|
||||
log_file_number_size.AddSize(*log_size);
|
||||
log_empty_ = false;
|
||||
|
||||
return io_s;
|
||||
}
|
||||
|
||||
@@ -1483,9 +1480,14 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
io_s = log.writer->file()->Sync(opts, immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
// If last sync failed on a later WAL, this could be a fully synced
|
||||
// and closed WAL that just needs to be recorded as synced in the
|
||||
// manifest.
|
||||
if (auto* f = log.writer->file()) {
|
||||
io_s = f->Sync(opts, immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1650,7 +1652,8 @@ Status DBImpl::WriteRecoverableState() {
|
||||
|
||||
void DBImpl::SelectColumnFamiliesForAtomicFlush(
|
||||
autovector<ColumnFamilyData*>* selected_cfds,
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds) {
|
||||
const autovector<ColumnFamilyData*>& provided_candidate_cfds,
|
||||
FlushReason flush_reason) {
|
||||
mutex_.AssertHeld();
|
||||
assert(selected_cfds);
|
||||
|
||||
@@ -1673,7 +1676,8 @@ void DBImpl::SelectColumnFamiliesForAtomicFlush(
|
||||
continue;
|
||||
}
|
||||
if (cfd->imm()->NumNotFlushed() != 0 || !cfd->mem()->IsEmpty() ||
|
||||
!cached_recoverable_state_empty_.load()) {
|
||||
!cached_recoverable_state_empty_.load() ||
|
||||
IsRecoveryFlush(flush_reason)) {
|
||||
selected_cfds->push_back(cfd);
|
||||
}
|
||||
}
|
||||
@@ -2363,8 +2367,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
read_options, write_options, &wal_deletion, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
if (!s.ok() && versions_->io_status().IsIOError()) {
|
||||
s = error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -705,6 +705,7 @@ class DBIOCorruptionTest
|
||||
DBIOCorruptionTest() : DBIOFailureTest() {
|
||||
BlockBasedTableOptions bbto;
|
||||
options_ = CurrentOptions();
|
||||
options_.statistics = CreateDBStatistics();
|
||||
|
||||
base_env_ = env_;
|
||||
EXPECT_NE(base_env_, nullptr);
|
||||
@@ -727,6 +728,8 @@ class DBIOCorruptionTest
|
||||
|
||||
Status ReopenDB() { return TryReopen(options_); }
|
||||
|
||||
Statistics* stats() { return options_.statistics.get(); }
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Env> env_guard_;
|
||||
std::shared_ptr<CorruptionFS> fs_;
|
||||
@@ -749,8 +752,12 @@ TEST_P(DBIOCorruptionTest, GetReadCorruptionRetry) {
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(val, "val1");
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,8 +780,12 @@ TEST_P(DBIOCorruptionTest, IterReadCorruptionRetry) {
|
||||
}
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
@@ -799,9 +810,13 @@ TEST_P(DBIOCorruptionTest, MultiGetReadCorruptionRetry) {
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_EQ(values[0].ToString(), "val1");
|
||||
ASSERT_EQ(values[1].ToString(), "val2");
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_TRUE(statuses[1].IsCorruption());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,6 +833,9 @@ TEST_P(DBIOCorruptionTest, CompactionReadCorruptionRetry) {
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
@@ -826,6 +844,7 @@ TEST_P(DBIOCorruptionTest, CompactionReadCorruptionRetry) {
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,6 +857,9 @@ TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
@@ -846,6 +868,7 @@ TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -862,12 +885,91 @@ TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(ReopenDB());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_EQ(ReopenDB(), Status::Corruption());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, FooterReadCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadFooterFromFileInternal:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
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);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
ASSERT_GT(stats()->getTickerCount(SST_FOOTER_CORRUPTION_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, TablePropertiesCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadTablePropertiesHelper:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
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);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -538,6 +538,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
|
||||
@@ -684,13 +684,14 @@ class DbMemtableKVChecksumTest : public DbKvChecksumTest {
|
||||
DbMemtableKVChecksumTest() : DbKvChecksumTest() {}
|
||||
|
||||
protected:
|
||||
const size_t kValueLenOffset = 12;
|
||||
// Indices in the memtable entry that we will not corrupt.
|
||||
// For memtable entry format, see comments in MemTable::Add().
|
||||
// We do not corrupt key length and value length fields in this test
|
||||
// case since it causes segfault and ASAN will complain.
|
||||
// For this test case, key and value are all of length 3, so
|
||||
// key length field is at index 0 and value length field is at index 12.
|
||||
const std::set<size_t> index_not_to_corrupt{0, 12};
|
||||
const std::set<size_t> index_not_to_corrupt{0, kValueLenOffset};
|
||||
|
||||
void SkipNotToCorruptEntry() {
|
||||
if (index_not_to_corrupt.find(corrupt_byte_offset_) !=
|
||||
@@ -737,6 +738,8 @@ TEST_P(DbMemtableKVChecksumTest, GetWithCorruptAfterMemtableInsert) {
|
||||
buf[corrupt_byte_offset_] += corrupt_byte_addend_;
|
||||
++corrupt_byte_offset_;
|
||||
});
|
||||
// Corrupt value only so that MultiGet below can find the key.
|
||||
corrupt_byte_offset_ = kValueLenOffset + 1;
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options = CurrentOptions();
|
||||
options.memtable_protection_bytes_per_key =
|
||||
@@ -745,12 +748,17 @@ TEST_P(DbMemtableKVChecksumTest, GetWithCorruptAfterMemtableInsert) {
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
}
|
||||
|
||||
std::string key = "key";
|
||||
SkipNotToCorruptEntry();
|
||||
while (MoreBytesToCorrupt()) {
|
||||
Reopen(options);
|
||||
ASSERT_OK(ExecuteWrite(nullptr));
|
||||
std::string val;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "key", &val).IsCorruption());
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), key, &val).IsCorruption());
|
||||
std::vector<std::string> vals = {val};
|
||||
std::vector<Status> statuses = db_->MultiGet(
|
||||
ReadOptions(), {db_->DefaultColumnFamily()}, {key}, &vals, nullptr);
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
Destroy(options);
|
||||
SkipNotToCorruptEntry();
|
||||
}
|
||||
|
||||
@@ -339,6 +339,91 @@ TEST_F(DBMemTableTest, ColumnFamilyId) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, IntegrityChecks) {
|
||||
// We insert keys key000000, key000001 and key000002 into skiplist at fixed
|
||||
// height 1 (smallest height). Then we corrupt the second key to aey000001 to
|
||||
// make it smaller. With `paranoid_memory_checks` set to true, if the
|
||||
// skip list sees key000000 and then aey000001, then it will report out of
|
||||
// order keys with corruption status. With `paranoid_memory_checks` set
|
||||
// to false, read/scan may return wrong results.
|
||||
for (bool allow_data_in_error : {false, true}) {
|
||||
Options options = CurrentOptions();
|
||||
options.allow_data_in_errors = allow_data_in_error;
|
||||
options.paranoid_memory_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"InlineSkipList::RandomHeight::height", [](void* h) {
|
||||
auto height_ptr = static_cast<int*>(h);
|
||||
*height_ptr = 1;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(Key(0), "val0"));
|
||||
ASSERT_OK(Put(Key(2), "val2"));
|
||||
// p will point to the buffer for encoded key000001
|
||||
char* p = nullptr;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MemTable::Add:BeforeReturn:Encoded", [&](void* encoded) {
|
||||
p = const_cast<char*>(static_cast<Slice*>(encoded)->data());
|
||||
});
|
||||
ASSERT_OK(Put(Key(1), "val1"));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_TRUE(p);
|
||||
// Offset 0 is key size, key bytes start at offset 1.
|
||||
// "key000001 -> aey000001"
|
||||
p[1] = 'a';
|
||||
|
||||
ReadOptions rops;
|
||||
std::string val;
|
||||
Status s = db_->Get(rops, Key(1), &val);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
std::string key0 = Slice(Key(0)).ToString(true);
|
||||
ASSERT_EQ(s.ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
// Without `paranoid_memory_checks`, NotFound will be returned.
|
||||
// This would fail an assertion in InlineSkipList::FindGreaterOrEqual().
|
||||
// If we remove the assertion, this passes.
|
||||
// ASSERT_TRUE(db_->Get(ReadOptions(), Key(1), &val).IsNotFound());
|
||||
|
||||
std::vector<std::string> vals;
|
||||
std::vector<Status> statuses = db_->MultiGet(
|
||||
rops, {db_->DefaultColumnFamily()}, {Key(1)}, &vals, nullptr);
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_EQ(statuses[0].ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Seek(Key(1));
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
ASSERT_EQ(iter->status().ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
|
||||
iter->Seek(Key(0));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
// iterating through skip list at height at 1 should catch out-of-order keys
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
ASSERT_EQ(iter->status().ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
|
||||
iter->SeekForPrev(Key(2));
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
ASSERT_EQ(iter->status().ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
|
||||
// Internally DB Iter will iterate backwards (call Prev()) after
|
||||
// SeekToLast() to find the correct internal key with the last user key.
|
||||
// Prev() will do integrity checks and catch corruption.
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
ASSERT_EQ(iter->status().ToString().find(key0) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -245,7 +245,13 @@ TEST_P(DBRateLimiterOnReadTest, VerifyChecksum) {
|
||||
// In DirectIO, where we support tail prefetching, during table open, we only
|
||||
// do 1 read instead of 4 as described above. Actual checksum verification
|
||||
// reads stay the same.
|
||||
#ifdef OS_WIN
|
||||
// No file system prefetch implemented for OS Win. During table open,
|
||||
// we only do 1 read for BufferedIO.
|
||||
int num_read_per_file = 4;
|
||||
#else
|
||||
int num_read_per_file = (!use_direct_io_) ? 7 : 4;
|
||||
#endif
|
||||
int expected = kNumFiles * num_read_per_file;
|
||||
|
||||
ASSERT_EQ(expected, options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
|
||||
+55
-2
@@ -507,6 +507,23 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
ASSERT_EQ(files_deleted, 0);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 0);
|
||||
Close();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
files_deleted++;
|
||||
}
|
||||
});
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_EQ(files_deleted, blob_files.size());
|
||||
ASSERT_EQ(files_scheduled_to_delete, blob_files.size());
|
||||
@@ -649,6 +666,23 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
}
|
||||
|
||||
Close();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
files_deleted++;
|
||||
}
|
||||
});
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(files_deleted, 5);
|
||||
@@ -883,8 +917,9 @@ TEST_P(DBWALTestWithParam, WALTrashCleanupOnOpen) {
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
if (v == 'c') {
|
||||
// Maximize the change that the last log file will be preserved in trash
|
||||
// before restarting the DB.
|
||||
// Maximize the chance that the last log file will be preserved in trash
|
||||
// before restarting the DB. (Enable slow deletion but at a very slow
|
||||
// deletion rate)
|
||||
// We have to set this on the 2nd to last file for it to delay deletion
|
||||
// on the last file. (Quirk of DeleteScheduler::BackgroundEmptyTrash())
|
||||
options.sst_file_manager->SetDeleteRateBytesPerSecond(1);
|
||||
@@ -1902,6 +1937,24 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
|
||||
ASSERT_EQ(files_deleted, 1);
|
||||
|
||||
Close();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::OnDeleteFile", [&](void* arg) {
|
||||
const std::string* const file_path =
|
||||
static_cast<const std::string*>(arg);
|
||||
if (EndsWith(*file_path, ".blob")) {
|
||||
files_deleted++;
|
||||
}
|
||||
});
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
|
||||
ASSERT_EQ(files_scheduled_to_delete, 4);
|
||||
|
||||
+231
@@ -10,6 +10,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -6544,6 +6546,235 @@ TEST_P(RenameCurrentTest, Compaction) {
|
||||
ASSERT_EQ("d_value", Get("d"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
constexpr size_t kNumberFileTypes = static_cast<size_t>(kBlobFile) + 1U;
|
||||
|
||||
struct MyTestFS : public FileTemperatureTestFS {
|
||||
explicit MyTestFS(const std::shared_ptr<FileSystem>& fs)
|
||||
: FileTemperatureTestFS(fs) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios =
|
||||
FileTemperatureTestFS::NewWritableFile(fname, opts, result, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(GetFileName(fname), &number, "LOG", &type)) {
|
||||
if (type == kTableFile) {
|
||||
// Not checked here
|
||||
} else if (type == kWalFile) {
|
||||
if (opts.temperature != expected_wal_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (type == kDescriptorFile) {
|
||||
if (opts.temperature != expected_manifest_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (opts.temperature != expected_other_metadata_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
UpdateCount(type, 1);
|
||||
}
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
IOStatus RenameFile(const std::string& src, const std::string& dst,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios = FileTemperatureTestFS::RenameFile(src, dst, options, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType src_type;
|
||||
FileType dst_type;
|
||||
assert(ParseFileName(GetFileName(src), &number, "LOG", &src_type));
|
||||
assert(ParseFileName(GetFileName(dst), &number, "LOG", &dst_type));
|
||||
|
||||
UpdateCount(src_type, -1);
|
||||
UpdateCount(dst_type, 1);
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
void UpdateCount(FileType type, int delta) {
|
||||
size_t i = static_cast<size_t>(type);
|
||||
assert(i < kNumberFileTypes);
|
||||
counts[i].FetchAddRelaxed(delta);
|
||||
}
|
||||
|
||||
std::map<FileType, size_t> PopCounts() {
|
||||
std::map<FileType, size_t> ret;
|
||||
for (size_t i = 0; i < kNumberFileTypes; ++i) {
|
||||
int c = counts[i].ExchangeRelaxed(0);
|
||||
if (c > 0) {
|
||||
ret[static_cast<FileType>(i)] = c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogWrite(
|
||||
const FileOptions& file_options,
|
||||
const DBOptions& /*db_options*/) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_wal_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_wal_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForManifestWrite(
|
||||
const FileOptions& file_options) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_manifest_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_manifest_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
optimize_manifest_temperature = Temperature::kUnknown;
|
||||
optimize_wal_temperature = Temperature::kUnknown;
|
||||
expected_manifest_temperature = Temperature::kUnknown;
|
||||
expected_other_metadata_temperature = Temperature::kUnknown;
|
||||
expected_wal_temperature = Temperature::kUnknown;
|
||||
for (auto& c : counts) {
|
||||
c.StoreRelaxed(0);
|
||||
}
|
||||
}
|
||||
|
||||
Temperature optimize_manifest_temperature;
|
||||
Temperature optimize_wal_temperature;
|
||||
Temperature expected_manifest_temperature;
|
||||
Temperature expected_other_metadata_temperature;
|
||||
Temperature expected_wal_temperature;
|
||||
std::array<RelaxedAtomic<int>, kNumberFileTypes> counts;
|
||||
};
|
||||
|
||||
// We don't have enough non-unknown temps to confidently distinguish that
|
||||
// a specific setting caused a specific outcome, in a single run. This is a
|
||||
// reasonable work-around without blowing up test time. Only returns
|
||||
// non-unknown temperatures.
|
||||
auto RandomTemp = [] {
|
||||
static std::vector<Temperature> temps = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCold};
|
||||
return temps[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(temps.size()))];
|
||||
};
|
||||
|
||||
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
for (bool use_optimize : {false, true}) {
|
||||
std::cerr << "use_optimize: " << std::to_string(use_optimize) << std::endl;
|
||||
for (bool use_temp_options : {false, true}) {
|
||||
std::cerr << "use_temp_options: " << std::to_string(use_temp_options)
|
||||
<< std::endl;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
// Currently require for last level temperature
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = env.get();
|
||||
test_fs->Reset();
|
||||
if (use_optimize) {
|
||||
test_fs->optimize_manifest_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
test_fs->optimize_manifest_temperature;
|
||||
test_fs->optimize_wal_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
|
||||
}
|
||||
if (use_temp_options) {
|
||||
options.metadata_write_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
options.metadata_write_temperature;
|
||||
test_fs->expected_other_metadata_temperature =
|
||||
options.metadata_write_temperature;
|
||||
options.wal_write_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = options.wal_write_temperature;
|
||||
options.last_level_temperature = RandomTemp();
|
||||
options.default_write_temperature = RandomTemp();
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Defer closer([&] { Close(); });
|
||||
|
||||
using FTC = std::map<FileType, size_t>;
|
||||
// Files on DB startup
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kDescriptorFile, 2},
|
||||
{kCurrentFile, 2},
|
||||
{kIdentityFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
// Temperature count map
|
||||
using TCM = std::map<Temperature, size_t>;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), TCM({}));
|
||||
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "2"));
|
||||
ASSERT_OK(Put("bar", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.default_write_temperature, 2}}));
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.last_level_temperature, 1}}));
|
||||
|
||||
ASSERT_OK(Put("foo", "3"));
|
||||
ASSERT_OK(Put("bar", "3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Just in memtable/WAL
|
||||
ASSERT_OK(Put("dog", "3"));
|
||||
|
||||
{
|
||||
TCM expected;
|
||||
expected[options.default_write_temperature] += 1;
|
||||
expected[options.last_level_temperature] += 1;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), expected);
|
||||
}
|
||||
|
||||
// New files during operation
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 3}, {kTableFile, 4}}));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// New files during re-open/recovery
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kTableFile, 1},
|
||||
{kDescriptorFile, 1},
|
||||
{kCurrentFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LastLevelTemperature) {
|
||||
class TestListener : public EventListener {
|
||||
public:
|
||||
|
||||
@@ -366,6 +366,11 @@ Options DBTestBase::GetOptions(
|
||||
table_options.block_cache = NewLRUCache(/* too small */ 1);
|
||||
}
|
||||
|
||||
// Test anticipated new default as much as reasonably possible (and remove
|
||||
// this code when obsolete)
|
||||
assert(!table_options.decouple_partitioned_filters);
|
||||
table_options.decouple_partitioned_filters = true;
|
||||
|
||||
bool can_allow_mmap = IsMemoryMappedAccessSupported();
|
||||
switch (option_config) {
|
||||
case kHashSkipList:
|
||||
@@ -565,6 +570,11 @@ Options DBTestBase::GetOptions(
|
||||
options.unordered_write = false;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithBinarySearchWithFirstKeyIndex: {
|
||||
table_options.index_type =
|
||||
BlockBasedTableOptions::kBinarySearchWithFirstKey;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
+11
-1
@@ -831,6 +831,15 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
return count;
|
||||
}
|
||||
|
||||
std::map<Temperature, size_t> CountCurrentSstFilesByTemp() {
|
||||
MutexLock lock(&mu_);
|
||||
std::map<Temperature, size_t> ret;
|
||||
for (const auto& e : current_sst_file_temperatures_) {
|
||||
ret[e.second]++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OverrideSstFileTemperature(uint64_t number, Temperature temp) {
|
||||
MutexLock lock(&mu_);
|
||||
current_sst_file_temperatures_[number] = temp;
|
||||
@@ -842,7 +851,7 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
requested_sst_file_temperatures_;
|
||||
std::map<uint64_t, Temperature> current_sst_file_temperatures_;
|
||||
|
||||
std::string GetFileName(const std::string& fname) {
|
||||
static std::string GetFileName(const std::string& fname) {
|
||||
auto filename = fname.substr(fname.find_last_of(kFilePathSeparator) + 1);
|
||||
// workaround only for Windows that the file path could contain both Windows
|
||||
// FilePathSeparator and '/'
|
||||
@@ -1041,6 +1050,7 @@ class DBTestBase : public testing::Test {
|
||||
kPartitionedFilterWithNewTableReaderForCompactions,
|
||||
kUniversalSubcompactions,
|
||||
kUnorderedWrite,
|
||||
kBlockBasedTableWithBinarySearchWithFirstKeyIndex,
|
||||
// This must be the last line
|
||||
kEnd,
|
||||
};
|
||||
|
||||
@@ -213,7 +213,6 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrigger) {
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
// trigger compaction if there are >= 4 files
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
KeepFilterFactory* filter = new KeepFilterFactory(true);
|
||||
|
||||
+175
-1
@@ -1472,6 +1472,126 @@ TEST_F(DBWALTest, SyncMultipleLogs) {
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, DISABLED_RecycleMultipleWalsCrash) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_write_buffer_number = 5;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.max_bgerror_resume_count = 0; // manual resume
|
||||
options.recycle_log_file_num = 3;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
|
||||
// Disable truncating recycled WALs to new size in posix env
|
||||
// (approximating a crash)
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixWritableFile::Close",
|
||||
[](void* arg) { *(static_cast<size_t*>(arg)) = 0; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Re-open with desired options
|
||||
DestroyAndReopen(options);
|
||||
Defer closer([this]() { Close(); });
|
||||
|
||||
// Ensure WAL recycling wasn't sanitized away
|
||||
ASSERT_EQ(db_->GetOptions().recycle_log_file_num,
|
||||
options.recycle_log_file_num);
|
||||
|
||||
// Prepare external files for later ingestion
|
||||
std::string sst_files_dir = dbname_ + "/sst_files/";
|
||||
ASSERT_OK(DestroyDir(env_, sst_files_dir));
|
||||
ASSERT_OK(env_->CreateDir(sst_files_dir));
|
||||
std::string external_file1 = sst_files_dir + "file1.sst";
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
ASSERT_OK(sst_file_writer.Open(external_file1));
|
||||
ASSERT_OK(sst_file_writer.Put("external1", "ex1"));
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file_info));
|
||||
}
|
||||
std::string external_file2 = sst_files_dir + "file2.sst";
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
ASSERT_OK(sst_file_writer.Open(external_file2));
|
||||
ASSERT_OK(sst_file_writer.Put("external2", "ex2"));
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file_info));
|
||||
}
|
||||
|
||||
// Populate some WALs to be recycled such that there will be extra data
|
||||
// from an old incarnation of the WAL on recovery
|
||||
ASSERT_OK(db_->PauseBackgroundWork());
|
||||
ASSERT_OK(Put("ignore1", Random::GetTLSInstance()->RandomString(500)));
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
|
||||
ASSERT_OK(Put("ignore2", Random::GetTLSInstance()->RandomString(500)));
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
|
||||
ASSERT_OK(db_->ContinueBackgroundWork());
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("ignore3", Random::GetTLSInstance()->RandomString(500)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Verify expected log files (still there for recycling)
|
||||
std::vector<FileAttributes> files;
|
||||
int log_count = 0;
|
||||
ASSERT_OK(options.env->GetChildrenFileAttributes(dbname_, &files));
|
||||
for (const auto& f : files) {
|
||||
if (EndsWith(f.name, ".log")) {
|
||||
EXPECT_GT(f.size_bytes, 500);
|
||||
++log_count;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(log_count, 3);
|
||||
|
||||
// (Re-used recipe) Generate two inactive WALs and one active WAL, with a
|
||||
// gap in sequence numbers to interfere with recovery
|
||||
ASSERT_OK(db_->PauseBackgroundWork());
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
|
||||
ASSERT_OK(Put("key2", "val2"));
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
|
||||
// Need a gap in sequence numbers, so e.g. ingest external file
|
||||
// with an open snapshot
|
||||
{
|
||||
ManagedSnapshot snapshot(db_);
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile({external_file1}, IngestExternalFileOptions()));
|
||||
}
|
||||
ASSERT_OK(Put("key3", "val3"));
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
// Need an SST file that is logically after that WAL, so that dropping WAL
|
||||
// data is not a valid point in time.
|
||||
{
|
||||
ManagedSnapshot snapshot(db_);
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile({external_file2}, IngestExternalFileOptions()));
|
||||
}
|
||||
|
||||
// Approximate a crash, with respect to recycled WAL data extending past
|
||||
// the end of the current WAL data (see SyncPoint callback above)
|
||||
Close();
|
||||
|
||||
// Verify recycled log files haven't been truncated
|
||||
files.clear();
|
||||
log_count = 0;
|
||||
ASSERT_OK(options.env->GetChildrenFileAttributes(dbname_, &files));
|
||||
for (const auto& f : files) {
|
||||
if (EndsWith(f.name, ".log")) {
|
||||
EXPECT_GT(f.size_bytes, 500);
|
||||
++log_count;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(log_count, 3);
|
||||
|
||||
// Verify no data loss after reopen.
|
||||
Reopen(options);
|
||||
EXPECT_EQ("val1", Get("key1"));
|
||||
EXPECT_EQ("val2", Get("key2")); // Passes because of adjacent seqnos
|
||||
EXPECT_EQ("ex1", Get("external1"));
|
||||
EXPECT_EQ("val3", Get("key3")); // <- ONLY FAILURE! (Not a point in time)
|
||||
EXPECT_EQ("ex2", Get("external2"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, SyncWalPartialFailure) {
|
||||
class MyTestFileSystem : public FileSystemWrapper {
|
||||
public:
|
||||
@@ -1532,7 +1652,7 @@ TEST_F(DBWALTest, SyncWalPartialFailure) {
|
||||
// * one inactive WAL, not synced, and
|
||||
// * one active WAL, not synced
|
||||
// with a single thread, to exercise as much logic as we reasonably can.
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->PauseBackgroundWork());
|
||||
ASSERT_OK(db_->PauseBackgroundWork());
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable());
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
@@ -2757,6 +2877,60 @@ TEST_F(DBWALTest, EmptyWalReopenTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoveryFlushSwitchWALOnEmptyMemtable) {
|
||||
Options options = CurrentOptions();
|
||||
auto fault_fs = std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
options.env = fault_fs_env.get();
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Make sure the memtable switch in recovery flush happened after test checks
|
||||
// the memtable is empty.
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWALTest.RecoveryFlushSwitchWALOnEmptyMemtable:"
|
||||
"AfterCheckMemtableEmpty",
|
||||
"RecoverFromRetryableBGIOError:BeforeStart"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kMetadataWrite, 7 /* seed*/, 1 /* one_in */,
|
||||
true /* retryable */, false /* has_data_loss*/);
|
||||
fault_fs->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
Status s = Put("k", "old_v", wo);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
// To verify the key is not in memtable nor SST
|
||||
ASSERT_TRUE(static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd()
|
||||
->mem()
|
||||
->IsEmpty());
|
||||
ASSERT_EQ("NOT_FOUND", Get("k"));
|
||||
TEST_SYNC_POINT(
|
||||
"DBWALTest.RecoveryFlushSwitchWALOnEmptyMemtable:"
|
||||
"AfterCheckMemtableEmpty");
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
fault_fs->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
|
||||
// Keep trying write until recovery of the previous IO error finishes
|
||||
while (!s.ok()) {
|
||||
options.env->SleepForMicroseconds(1000);
|
||||
s = Put("k", "new_v");
|
||||
}
|
||||
|
||||
// If recovery flush didn't switch WAL, we will end up having two duplicate
|
||||
// WAL entries with same seqno and same key that violate assertion during WAL
|
||||
// recovery and fail DB reopen
|
||||
options.avoid_flush_during_recovery = false;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ("new_v", Get("k"));
|
||||
Destroy(options);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -172,6 +172,70 @@ TEST_F(DBBasicTestWithTimestamp, MixedCfs) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetMultipleCfs) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Options options1 = CurrentOptions();
|
||||
options1.env = env_;
|
||||
options1.comparator = &test_cmp;
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
Status s = db_->CreateColumnFamily(options1, "data", &handle);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string ts = Timestamp(1, 0);
|
||||
WriteBatch wb(0, 0, 0, kTimestampSize);
|
||||
ASSERT_OK(wb.Put("a", "value"));
|
||||
ASSERT_OK(wb.Put(handle, "a", "value"));
|
||||
const auto ts_sz_func = [kTimestampSize](uint32_t /*cf_id*/) {
|
||||
return kTimestampSize;
|
||||
};
|
||||
ASSERT_OK(wb.UpdateTimestamps(ts, ts_sz_func));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &wb));
|
||||
|
||||
int num_keys = 2;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> expected_values;
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
keys.push_back("a");
|
||||
expected_values.push_back("value");
|
||||
}
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
handles.push_back(db_->DefaultColumnFamily());
|
||||
handles.push_back(handle);
|
||||
|
||||
{
|
||||
Slice read_ts_slice(ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
|
||||
std::vector<PinnableSlice> values;
|
||||
values.resize(num_keys);
|
||||
std::vector<Status> statuses;
|
||||
statuses.resize(num_keys);
|
||||
std::vector<std::string> timestamps;
|
||||
timestamps.resize(num_keys);
|
||||
|
||||
db_->MultiGet(read_opts, num_keys, handles.data(), keys.data(),
|
||||
values.data(), timestamps.data(), statuses.data());
|
||||
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(expected_values[i], values[i].ToString());
|
||||
ASSERT_EQ(ts, timestamps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
delete handle;
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
|
||||
+5
-6
@@ -330,17 +330,16 @@ inline Slice ExtractUserKey(const Slice& internal_key) {
|
||||
// output : <user_provided_key>
|
||||
inline Slice ExtractUserKeyAndStripTimestamp(const Slice& internal_key,
|
||||
size_t ts_sz) {
|
||||
Slice ret = internal_key;
|
||||
ret.remove_suffix(kNumInternalBytes + ts_sz);
|
||||
return ret;
|
||||
assert(internal_key.size() >= kNumInternalBytes + ts_sz);
|
||||
return Slice(internal_key.data(),
|
||||
internal_key.size() - (kNumInternalBytes + ts_sz));
|
||||
}
|
||||
|
||||
// input [user key]: <user_provided_key | ts>
|
||||
// output: <user_provided_key>
|
||||
inline Slice StripTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
|
||||
Slice ret = user_key;
|
||||
ret.remove_suffix(ts_sz);
|
||||
return ret;
|
||||
assert(user_key.size() >= ts_sz);
|
||||
return Slice(user_key.data(), user_key.size() - ts_sz);
|
||||
}
|
||||
|
||||
// input [user key]: <user_provided_key | ts>
|
||||
|
||||
+8
-8
@@ -23,17 +23,17 @@ static std::string IKey(const std::string& user_key, uint64_t seq,
|
||||
}
|
||||
|
||||
static std::string Shorten(const std::string& s, const std::string& l) {
|
||||
std::string result = s;
|
||||
ShortenedIndexBuilder::FindShortestInternalKeySeparator(*BytewiseComparator(),
|
||||
&result, l);
|
||||
return result;
|
||||
std::string scratch;
|
||||
return ShortenedIndexBuilder::FindShortestInternalKeySeparator(
|
||||
*BytewiseComparator(), s, l, &scratch)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
static std::string ShortSuccessor(const std::string& s) {
|
||||
std::string result = s;
|
||||
ShortenedIndexBuilder::FindShortInternalKeySuccessor(*BytewiseComparator(),
|
||||
&result);
|
||||
return result;
|
||||
std::string scratch;
|
||||
return ShortenedIndexBuilder::FindShortInternalKeySuccessor(
|
||||
*BytewiseComparator(), s, &scratch)
|
||||
.ToString();
|
||||
}
|
||||
|
||||
static void TestKey(const std::string& key, uint64_t seq, ValueType vt) {
|
||||
|
||||
+34
-39
@@ -250,8 +250,6 @@ void ErrorHandler::CancelErrorRecovery() {
|
||||
EndAutoRecovery();
|
||||
}
|
||||
|
||||
STATIC_AVOID_DESTRUCTION(const Status, kOkStatus){Status::OK()};
|
||||
|
||||
// This is the main function for looking at an error during a background
|
||||
// operation and deciding the severity, and error recovery strategy. The high
|
||||
// level algorithm is as follows -
|
||||
@@ -270,11 +268,11 @@ STATIC_AVOID_DESTRUCTION(const Status, kOkStatus){Status::OK()};
|
||||
// This can also get called as part of a recovery operation. In that case, we
|
||||
// also track the error separately in recovery_error_ so we can tell in the
|
||||
// end whether recovery succeeded or not
|
||||
const Status& ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason) {
|
||||
void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
if (bg_err.ok()) {
|
||||
return kOkStatus;
|
||||
return;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -339,7 +337,7 @@ const Status& ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
} else {
|
||||
// This error is less severe than previously encountered error. Don't
|
||||
// take any further action
|
||||
return bg_error_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +354,6 @@ const Status& ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
if (bg_error_.severity() >= Status::Severity::kHardError) {
|
||||
is_db_stopped_.store(true, std::memory_order_release);
|
||||
}
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
// This is the main function for looking at IO related error during the
|
||||
@@ -383,14 +380,14 @@ const Status& ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
// 3) for other cases, HandleKnownErrors(const Status& bg_err,
|
||||
// BackgroundErrorReason reason) will be called to handle other error cases
|
||||
// such as delegating to SstFileManager to handle no space error.
|
||||
const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
BackgroundErrorReason reason) {
|
||||
void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
Status tmp_status = bg_status;
|
||||
IOStatus bg_io_err = status_to_io_status(std::move(tmp_status));
|
||||
|
||||
if (bg_io_err.ok()) {
|
||||
return kOkStatus;
|
||||
return;
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s",
|
||||
bg_io_err.ToString().c_str());
|
||||
@@ -413,11 +410,11 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
recover_context_ = context;
|
||||
return bg_error_;
|
||||
} else if (bg_io_err.subcode() != IOStatus::SubCode::kNoSpace &&
|
||||
(bg_io_err.GetScope() ==
|
||||
IOStatus::IOErrorScope::kIOErrorScopeFile ||
|
||||
bg_io_err.GetRetryable())) {
|
||||
return;
|
||||
}
|
||||
if (bg_io_err.subcode() != IOStatus::SubCode::kNoSpace &&
|
||||
(bg_io_err.GetScope() == IOStatus::IOErrorScope::kIOErrorScopeFile ||
|
||||
bg_io_err.GetRetryable())) {
|
||||
// Second, check if the error is a retryable IO error (file scope IO error
|
||||
// is also treated as retryable IO error in RocksDB write path). if it is
|
||||
// retryable error and its severity is higher than bg_error_, overwrite the
|
||||
@@ -426,10 +423,6 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
// IO error as hard error. Note that, all the NoSpace error should be
|
||||
// handled by the SstFileManager::StartErrorRecovery(). Therefore, no matter
|
||||
// it is retryable or file scope, this logic will be bypassed.
|
||||
bool auto_recovery = false;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&new_bg_io_err, db_mutex_,
|
||||
&auto_recovery);
|
||||
|
||||
RecordStats({ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT},
|
||||
{} /* int_histograms */);
|
||||
@@ -445,9 +438,13 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"ErrorHandler: Compaction will schedule by itself to resume\n");
|
||||
bool auto_recovery = false;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&new_bg_io_err, db_mutex_,
|
||||
&auto_recovery);
|
||||
// Not used in this code path.
|
||||
new_bg_io_err.PermitUncheckedError();
|
||||
return bg_error_;
|
||||
return;
|
||||
}
|
||||
|
||||
Status::Severity severity;
|
||||
@@ -469,10 +466,14 @@ const Status& ErrorHandler::SetBGError(const Status& bg_status,
|
||||
Status bg_err(new_bg_io_err, severity);
|
||||
CheckAndSetRecoveryAndBGError(bg_err);
|
||||
recover_context_ = context;
|
||||
return StartRecoverFromRetryableBGIOError(bg_io_err);
|
||||
} else {
|
||||
return HandleKnownErrors(new_bg_io_err, reason);
|
||||
bool auto_recovery = db_options_.max_bgerror_resume_count > 0;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&new_bg_io_err, db_mutex_,
|
||||
&auto_recovery);
|
||||
StartRecoverFromRetryableBGIOError(bg_io_err);
|
||||
return;
|
||||
}
|
||||
HandleKnownErrors(new_bg_io_err, reason);
|
||||
}
|
||||
|
||||
void ErrorHandler::AddFilesToQuarantine(
|
||||
@@ -620,23 +621,23 @@ Status ErrorHandler::RecoverFromBGError(bool is_manual) {
|
||||
return s;
|
||||
}
|
||||
|
||||
const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
void ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
const IOStatus& io_error) {
|
||||
db_mutex_->AssertHeld();
|
||||
if (bg_error_.ok()) {
|
||||
return bg_error_;
|
||||
} else if (io_error.ok()) {
|
||||
return kOkStatus;
|
||||
} else if (db_options_.max_bgerror_resume_count <= 0 || recovery_in_prog_) {
|
||||
// Auto resume BG error is not enabled, directly return bg_error_.
|
||||
return bg_error_;
|
||||
} else if (end_recovery_) {
|
||||
if (bg_error_.ok() || io_error.ok()) {
|
||||
return;
|
||||
}
|
||||
if (db_options_.max_bgerror_resume_count <= 0 || recovery_in_prog_) {
|
||||
// Auto resume BG error is not enabled
|
||||
return;
|
||||
}
|
||||
if (end_recovery_) {
|
||||
// Can temporarily release db mutex
|
||||
EventHelpers::NotifyOnErrorRecoveryEnd(db_options_.listeners, bg_error_,
|
||||
Status::ShutdownInProgress(),
|
||||
db_mutex_);
|
||||
db_mutex_->AssertHeld();
|
||||
return bg_error_;
|
||||
return;
|
||||
}
|
||||
RecordStats({ERROR_HANDLER_AUTORESUME_COUNT}, {} /* int_histograms */);
|
||||
ROCKS_LOG_INFO(
|
||||
@@ -664,12 +665,6 @@ const Status& ErrorHandler::StartRecoverFromRetryableBGIOError(
|
||||
|
||||
recovery_thread_.reset(
|
||||
new port::Thread(&ErrorHandler::RecoverFromRetryableBGIOError, this));
|
||||
|
||||
if (recovery_error_.ok()) {
|
||||
return recovery_error_;
|
||||
} else {
|
||||
return bg_error_;
|
||||
}
|
||||
}
|
||||
|
||||
// Automatic recover from Retryable BG IO error. Must be called after db
|
||||
|
||||
+3
-4
@@ -56,7 +56,7 @@ class ErrorHandler {
|
||||
Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
|
||||
Status::Code code, Status::SubCode subcode);
|
||||
|
||||
const Status& SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
void SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
|
||||
Status GetBGError() const { return bg_error_; }
|
||||
|
||||
@@ -135,11 +135,10 @@ class ErrorHandler {
|
||||
// unsorted.
|
||||
autovector<uint64_t> files_to_quarantine_;
|
||||
|
||||
const Status& HandleKnownErrors(const Status& bg_err,
|
||||
BackgroundErrorReason reason);
|
||||
void HandleKnownErrors(const Status& bg_err, BackgroundErrorReason reason);
|
||||
Status OverrideNoSpaceError(const Status& bg_error, bool* auto_recovery);
|
||||
void RecoverFromNoSpace();
|
||||
const Status& StartRecoverFromRetryableBGIOError(const IOStatus& io_error);
|
||||
void StartRecoverFromRetryableBGIOError(const IOStatus& io_error);
|
||||
void RecoverFromRetryableBGIOError();
|
||||
// First, if it is in recovery and the recovery_error is ok. Set the
|
||||
// recovery_error_ to bg_err. Second, if the severity is higher than the
|
||||
|
||||
@@ -851,13 +851,17 @@ TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_TRUE(s.IsNoSpace());
|
||||
ASSERT_EQ(dbfull()->TEST_GetBGError().severity(),
|
||||
ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
|
||||
// This Resume() will attempt to create a new manifest file and fail again
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_TRUE(s.IsNoSpace());
|
||||
ASSERT_EQ(dbfull()->TEST_GetBGError().severity(),
|
||||
ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
ASSERT_FALSE(dbfull()->TEST_GetFilesToQuarantine().empty());
|
||||
fault_fs_->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
+6
-3
@@ -228,15 +228,18 @@ void EventHelpers::NotifyOnErrorRecoveryEnd(
|
||||
InstrumentedMutex* db_mutex) {
|
||||
if (!listeners.empty()) {
|
||||
db_mutex->AssertHeld();
|
||||
// Make copies before releasing mutex to avoid race.
|
||||
Status old_bg_error_cp = old_bg_error;
|
||||
Status new_bg_error_cp = new_bg_error;
|
||||
// release lock while notifying events
|
||||
db_mutex->Unlock();
|
||||
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:1");
|
||||
TEST_SYNC_POINT("NotifyOnErrorRecoveryEnd:MutexUnlocked:2");
|
||||
for (auto& listener : listeners) {
|
||||
BackgroundErrorRecoveryInfo info;
|
||||
info.old_bg_error = old_bg_error;
|
||||
info.new_bg_error = new_bg_error;
|
||||
listener->OnErrorRecoveryCompleted(old_bg_error);
|
||||
info.old_bg_error = old_bg_error_cp;
|
||||
info.new_bg_error = new_bg_error_cp;
|
||||
listener->OnErrorRecoveryCompleted(old_bg_error_cp);
|
||||
listener->OnErrorRecoveryEnd(info);
|
||||
info.old_bg_error.PermitUncheckedError();
|
||||
info.new_bg_error.PermitUncheckedError();
|
||||
|
||||
+3
-2
@@ -711,7 +711,7 @@ class SstQueryFilterConfigsManagerImpl : public SstQueryFilterConfigsManager {
|
||||
uint64_t /*file_size*/) override {
|
||||
// FIXME later: `key` might contain user timestamp. That should be
|
||||
// exposed properly in a future update to TablePropertiesCollector
|
||||
KeySegmentsExtractor::Result extracted;
|
||||
extracted.Reset();
|
||||
if (extractor) {
|
||||
extractor->Extract(key, KeySegmentsExtractor::kFullUserKey, &extracted);
|
||||
if (UNLIKELY(extracted.category >=
|
||||
@@ -750,7 +750,7 @@ class SstQueryFilterConfigsManagerImpl : public SstQueryFilterConfigsManager {
|
||||
}
|
||||
}
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_extracted = std::move(extracted);
|
||||
std::swap(prev_extracted, extracted);
|
||||
first_key = false;
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -859,6 +859,7 @@ class SstQueryFilterConfigsManagerImpl : public SstQueryFilterConfigsManager {
|
||||
std::vector<std::shared_ptr<SstQueryFilterBuilder>> builders;
|
||||
bool first_key = true;
|
||||
std::string prev_key;
|
||||
KeySegmentsExtractor::Result extracted;
|
||||
KeySegmentsExtractor::Result prev_extracted;
|
||||
KeySegmentsExtractor::KeyCategorySet categories_seen;
|
||||
};
|
||||
|
||||
@@ -44,9 +44,12 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
return status;
|
||||
}
|
||||
|
||||
// Files generated in another DB or CF may have a different column family
|
||||
// ID, so we let it pass here.
|
||||
if (file_to_ingest.cf_id !=
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily &&
|
||||
file_to_ingest.cf_id != cfd_->GetID()) {
|
||||
file_to_ingest.cf_id != cfd_->GetID() &&
|
||||
!ingestion_options_.allow_db_generated_files) {
|
||||
return Status::InvalidArgument(
|
||||
"External file column family id don't match");
|
||||
}
|
||||
@@ -111,6 +114,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::string path_inside_db = TableFileName(
|
||||
cfd_->ioptions()->cf_paths, f.fd.GetNumber(), f.fd.GetPathId());
|
||||
if (ingestion_options_.move_files) {
|
||||
assert(!ingestion_options_.allow_db_generated_files);
|
||||
status =
|
||||
fs_->LinkFile(path_outside_db, path_inside_db, IOOptions(), nullptr);
|
||||
if (status.ok()) {
|
||||
@@ -704,9 +708,16 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// Get table version
|
||||
auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (version_iter == uprops.end()) {
|
||||
return Status::Corruption("External file version not found");
|
||||
if (!ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption("External file version not found");
|
||||
} else {
|
||||
// 0 is special version for when a file from live DB does not have the
|
||||
// version table property
|
||||
file_to_ingest->version = 0;
|
||||
}
|
||||
} else {
|
||||
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
|
||||
}
|
||||
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
|
||||
|
||||
auto seqno_iter = uprops.find(ExternalSstFilePropertyNames::kGlobalSeqno);
|
||||
if (file_to_ingest->version == 2) {
|
||||
@@ -733,8 +744,15 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
return Status::InvalidArgument(
|
||||
"External SST file V1 does not support global seqno");
|
||||
}
|
||||
} else if (file_to_ingest->version == 0) {
|
||||
// allow_db_generated_files is true
|
||||
assert(seqno_iter == uprops.end());
|
||||
file_to_ingest->original_seqno = 0;
|
||||
file_to_ingest->global_seqno_offset = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("External file version is not supported");
|
||||
return Status::InvalidArgument("External file version " +
|
||||
std::to_string(file_to_ingest->version) +
|
||||
" is not supported");
|
||||
}
|
||||
|
||||
file_to_ingest->cf_id = static_cast<uint32_t>(props->column_family_id);
|
||||
@@ -896,6 +914,25 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
if (ingestion_options_.allow_db_generated_files) {
|
||||
// Verify that all keys have seqno zero.
|
||||
// TODO: store largest seqno in table property and validate it instead.
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::NotSupported(
|
||||
"External file has a key with non zero sequence number.");
|
||||
}
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
@@ -911,6 +948,11 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption(
|
||||
"External file has a range deletion with non zero sequence "
|
||||
"number.");
|
||||
}
|
||||
RangeTombstone tombstone(key, range_del_iter->value());
|
||||
|
||||
InternalKey start_key = tombstone.SerializeKey();
|
||||
@@ -1045,11 +1087,18 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
"Column family enables user-defined timestamps, please make sure the "
|
||||
"key range (without timestamp) of external file does not overlap "
|
||||
"with key range (without timestamp) in the db");
|
||||
return status;
|
||||
}
|
||||
if (*assigned_seqno == 0) {
|
||||
*assigned_seqno = last_seqno + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ingestion_options_.allow_db_generated_files && *assigned_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"An ingested file is assigned to a non-zero sequence number, which is "
|
||||
"incompatible with ingestion option allow_db_generated_files.");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -674,10 +674,8 @@ class SstFileWriterCollector : public TablePropertiesCollector {
|
||||
|
||||
Status Finish(UserCollectedProperties* properties) override {
|
||||
std::string count = std::to_string(count_);
|
||||
*properties = UserCollectedProperties{
|
||||
{prefix_ + "_SstFileWriterCollector", "YES"},
|
||||
{prefix_ + "_Count", count},
|
||||
};
|
||||
properties->insert({prefix_ + "_SstFileWriterCollector", "YES"});
|
||||
properties->insert({prefix_ + "_Count", count});
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -3719,10 +3717,7 @@ TEST_F(ExternalSSTFileWithTimestampTest, TimestampsNotPersistedBasic) {
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, false),
|
||||
std::make_tuple(true, true)));
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternSSTFileLinkFailFallbackTest,
|
||||
ExternSSTFileLinkFailFallbackTest,
|
||||
@@ -3730,6 +3725,339 @@ INSTANTIATE_TEST_CASE_P(ExternSSTFileLinkFailFallbackTest,
|
||||
std::make_tuple(true, true),
|
||||
std::make_tuple(false, false)));
|
||||
|
||||
class IngestDBGeneratedFileTest : public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
IngestDBGeneratedFileTest() {
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
ingest_opts.move_files = false;
|
||||
ingest_opts.verify_checksums_before_ingest = GetParam();
|
||||
ingest_opts.snapshot_consistency = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BasicMultiConfig, IngestDBGeneratedFileTest,
|
||||
testing::Bool());
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
// Ingesting overlapping data should always fail.
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"toto"}, options);
|
||||
// Fill CFs with overlapping keys. Will try to ingest CF1 into default CF.
|
||||
for (int k = 0; k < 50; ++k) {
|
||||
ASSERT_OK(Put(Key(k), "default_cf_" + Key(k)));
|
||||
}
|
||||
for (int k = 49; k < 100; ++k) {
|
||||
ASSERT_OK(Put(1, Key(k), "cf1_" + Key(k)));
|
||||
}
|
||||
ASSERT_OK(Flush(/*cf=*/1));
|
||||
{
|
||||
// Verify that largest key of the file has non-zero seqno.
|
||||
std::vector<std::vector<FileMetaData>> metadata;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
|
||||
const FileMetaData& file = metadata[0][0];
|
||||
ValueType vtype;
|
||||
SequenceNumber seq;
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_GE(seq, 0);
|
||||
}
|
||||
std::vector<LiveFileMetaData> live_meta;
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
std::vector<std::string> to_ingest_files;
|
||||
to_ingest_files.emplace_back(live_meta[0].directory + "/" +
|
||||
live_meta[0].relative_filename);
|
||||
// Ingesting a file whose boundary key has non-zero seqno.
|
||||
Status s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find("External file has non zero sequence number") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
{
|
||||
// Only non-boundary key with non-zero seqno.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put(1, Key(70), "cf1_" + Key(70)));
|
||||
ASSERT_OK(Flush(1));
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
|
||||
// Verify that only the non-boundary key of the file has non-zero seqno.
|
||||
std::vector<std::vector<FileMetaData>> metadata;
|
||||
// File may be at different level for different options.
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
|
||||
bool found_file = false;
|
||||
for (const auto& level : metadata) {
|
||||
if (level.empty()) {
|
||||
continue;
|
||||
}
|
||||
ASSERT_FALSE(found_file);
|
||||
found_file = true;
|
||||
ASSERT_EQ(1, level.size());
|
||||
const FileMetaData& file = level[0];
|
||||
ValueType vtype;
|
||||
SequenceNumber seq;
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_EQ(seq, 0);
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.smallest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_EQ(seq, 0);
|
||||
ASSERT_GT(file.fd.largest_seqno, 0);
|
||||
}
|
||||
ASSERT_TRUE(found_file);
|
||||
live_meta.clear();
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
to_ingest_files[0] =
|
||||
live_meta[0].directory + "/" + live_meta[0].relative_filename;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find(
|
||||
"External file has a key with non zero sequence number") !=
|
||||
std::string::npos);
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
live_meta.clear();
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
ASSERT_EQ(0, live_meta[0].largest_seqno);
|
||||
to_ingest_files[0] =
|
||||
live_meta[0].directory + "/" + live_meta[0].relative_filename;
|
||||
|
||||
ingest_opts.allow_db_generated_files = false;
|
||||
// Ingesting a DB genrate file with allow_db_generated_files = false;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find("External file version not found") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
const std::string err =
|
||||
"An ingested file is assigned to a non-zero sequence number, which is "
|
||||
"incompatible with ingestion option allow_db_generated_files";
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
if (options.compaction_style != kCompactionStyleUniversal) {
|
||||
// FIXME: after fixing ingestion with universal compaction, currently
|
||||
// will always ingest into L0.
|
||||
ingest_opts.fail_if_not_bottommost_level = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
|
||||
std::string::npos);
|
||||
ingest_opts.fail_if_not_bottommost_level = false;
|
||||
}
|
||||
ingest_opts.write_global_seqno = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find("write_global_seqno is deprecated and does "
|
||||
"not work with allow_db_generated_files") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
ingest_opts.write_global_seqno = false;
|
||||
|
||||
// Delete the overlapping key.
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), handles_[1], Key(49)));
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
live_meta.clear();
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
bool cf1_file_found = false;
|
||||
for (const auto& f : live_meta) {
|
||||
if (f.column_family_name == "toto") {
|
||||
ASSERT_FALSE(cf1_file_found);
|
||||
cf1_file_found = true;
|
||||
ASSERT_EQ(0, f.largest_seqno);
|
||||
to_ingest_files[0] = f.directory + "/" + f.relative_filename;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(cf1_file_found);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ingest_opts.snapshot_consistency = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
// snapshot_consistency with snapshot will assign a newest sequence number.
|
||||
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
ingest_opts.move_files = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ingest_opts.move_files = false;
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find("Options move_files and allow_db_generated_files are "
|
||||
"not compatible") != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
ingest_opts.snapshot_consistency = false;
|
||||
ASSERT_OK(db_->IngestExternalFile(to_ingest_files, ingest_opts));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
// Verify default CF content.
|
||||
std::string val;
|
||||
for (int k = 0; k < 100; ++k) {
|
||||
ASSERT_OK(db_->Get(ReadOptions(), Key(k), &val));
|
||||
if (k < 50) {
|
||||
ASSERT_EQ(val, "default_cf_" + Key(k));
|
||||
} else {
|
||||
ASSERT_EQ(val, "cf1_" + Key(k));
|
||||
}
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
class IngestDBGeneratedFileTest2
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool, bool>> {
|
||||
public:
|
||||
IngestDBGeneratedFileTest2() = default;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(VaryingOptions, IngestDBGeneratedFileTest2,
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool(), testing::Bool()));
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
|
||||
// Use a separate column family to sort some data, generate multiple SST
|
||||
// files. Then ingest these files into another column family or DB. The data
|
||||
// to be ingested does not overlap with existing data.
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
ingest_opts.move_files = false;
|
||||
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
|
||||
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
|
||||
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
|
||||
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
|
||||
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
Options options = CurrentOptions();
|
||||
// vector memtable for temp CF does not support concurrent write
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
CreateAndReopenWithCF({"toto"}, options);
|
||||
|
||||
// non-empty bottommost level
|
||||
WriteOptions wo;
|
||||
for (int k = 0; k < 50; ++k) {
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], Key(k), "base_val_" + Key(k)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
// non-empty memtable
|
||||
for (int k = 50; k < 100; ++k) {
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], Key(k), "base_val_" + Key(k)));
|
||||
}
|
||||
|
||||
// load external data to sort, generate multiple files
|
||||
Options temp_cf_opts;
|
||||
ColumnFamilyHandle* temp_cfh;
|
||||
temp_cf_opts.target_file_size_base = 4 << 10;
|
||||
temp_cf_opts.memtable_factory.reset(new VectorRepFactory());
|
||||
temp_cf_opts.allow_concurrent_memtable_write = false;
|
||||
temp_cf_opts.compaction_style = kCompactionStyleUniversal;
|
||||
ASSERT_OK(db_->CreateColumnFamily(temp_cf_opts, "temp_cf", &temp_cfh));
|
||||
|
||||
Random rnd(301);
|
||||
std::vector<std::string> expected_value;
|
||||
expected_value.resize(100);
|
||||
// Out of order insertion of keys from 100 to 199.
|
||||
for (int k = 99; k >= 0; --k) {
|
||||
expected_value[k] = rnd.RandomString(200);
|
||||
ASSERT_OK(db_->Put(wo, temp_cfh, Key(k + 100), expected_value[k]));
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(cro, temp_cfh, nullptr, nullptr));
|
||||
std::vector<std::string> sst_file_paths;
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(temp_cfh, &cf_meta);
|
||||
ASSERT_GT(cf_meta.file_count, 1);
|
||||
for (const auto& level_meta : cf_meta.levels) {
|
||||
if (level_meta.level + 1 < temp_cf_opts.num_levels) {
|
||||
ASSERT_EQ(0, level_meta.files.size());
|
||||
} else {
|
||||
ASSERT_GT(level_meta.files.size(), 1);
|
||||
for (const auto& meta : level_meta.files) {
|
||||
ASSERT_EQ(0, meta.largest_seqno);
|
||||
sst_file_paths.emplace_back(meta.directory + "/" +
|
||||
meta.relative_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(handles_[1], sst_file_paths, ingest_opts));
|
||||
// Verify state of the CF1
|
||||
ReadOptions ro;
|
||||
std::string val;
|
||||
for (int k = 0; k < 100; ++k) {
|
||||
ASSERT_OK(db_->Get(ro, handles_[1], Key(k), &val));
|
||||
ASSERT_EQ(val, "base_val_" + Key(k));
|
||||
ASSERT_OK(db_->Get(ro, handles_[1], Key(100 + k), &val));
|
||||
ASSERT_EQ(val, expected_value[k]);
|
||||
}
|
||||
|
||||
// Ingest into another DB.
|
||||
if (!encrypted_env_) {
|
||||
// Ingestion between encrypted env and non-encrypted env won't work.
|
||||
std::string db2_path = test::PerThreadDBPath("DB2");
|
||||
Options db2_options;
|
||||
db2_options.create_if_missing = true;
|
||||
DB* db2 = nullptr;
|
||||
ASSERT_OK(DB::Open(db2_options, db2_path, &db2));
|
||||
// Write some base data.
|
||||
expected_value.emplace_back(rnd.RandomString(100));
|
||||
ASSERT_OK(db2->Put(WriteOptions(), Key(200), expected_value.back()));
|
||||
ASSERT_OK(db2->CompactRange(cro, nullptr, nullptr));
|
||||
expected_value.emplace_back(rnd.RandomString(100));
|
||||
ASSERT_OK(db2->Put(WriteOptions(), Key(201), expected_value.back()));
|
||||
|
||||
ASSERT_OK(db2->IngestExternalFile({sst_file_paths}, ingest_opts));
|
||||
{
|
||||
std::unique_ptr<Iterator> iter{db2->NewIterator(ReadOptions())};
|
||||
iter->SeekToFirst();
|
||||
// The DB should have keys 100-199 from ingested files, and keys 200 and
|
||||
// 201 from itself.
|
||||
for (int k = 100; k <= 201; ++k, iter->Next()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), Key(k));
|
||||
ASSERT_EQ(iter->value(), expected_value[k - 100]);
|
||||
}
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Dropping the original CF should not affect db2, reopening it should not
|
||||
// miss SST files.
|
||||
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(temp_cfh));
|
||||
ASSERT_OK(db2->Close());
|
||||
delete db2;
|
||||
ASSERT_OK(DB::Open(db2_options, db2_path, &db2));
|
||||
ASSERT_OK(db2->Close());
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(db2_path, db2_options));
|
||||
} else {
|
||||
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(temp_cfh));
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+57
-23
@@ -567,34 +567,44 @@ class FaultInjectionDBTest : public DBTestBase {
|
||||
TEST(FaultInjectionFSTest, ReadUnsyncedData) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
fault_fs->SetInjectUnsyncedDataLoss(true);
|
||||
ASSERT_TRUE(fault_fs->ReadUnsyncedData());
|
||||
ASSERT_TRUE(fault_fs->InjectUnsyncedDataLoss());
|
||||
|
||||
uint32_t len = Random::GetTLSInstance()->Uniform(10000) + 1;
|
||||
Random rnd(len);
|
||||
// This is a randomized mini-stress test, to reduce the chances of bugs in
|
||||
// FaultInjectionTestFS being caught only in db_stress, where they are
|
||||
// difficult to debug. ~1000 iterations might be needed to debug relevant
|
||||
// code changes. Limiting to 10 for each regular unit test run.
|
||||
auto seed = Random::GetTLSInstance()->Next();
|
||||
for (int i = 0; i < 10; i++, seed++) {
|
||||
Random rnd(seed);
|
||||
uint32_t len = rnd.Uniform(10000) + 1;
|
||||
|
||||
// Create partially synced file
|
||||
std::string f = test::PerThreadDBPath("read_unsynced.data");
|
||||
std::string data = rnd.RandomString(len);
|
||||
{
|
||||
std::string f =
|
||||
test::PerThreadDBPath("read_unsynced." + std::to_string(seed));
|
||||
std::string data = rnd.RandomString(len);
|
||||
|
||||
// Create partially synced file
|
||||
std::unique_ptr<FSWritableFile> w;
|
||||
ASSERT_OK(fault_fs->NewWritableFile(f, {}, &w, nullptr));
|
||||
uint32_t synced_len = rnd.Uniform(len + 1);
|
||||
ASSERT_OK(w->Append(Slice(data.data(), synced_len), {}, nullptr));
|
||||
ASSERT_OK(w->Sync({}, nullptr));
|
||||
if (synced_len > 0) {
|
||||
ASSERT_OK(w->Sync({}, nullptr));
|
||||
}
|
||||
ASSERT_OK(w->Append(Slice(data.data() + synced_len, len - synced_len), {},
|
||||
nullptr));
|
||||
// no sync here
|
||||
ASSERT_OK(w->Close({}, nullptr));
|
||||
}
|
||||
// Test file size includes unsynced data
|
||||
{
|
||||
uint64_t file_size;
|
||||
ASSERT_OK(fault_fs->GetFileSize(f, {}, &file_size, nullptr));
|
||||
ASSERT_EQ(len, file_size);
|
||||
}
|
||||
// Test read file contents, with two reads that probably don't
|
||||
// align with the unsynced split.
|
||||
{
|
||||
|
||||
// Test file size includes unsynced data
|
||||
{
|
||||
uint64_t file_size;
|
||||
ASSERT_OK(fault_fs->GetFileSize(f, {}, &file_size, nullptr));
|
||||
ASSERT_EQ(len, file_size);
|
||||
}
|
||||
|
||||
// Test read file contents, with two reads that probably don't
|
||||
// align with the unsynced split. And maybe a sync or write between
|
||||
// the two reads.
|
||||
std::unique_ptr<FSSequentialFile> r;
|
||||
ASSERT_OK(fault_fs->NewSequentialFile(f, {}, &r, nullptr));
|
||||
uint32_t first_read_len = rnd.Uniform(len + 1);
|
||||
@@ -603,12 +613,36 @@ TEST(FaultInjectionFSTest, ReadUnsyncedData) {
|
||||
ASSERT_OK(r->Read(first_read_len, {}, &sl, scratch.get(), nullptr));
|
||||
ASSERT_EQ(first_read_len, sl.size());
|
||||
ASSERT_EQ(0, sl.compare(Slice(data.data(), first_read_len)));
|
||||
uint32_t second_read_len = len - first_read_len;
|
||||
|
||||
// Maybe a sync and/or write and/or close between the two reads.
|
||||
if (rnd.OneIn(2)) {
|
||||
ASSERT_OK(w->Sync({}, nullptr));
|
||||
}
|
||||
if (rnd.OneIn(2)) {
|
||||
uint32_t more_len = rnd.Uniform(1000) + 1;
|
||||
std::string more_data = rnd.RandomString(more_len);
|
||||
ASSERT_OK(w->Append(more_data, {}, nullptr));
|
||||
data += more_data;
|
||||
len += more_len;
|
||||
}
|
||||
if (rnd.OneIn(2)) {
|
||||
ASSERT_OK(w->Sync({}, nullptr));
|
||||
}
|
||||
if (rnd.OneIn(2)) {
|
||||
ASSERT_OK(w->Close({}, nullptr));
|
||||
w.reset();
|
||||
}
|
||||
|
||||
// Second read some of, all of, or more than rest of file
|
||||
uint32_t second_read_len = rnd.Uniform(len + 1);
|
||||
scratch.reset(new char[second_read_len]);
|
||||
ASSERT_OK(r->Read(second_read_len, {}, &sl, scratch.get(), nullptr));
|
||||
ASSERT_EQ(second_read_len, sl.size());
|
||||
ASSERT_EQ(0,
|
||||
sl.compare(Slice(data.data() + first_read_len, second_read_len)));
|
||||
if (len - first_read_len < second_read_len) {
|
||||
ASSERT_EQ(len - first_read_len, sl.size());
|
||||
} else {
|
||||
ASSERT_EQ(second_read_len, sl.size());
|
||||
}
|
||||
ASSERT_EQ(0, sl.compare(Slice(data.data() + first_read_len, sl.size())));
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+7
-2
@@ -235,7 +235,7 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker, FileMetaData* file_meta,
|
||||
|
||||
AutoThreadOperationStageUpdater stage_run(ThreadStatus::STAGE_FLUSH_RUN);
|
||||
if (mems_.empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_, "[%s] Nothing in memtable to flush",
|
||||
ROCKS_LOG_BUFFER(log_buffer_, "[%s] No memtable to flush",
|
||||
cfd_->GetName().c_str());
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -1017,10 +1017,15 @@ Status FlushJob::WriteLevel0Table() {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": %" PRIu64
|
||||
" bytes %s"
|
||||
"%s",
|
||||
" %s"
|
||||
" %s",
|
||||
cfd_->GetName().c_str(), job_context_->job_id,
|
||||
meta_.fd.GetNumber(), meta_.fd.GetFileSize(),
|
||||
s.ToString().c_str(),
|
||||
s.ok() && meta_.fd.GetFileSize() == 0
|
||||
? "It's an empty SST file from a successful flush so "
|
||||
"won't be kept in the DB"
|
||||
: "",
|
||||
meta_.marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
if (s.ok() && output_file_directory_ != nullptr && sync_output_directory_) {
|
||||
|
||||
@@ -68,7 +68,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -114,7 +115,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -354,13 +354,13 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
}
|
||||
|
||||
TEST_F(EventListenerTest, MultiCF) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
for (auto atomic_flush : {false, true}) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+12
-3
@@ -55,7 +55,10 @@ IOStatus Writer::WriteBuffer(const WriteOptions& write_options) {
|
||||
if (dest_->seen_error()) {
|
||||
#ifndef NDEBUG
|
||||
if (dest_->seen_injected_error()) {
|
||||
return IOStatus::IOError("Seen injected error. Skip writing buffer.");
|
||||
std::stringstream msg;
|
||||
msg << "Seen " << FaultInjectionTestFS::kInjected
|
||||
<< " error. Skip writing buffer.";
|
||||
return IOStatus::IOError(msg.str());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
return IOStatus::IOError("Seen error. Skip writing buffer.");
|
||||
@@ -93,7 +96,10 @@ IOStatus Writer::AddRecord(const WriteOptions& write_options,
|
||||
if (dest_->seen_error()) {
|
||||
#ifndef NDEBUG
|
||||
if (dest_->seen_injected_error()) {
|
||||
return IOStatus::IOError("Seen injected error. Skip writing buffer.");
|
||||
std::stringstream msg;
|
||||
msg << "Seen " << FaultInjectionTestFS::kInjected
|
||||
<< " error. Skip writing buffer.";
|
||||
return IOStatus::IOError(msg.str());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
return IOStatus::IOError("Seen error. Skip writing buffer.");
|
||||
@@ -205,7 +211,10 @@ IOStatus Writer::AddCompressionTypeRecord(const WriteOptions& write_options) {
|
||||
if (dest_->seen_error()) {
|
||||
#ifndef NDEBUG
|
||||
if (dest_->seen_injected_error()) {
|
||||
return IOStatus::IOError("Seen injected error. Skip writing buffer.");
|
||||
std::stringstream msg;
|
||||
msg << "Seen " << FaultInjectionTestFS::kInjected
|
||||
<< " error. Skip writing buffer.";
|
||||
return IOStatus::IOError(msg.str());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
return IOStatus::IOError("Seen error. Skip writing buffer.");
|
||||
|
||||
+83
-23
@@ -67,9 +67,10 @@ ImmutableMemTableOptions::ImmutableMemTableOptions(
|
||||
statistics(ioptions.stats),
|
||||
merge_operator(ioptions.merge_operator.get()),
|
||||
info_log(ioptions.logger),
|
||||
allow_data_in_errors(ioptions.allow_data_in_errors),
|
||||
protection_bytes_per_key(
|
||||
mutable_cf_options.memtable_protection_bytes_per_key) {}
|
||||
mutable_cf_options.memtable_protection_bytes_per_key),
|
||||
allow_data_in_errors(ioptions.allow_data_in_errors),
|
||||
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks) {}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
@@ -370,15 +371,17 @@ class MemTableIterator : public InternalIterator {
|
||||
: bloom_(nullptr),
|
||||
prefix_extractor_(mem.prefix_extractor_),
|
||||
comparator_(mem.comparator_),
|
||||
valid_(false),
|
||||
seqno_to_time_mapping_(seqno_to_time_mapping),
|
||||
arena_mode_(arena != nullptr),
|
||||
value_pinned_(
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
protection_bytes_per_key_(mem.moptions_.protection_bytes_per_key),
|
||||
status_(Status::OK()),
|
||||
logger_(mem.moptions_.info_log),
|
||||
ts_sz_(mem.ts_sz_) {
|
||||
ts_sz_(mem.ts_sz_),
|
||||
protection_bytes_per_key_(mem.moptions_.protection_bytes_per_key),
|
||||
valid_(false),
|
||||
value_pinned_(
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
arena_mode_(arena != nullptr),
|
||||
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
|
||||
allow_data_in_error(mem.moptions_.allow_data_in_errors) {
|
||||
if (use_range_del_table) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr && !read_options.total_order_seek &&
|
||||
@@ -406,6 +409,7 @@ class MemTableIterator : public InternalIterator {
|
||||
} else {
|
||||
delete iter_;
|
||||
}
|
||||
status_.PermitUncheckedError();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -415,10 +419,16 @@ class MemTableIterator : public InternalIterator {
|
||||
PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
|
||||
#endif
|
||||
|
||||
bool Valid() const override { return valid_ && status_.ok(); }
|
||||
bool Valid() const override {
|
||||
// If inner iter_ is not valid, then this iter should also not be valid.
|
||||
assert(iter_->Valid() || !(valid_ && status_.ok()));
|
||||
return valid_ && status_.ok();
|
||||
}
|
||||
|
||||
void Seek(const Slice& k) override {
|
||||
PERF_TIMER_GUARD(seek_on_memtable_time);
|
||||
PERF_COUNTER_ADD(seek_on_memtable_count, 1);
|
||||
status_ = Status::OK();
|
||||
if (bloom_) {
|
||||
// iterator should only use prefix bloom filter
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz_));
|
||||
@@ -433,13 +443,18 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
iter_->Seek(k, nullptr);
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
}
|
||||
void SeekForPrev(const Slice& k) override {
|
||||
PERF_TIMER_GUARD(seek_on_memtable_time);
|
||||
PERF_COUNTER_ADD(seek_on_memtable_count, 1);
|
||||
status_ = Status::OK();
|
||||
if (bloom_) {
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz_));
|
||||
if (prefix_extractor_->InDomain(user_k_without_ts)) {
|
||||
@@ -453,7 +468,11 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
iter_->Seek(k, nullptr);
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
if (!Valid() && status().ok()) {
|
||||
@@ -464,11 +483,13 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
void SeekToFirst() override {
|
||||
status_ = Status::OK();
|
||||
iter_->SeekToFirst();
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
}
|
||||
void SeekToLast() override {
|
||||
status_ = Status::OK();
|
||||
iter_->SeekToLast();
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
@@ -476,8 +497,12 @@ class MemTableIterator : public InternalIterator {
|
||||
void Next() override {
|
||||
PERF_COUNTER_ADD(next_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
iter_->Next();
|
||||
TEST_SYNC_POINT_CALLBACK("MemTableIterator::Next:0", iter_);
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->NextAndValidate(allow_data_in_error);
|
||||
} else {
|
||||
iter_->Next();
|
||||
TEST_SYNC_POINT_CALLBACK("MemTableIterator::Next:0", iter_);
|
||||
}
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
}
|
||||
@@ -494,7 +519,11 @@ class MemTableIterator : public InternalIterator {
|
||||
void Prev() override {
|
||||
PERF_COUNTER_ADD(prev_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
iter_->Prev();
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->PrevAndValidate(allow_data_in_error);
|
||||
} else {
|
||||
iter_->Prev();
|
||||
}
|
||||
valid_ = iter_->Valid();
|
||||
VerifyEntryChecksum();
|
||||
}
|
||||
@@ -540,15 +569,17 @@ class MemTableIterator : public InternalIterator {
|
||||
const SliceTransform* const prefix_extractor_;
|
||||
const MemTable::KeyComparator comparator_;
|
||||
MemTableRep::Iterator* iter_;
|
||||
bool valid_;
|
||||
// The seqno to time mapping is owned by the SuperVersion.
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping_;
|
||||
bool arena_mode_;
|
||||
bool value_pinned_;
|
||||
uint32_t protection_bytes_per_key_;
|
||||
Status status_;
|
||||
Logger* logger_;
|
||||
size_t ts_sz_;
|
||||
uint32_t protection_bytes_per_key_;
|
||||
bool valid_;
|
||||
bool value_pinned_;
|
||||
bool arena_mode_;
|
||||
const bool paranoid_memory_checks_;
|
||||
const bool allow_data_in_error;
|
||||
|
||||
void VerifyEntryChecksum() {
|
||||
if (protection_bytes_per_key_ > 0 && Valid()) {
|
||||
@@ -933,6 +964,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
Saver* s = static_cast<Saver*>(arg);
|
||||
assert(s != nullptr);
|
||||
assert(!s->value || !s->columns);
|
||||
assert(!*(s->found_final_value));
|
||||
assert(s->status->ok() || s->status->IsMergeInProgress());
|
||||
|
||||
MergeContext* merge_context = s->merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = s->max_covering_tombstone_seq;
|
||||
@@ -966,6 +999,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->status) = MemTable::VerifyEntryChecksum(
|
||||
entry, s->protection_bytes_per_key, s->allow_data_in_errors);
|
||||
if (!s->status->ok()) {
|
||||
*(s->found_final_value) = true;
|
||||
ROCKS_LOG_ERROR(s->logger, "In SaveValue: %s", s->status->getState());
|
||||
// Memtable entry corrupted
|
||||
return false;
|
||||
@@ -1231,6 +1265,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
". ");
|
||||
msg.append("seq: " + std::to_string(seq) + ".");
|
||||
}
|
||||
*(s->found_final_value) = true;
|
||||
*(s->status) = Status::Corruption(msg.c_str());
|
||||
return false;
|
||||
}
|
||||
@@ -1310,8 +1345,12 @@ bool MemTable::Get(const LookupKey& key, std::string* value,
|
||||
|
||||
// No change to value, since we have not yet found a Put/Delete
|
||||
// Propagate corruption error
|
||||
if (!found_final_value && merge_in_progress && !s->IsCorruption()) {
|
||||
*s = Status::MergeInProgress();
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
if (s->ok()) {
|
||||
*s = Status::MergeInProgress();
|
||||
} else {
|
||||
assert(s->IsMergeInProgress());
|
||||
}
|
||||
}
|
||||
PERF_COUNTER_ADD(get_from_memtable_count, 1);
|
||||
return found_final_value;
|
||||
@@ -1347,7 +1386,19 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
saver.do_merge = do_merge;
|
||||
saver.allow_data_in_errors = moptions_.allow_data_in_errors;
|
||||
saver.protection_bytes_per_key = moptions_.protection_bytes_per_key;
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
|
||||
if (!moptions_.paranoid_memory_checks) {
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
} else {
|
||||
Status check_s = table_->GetAndValidate(key, &saver, SaveValue,
|
||||
moptions_.allow_data_in_errors);
|
||||
if (check_s.IsCorruption()) {
|
||||
*(saver.status) = check_s;
|
||||
// Should stop searching the LSM.
|
||||
*(saver.found_final_value) = true;
|
||||
}
|
||||
}
|
||||
assert(s->ok() || s->IsMergeInProgress() || *found_final_value);
|
||||
*seq = saver.seq;
|
||||
}
|
||||
|
||||
@@ -1421,10 +1472,19 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
&found_final_value, &merge_in_progress);
|
||||
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
*(iter->s) = Status::MergeInProgress();
|
||||
if (iter->s->ok()) {
|
||||
*(iter->s) = Status::MergeInProgress();
|
||||
} else {
|
||||
assert(iter->s->IsMergeInProgress());
|
||||
}
|
||||
}
|
||||
|
||||
if (found_final_value) {
|
||||
if (found_final_value ||
|
||||
(!iter->s->ok() && !iter->s->IsMergeInProgress())) {
|
||||
// `found_final_value` should be set if an error/corruption occurs.
|
||||
// The check on iter->s is just there in case GetFromTable() did not
|
||||
// set `found_final_value` properly.
|
||||
assert(found_final_value);
|
||||
if (iter->value) {
|
||||
iter->value->PinSelf();
|
||||
range->AddValueSize(iter->value->size());
|
||||
|
||||
+11
-3
@@ -60,8 +60,9 @@ struct ImmutableMemTableOptions {
|
||||
Statistics* statistics;
|
||||
MergeOperator* merge_operator;
|
||||
Logger* info_log;
|
||||
bool allow_data_in_errors;
|
||||
uint32_t protection_bytes_per_key;
|
||||
bool allow_data_in_errors;
|
||||
bool paranoid_memory_checks;
|
||||
};
|
||||
|
||||
// Batched counters to updated when inserting keys in one write batch.
|
||||
@@ -249,12 +250,14 @@ class MemTable {
|
||||
// If do_merge = true the default behavior which is Get value for key is
|
||||
// executed. Expected behavior is described right below.
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
// If memtable contains a deletion for key, store a NotFound() error
|
||||
// in *status and return true.
|
||||
// If memtable contains a deletion for key, store NotFound() in *status and
|
||||
// return true.
|
||||
// If memtable contains Merge operation as the most recent entry for a key,
|
||||
// and the merge process does not stop (not reaching a value or delete),
|
||||
// prepend the current merge operand to *operands.
|
||||
// store MergeInProgress in s, and return false.
|
||||
// If an unexpected error or corruption occurs, store Corruption() or other
|
||||
// error in *status and return true.
|
||||
// Else, return false.
|
||||
// If any operation was found, its most recent sequence number
|
||||
// will be stored in *seq on success (regardless of whether true/false is
|
||||
@@ -264,6 +267,11 @@ class MemTable {
|
||||
// If do_merge = false then any Merge Operands encountered for key are simply
|
||||
// stored in merge_context.operands_list and never actually merged to get a
|
||||
// final value. The raw Merge Operands are eventually returned to the user.
|
||||
// @param value If not null and memtable contains a value for key, `value`
|
||||
// will be set to the result value.
|
||||
// @param column If not null and memtable contains a value/WideColumn for key,
|
||||
// `column` will be set to the result value/WideColumn.
|
||||
// Note: only one of `value` and `column` can be non-nullptr.
|
||||
// @param immutable_memtable Whether this memtable is immutable. Used
|
||||
// internally by NewRangeTombstoneIterator(). See comment above
|
||||
// NewRangeTombstoneIterator() for more detail.
|
||||
|
||||
+11
-9
@@ -181,7 +181,8 @@ bool MemTableListVersion::GetFromList(
|
||||
}
|
||||
|
||||
if (done) {
|
||||
assert(*seq != kMaxSequenceNumber || s->IsNotFound());
|
||||
assert(*seq != kMaxSequenceNumber ||
|
||||
(!s->ok() && !s->IsMergeInProgress()));
|
||||
return true;
|
||||
}
|
||||
if (!s->ok() && !s->IsMergeInProgress() && !s->IsNotFound()) {
|
||||
@@ -235,19 +236,19 @@ void MemTableListVersion::AddIterators(
|
||||
SequenceNumber read_seq = options.snapshot != nullptr
|
||||
? options.snapshot->GetSequenceNumber()
|
||||
: kMaxSequenceNumber;
|
||||
TruncatedRangeDelIterator* mem_tombstone_iter = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
|
||||
auto range_del_iter = m->NewRangeTombstoneIterator(
|
||||
options, read_seq, true /* immutale_memtable */);
|
||||
if (range_del_iter == nullptr || range_del_iter->empty()) {
|
||||
delete range_del_iter;
|
||||
} else {
|
||||
mem_tombstone_iter = new TruncatedRangeDelIterator(
|
||||
mem_tombstone_iter = std::make_unique<TruncatedRangeDelIterator>(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(range_del_iter),
|
||||
&m->GetInternalKeyComparator(), nullptr /* smallest */,
|
||||
nullptr /* largest */);
|
||||
}
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(mem_iter,
|
||||
mem_tombstone_iter);
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(
|
||||
mem_iter, std::move(mem_tombstone_iter));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -558,11 +559,12 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
batch_file_number = m->file_number_;
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64 " started",
|
||||
"[%s] Level-0 commit flush result of table #%" PRIu64
|
||||
" started",
|
||||
cfd->GetName().c_str(), m->file_number_);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
"[%s] Level-0 commit flush result of table #%" PRIu64
|
||||
" (+%zu blob files) started",
|
||||
cfd->GetName().c_str(), m->file_number_,
|
||||
m->edit_.GetBlobFileAdditions().size());
|
||||
@@ -757,12 +759,12 @@ void MemTableList::RemoveMemTablesOrRestoreFlags(
|
||||
MemTable* m = current_->memlist_.back();
|
||||
if (m->edit_.GetBlobFileAdditions().empty()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
"[%s] Level-0 commit flush result of table #%" PRIu64
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_, mem_id);
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Level-0 commit table #%" PRIu64
|
||||
"[%s] Level-0 commit flush result of table #%" PRIu64
|
||||
" (+%zu blob files)"
|
||||
": memtable #%" PRIu64 " done",
|
||||
cfd->GetName().c_str(), m->file_number_,
|
||||
|
||||
@@ -287,6 +287,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -295,6 +296,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ(value, "value1");
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key1", 2), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -303,6 +305,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -311,6 +314,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key3", seq), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -350,6 +354,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -357,6 +362,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->Get(LookupKey("key1", saved_seq), &value,
|
||||
/*columns=*/nullptr, /*timestamp=*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
@@ -365,6 +371,7 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ("value1", value);
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -373,12 +380,14 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
ASSERT_EQ(value, "value2.3");
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->Get(LookupKey("key2", 1), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key3", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -438,6 +447,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -446,6 +456,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, /*columns*/ nullptr,
|
||||
/*timestamp*/ nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions(),
|
||||
@@ -462,6 +473,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -469,6 +481,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -508,6 +521,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are present in history
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key1", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
@@ -515,6 +529,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key2", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
@@ -568,6 +583,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -575,6 +591,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -582,6 +599,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key3", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
@@ -590,6 +608,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify that the second memtable's keys are in the history
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key1", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
@@ -597,6 +616,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key3", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
@@ -606,6 +626,7 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify that key2 from the first memtable is no longer in the history
|
||||
merge_context.Clear();
|
||||
s = Status::OK();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, /*columns=*/nullptr,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
|
||||
+4
-2
@@ -164,6 +164,7 @@ Status TableCache::GetTableReader(
|
||||
}
|
||||
|
||||
Cache::Handle* TableCache::Lookup(Cache* cache, uint64_t file_number) {
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&file_number);
|
||||
return cache->Lookup(key);
|
||||
}
|
||||
@@ -179,6 +180,7 @@ Status TableCache::FindTable(
|
||||
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
|
||||
PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock);
|
||||
uint64_t number = file_meta.fd.GetNumber();
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&number);
|
||||
*handle = cache_.Lookup(key);
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0",
|
||||
@@ -230,7 +232,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value,
|
||||
uint8_t block_protection_bytes_per_key, const SequenceNumber* read_seqno,
|
||||
TruncatedRangeDelIterator** range_del_iter) {
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* range_del_iter) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
Status s;
|
||||
@@ -285,7 +287,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
delete new_range_del_iter;
|
||||
*range_del_iter = nullptr;
|
||||
} else {
|
||||
*range_del_iter = new TruncatedRangeDelIterator(
|
||||
*range_del_iter = std::make_unique<TruncatedRangeDelIterator>(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(
|
||||
new_range_del_iter),
|
||||
&icomparator, &file_meta.smallest, &file_meta.largest);
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ class TableCache {
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value,
|
||||
uint8_t protection_bytes_per_key,
|
||||
const SequenceNumber* range_del_read_seqno = nullptr,
|
||||
TruncatedRangeDelIterator** range_del_iter = nullptr);
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* range_del_iter = nullptr);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call get_context->SaveValue() repeatedly until
|
||||
|
||||
+470
-40
@@ -24,11 +24,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_edit_handler.h"
|
||||
#include "db/version_set.h"
|
||||
#include "port/port.h"
|
||||
#include "table/table_reader.h"
|
||||
@@ -37,6 +39,25 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class VersionBuilder::Rep {
|
||||
class NewestFirstBySeqNo {
|
||||
public:
|
||||
bool operator()(const FileMetaData* lhs, const FileMetaData* rhs) const {
|
||||
assert(lhs);
|
||||
assert(rhs);
|
||||
|
||||
if (lhs->fd.largest_seqno != rhs->fd.largest_seqno) {
|
||||
return lhs->fd.largest_seqno > rhs->fd.largest_seqno;
|
||||
}
|
||||
|
||||
if (lhs->fd.smallest_seqno != rhs->fd.smallest_seqno) {
|
||||
return lhs->fd.smallest_seqno > rhs->fd.smallest_seqno;
|
||||
}
|
||||
|
||||
// Break ties by file number
|
||||
return lhs->fd.GetNumber() > rhs->fd.GetNumber();
|
||||
}
|
||||
};
|
||||
|
||||
class NewestFirstByEpochNumber {
|
||||
private:
|
||||
inline static const NewestFirstBySeqNo seqno_cmp;
|
||||
@@ -249,9 +270,9 @@ class VersionBuilder::Rep {
|
||||
std::unordered_map<uint64_t, int> table_file_levels_;
|
||||
// Current compact cursors that should be changed after the last compaction
|
||||
std::unordered_map<int, InternalKey> updated_compact_cursors_;
|
||||
NewestFirstByEpochNumber level_zero_cmp_by_epochno_;
|
||||
NewestFirstBySeqNo level_zero_cmp_by_seqno_;
|
||||
BySmallestKey level_nonzero_cmp_;
|
||||
std::shared_ptr<NewestFirstByEpochNumber> level_zero_cmp_by_epochno_;
|
||||
std::shared_ptr<NewestFirstBySeqNo> level_zero_cmp_by_seqno_;
|
||||
std::shared_ptr<BySmallestKey> level_nonzero_cmp_;
|
||||
|
||||
// Mutable metadata objects for all blob files affected by the series of
|
||||
// version edits.
|
||||
@@ -259,11 +280,56 @@ class VersionBuilder::Rep {
|
||||
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr_;
|
||||
|
||||
ColumnFamilyData* cfd_;
|
||||
VersionEditHandler* version_edit_handler_;
|
||||
bool track_found_and_missing_files_;
|
||||
// If false, only a complete Version with all files consisting it found is
|
||||
// considered valid. If true, besides complete Version, if the Version is
|
||||
// never edited in an atomic group, an incomplete Version with only a suffix
|
||||
// of L0 files missing is also considered valid.
|
||||
bool allow_incomplete_valid_version_;
|
||||
|
||||
// These are only tracked if `track_found_and_missing_files_` is enabled.
|
||||
|
||||
// The SST files that are found (blob files not included yet).
|
||||
std::unordered_set<uint64_t> found_files_;
|
||||
// Missing SST files for L0
|
||||
std::unordered_set<uint64_t> l0_missing_files_;
|
||||
// Missing SST files for non L0 levels
|
||||
std::unordered_set<uint64_t> non_l0_missing_files_;
|
||||
// Intermediate SST files (blob files not included yet)
|
||||
std::vector<std::string> intermediate_files_;
|
||||
// The highest file number for all the missing blob files, useful to check
|
||||
// if a complete Version is available.
|
||||
uint64_t missing_blob_files_high_ = kInvalidBlobFileNumber;
|
||||
// Missing blob files, useful to check if only the missing L0 files'
|
||||
// associated blob files are missing.
|
||||
std::unordered_set<uint64_t> missing_blob_files_;
|
||||
// True if all files consisting the Version can be found. Or if
|
||||
// `allow_incomplete_valid_version_` is true and the version history is not
|
||||
// ever edited in an atomic group, this will be true if only a
|
||||
// suffix of L0 SST files and their associated blob files are missing.
|
||||
bool valid_version_available_;
|
||||
// True if version is ever edited in an atomic group.
|
||||
bool edited_in_atomic_group_;
|
||||
|
||||
// Flag to indicate if the Version is updated since last validity check. If no
|
||||
// `Apply` call is made between a `Rep`'s construction and a
|
||||
// `ValidVersionAvailable` check or between two `ValidVersionAvailable` calls.
|
||||
// This flag will be true to indicate the cached validity value can be
|
||||
// directly used without a recheck.
|
||||
bool version_updated_since_last_check_;
|
||||
|
||||
// End of fields that are only tracked when `track_found_and_missing_files_`
|
||||
// is enabled.
|
||||
|
||||
public:
|
||||
Rep(const FileOptions& file_options, const ImmutableCFOptions* ioptions,
|
||||
TableCache* table_cache, VersionStorageInfo* base_vstorage,
|
||||
VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr)
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr,
|
||||
ColumnFamilyData* cfd, VersionEditHandler* version_edit_handler,
|
||||
bool track_found_and_missing_files, bool allow_incomplete_valid_version)
|
||||
: file_options_(file_options),
|
||||
ioptions_(ioptions),
|
||||
table_cache_(table_cache),
|
||||
@@ -271,11 +337,76 @@ class VersionBuilder::Rep {
|
||||
version_set_(version_set),
|
||||
num_levels_(base_vstorage->num_levels()),
|
||||
has_invalid_levels_(false),
|
||||
level_nonzero_cmp_(base_vstorage_->InternalComparator()),
|
||||
file_metadata_cache_res_mgr_(file_metadata_cache_res_mgr) {
|
||||
level_zero_cmp_by_epochno_(
|
||||
std::make_shared<NewestFirstByEpochNumber>()),
|
||||
level_zero_cmp_by_seqno_(std::make_shared<NewestFirstBySeqNo>()),
|
||||
level_nonzero_cmp_(std::make_shared<BySmallestKey>(
|
||||
base_vstorage_->InternalComparator())),
|
||||
file_metadata_cache_res_mgr_(file_metadata_cache_res_mgr),
|
||||
cfd_(cfd),
|
||||
version_edit_handler_(version_edit_handler),
|
||||
track_found_and_missing_files_(track_found_and_missing_files),
|
||||
allow_incomplete_valid_version_(allow_incomplete_valid_version) {
|
||||
assert(ioptions_);
|
||||
|
||||
levels_ = new LevelState[num_levels_];
|
||||
if (track_found_and_missing_files_) {
|
||||
assert(cfd_);
|
||||
assert(version_edit_handler_);
|
||||
// `track_found_and_missing_files_` mode used by VersionEditHandlerPIT
|
||||
// assumes the initial base version is valid. For best efforts recovery,
|
||||
// base will be empty. For manifest tailing usage like secondary instance,
|
||||
// they do not allow incomplete version, so the base version in subsequent
|
||||
// catch up attempts should be valid too.
|
||||
valid_version_available_ = true;
|
||||
edited_in_atomic_group_ = false;
|
||||
version_updated_since_last_check_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
Rep(const Rep& other)
|
||||
: file_options_(other.file_options_),
|
||||
ioptions_(other.ioptions_),
|
||||
table_cache_(other.table_cache_),
|
||||
base_vstorage_(other.base_vstorage_),
|
||||
version_set_(other.version_set_),
|
||||
num_levels_(other.num_levels_),
|
||||
invalid_level_sizes_(other.invalid_level_sizes_),
|
||||
has_invalid_levels_(other.has_invalid_levels_),
|
||||
table_file_levels_(other.table_file_levels_),
|
||||
updated_compact_cursors_(other.updated_compact_cursors_),
|
||||
level_zero_cmp_by_epochno_(other.level_zero_cmp_by_epochno_),
|
||||
level_zero_cmp_by_seqno_(other.level_zero_cmp_by_seqno_),
|
||||
level_nonzero_cmp_(other.level_nonzero_cmp_),
|
||||
mutable_blob_file_metas_(other.mutable_blob_file_metas_),
|
||||
file_metadata_cache_res_mgr_(other.file_metadata_cache_res_mgr_),
|
||||
cfd_(other.cfd_),
|
||||
version_edit_handler_(other.version_edit_handler_),
|
||||
track_found_and_missing_files_(other.track_found_and_missing_files_),
|
||||
allow_incomplete_valid_version_(other.allow_incomplete_valid_version_),
|
||||
found_files_(other.found_files_),
|
||||
l0_missing_files_(other.l0_missing_files_),
|
||||
non_l0_missing_files_(other.non_l0_missing_files_),
|
||||
intermediate_files_(other.intermediate_files_),
|
||||
missing_blob_files_high_(other.missing_blob_files_high_),
|
||||
missing_blob_files_(other.missing_blob_files_),
|
||||
valid_version_available_(other.valid_version_available_),
|
||||
edited_in_atomic_group_(other.edited_in_atomic_group_),
|
||||
version_updated_since_last_check_(
|
||||
other.version_updated_since_last_check_) {
|
||||
assert(ioptions_);
|
||||
levels_ = new LevelState[num_levels_];
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
levels_[level] = other.levels_[level];
|
||||
const auto& added = levels_[level].added_files;
|
||||
for (auto& pair : added) {
|
||||
RefFile(pair.second);
|
||||
}
|
||||
}
|
||||
if (track_found_and_missing_files_) {
|
||||
assert(cfd_);
|
||||
assert(version_edit_handler_);
|
||||
}
|
||||
}
|
||||
|
||||
~Rep() {
|
||||
@@ -289,6 +420,12 @@ class VersionBuilder::Rep {
|
||||
delete[] levels_;
|
||||
}
|
||||
|
||||
void RefFile(FileMetaData* f) {
|
||||
assert(f);
|
||||
assert(f->refs > 0);
|
||||
f->refs++;
|
||||
}
|
||||
|
||||
void UnrefFile(FileMetaData* f) {
|
||||
f->refs--;
|
||||
if (f->refs <= 0) {
|
||||
@@ -397,7 +534,7 @@ class VersionBuilder::Rep {
|
||||
|
||||
if (epoch_number_requirement ==
|
||||
EpochNumberRequirement::kMightMissing) {
|
||||
if (!level_zero_cmp_by_seqno_(lhs, rhs)) {
|
||||
if (!level_zero_cmp_by_seqno_->operator()(lhs, rhs)) {
|
||||
std::ostringstream oss;
|
||||
oss << "L0 files are not sorted properly: files #"
|
||||
<< lhs->fd.GetNumber() << " with seqnos (largest, smallest) "
|
||||
@@ -429,7 +566,7 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
if (!level_zero_cmp_by_epochno_(lhs, rhs)) {
|
||||
if (!level_zero_cmp_by_epochno_->operator()(lhs, rhs)) {
|
||||
std::ostringstream oss;
|
||||
oss << "L0 files are not sorted properly: files #"
|
||||
<< lhs->fd.GetNumber() << " with epoch number "
|
||||
@@ -458,7 +595,7 @@ class VersionBuilder::Rep {
|
||||
assert(lhs);
|
||||
assert(rhs);
|
||||
|
||||
if (!level_nonzero_cmp_(lhs, rhs)) {
|
||||
if (!level_nonzero_cmp_->operator()(lhs, rhs)) {
|
||||
std::ostringstream oss;
|
||||
oss << 'L' << level << " files are not sorted properly: files #"
|
||||
<< lhs->fd.GetNumber() << ", #" << rhs->fd.GetNumber();
|
||||
@@ -607,12 +744,9 @@ class VersionBuilder::Rep {
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
// Note: we use C++11 for now but in C++14, this could be done in a more
|
||||
// elegant way using generalized lambda capture.
|
||||
VersionSet* const vs = version_set_;
|
||||
const ImmutableCFOptions* const ioptions = ioptions_;
|
||||
|
||||
auto deleter = [vs, ioptions](SharedBlobFileMetaData* shared_meta) {
|
||||
auto deleter = [vs = version_set_, ioptions = ioptions_,
|
||||
bc = cfd_ ? cfd_->blob_file_cache()
|
||||
: nullptr](SharedBlobFileMetaData* shared_meta) {
|
||||
if (vs) {
|
||||
assert(ioptions);
|
||||
assert(!ioptions->cf_paths.empty());
|
||||
@@ -621,6 +755,9 @@ class VersionBuilder::Rep {
|
||||
vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(),
|
||||
ioptions->cf_paths.front().path);
|
||||
}
|
||||
if (bc) {
|
||||
bc->Evict(shared_meta->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
delete shared_meta;
|
||||
};
|
||||
@@ -629,12 +766,27 @@ class VersionBuilder::Rep {
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue(), deleter);
|
||||
blob_file_addition.GetChecksumValue(), std::move(deleter));
|
||||
|
||||
mutable_blob_file_metas_.emplace(
|
||||
blob_file_number, MutableBlobFileMetaData(std::move(shared_meta)));
|
||||
|
||||
return Status::OK();
|
||||
Status s;
|
||||
if (track_found_and_missing_files_) {
|
||||
assert(version_edit_handler_);
|
||||
s = version_edit_handler_->VerifyBlobFile(cfd_, blob_file_number,
|
||||
blob_file_addition);
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
missing_blob_files_high_ =
|
||||
std::max(missing_blob_files_high_, blob_file_number);
|
||||
missing_blob_files_.insert(blob_file_number);
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ApplyBlobFileGarbage(const BlobFileGarbage& blob_file_garbage) {
|
||||
@@ -752,6 +904,29 @@ class VersionBuilder::Rep {
|
||||
table_file_levels_[file_number] =
|
||||
VersionStorageInfo::FileLocation::Invalid().GetLevel();
|
||||
|
||||
if (track_found_and_missing_files_) {
|
||||
assert(version_edit_handler_);
|
||||
if (l0_missing_files_.find(file_number) != l0_missing_files_.end()) {
|
||||
l0_missing_files_.erase(file_number);
|
||||
} else if (non_l0_missing_files_.find(file_number) !=
|
||||
non_l0_missing_files_.end()) {
|
||||
non_l0_missing_files_.erase(file_number);
|
||||
} else {
|
||||
auto fiter = found_files_.find(file_number);
|
||||
// Only mark new files added during this catchup attempt for deletion.
|
||||
// These files were never installed in VersionStorageInfo.
|
||||
// Already referenced files that are deleted by a VersionEdit will
|
||||
// be added to the VersionStorageInfo's obsolete files when the old
|
||||
// version is dereferenced.
|
||||
if (fiter != found_files_.end()) {
|
||||
assert(!ioptions_->cf_paths.empty());
|
||||
intermediate_files_.emplace_back(
|
||||
MakeTableFileName(ioptions_->cf_paths[0].path, file_number));
|
||||
found_files_.erase(fiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -824,7 +999,31 @@ class VersionBuilder::Rep {
|
||||
|
||||
table_file_levels_[file_number] = level;
|
||||
|
||||
return Status::OK();
|
||||
Status s;
|
||||
if (track_found_and_missing_files_) {
|
||||
assert(version_edit_handler_);
|
||||
assert(!ioptions_->cf_paths.empty());
|
||||
const std::string fpath =
|
||||
MakeTableFileName(ioptions_->cf_paths[0].path, file_number);
|
||||
s = version_edit_handler_->VerifyFile(cfd_, fpath, level, meta);
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
if (0 == level) {
|
||||
l0_missing_files_.insert(file_number);
|
||||
} else {
|
||||
non_l0_missing_files_.insert(file_number);
|
||||
}
|
||||
if (s.IsCorruption()) {
|
||||
found_files_.insert(file_number);
|
||||
}
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
return s;
|
||||
} else {
|
||||
found_files_.insert(file_number);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ApplyCompactCursors(int level,
|
||||
@@ -845,6 +1044,7 @@ class VersionBuilder::Rep {
|
||||
|
||||
// Apply all of the edits in *edit to the current state.
|
||||
Status Apply(const VersionEdit* edit) {
|
||||
bool version_updated = false;
|
||||
{
|
||||
const Status s = CheckConsistency(base_vstorage_);
|
||||
if (!s.ok()) {
|
||||
@@ -862,6 +1062,7 @@ class VersionBuilder::Rep {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
version_updated = true;
|
||||
}
|
||||
|
||||
// Increase the amount of garbage for blob files affected by GC
|
||||
@@ -870,6 +1071,7 @@ class VersionBuilder::Rep {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
version_updated = true;
|
||||
}
|
||||
|
||||
// Delete table files
|
||||
@@ -881,6 +1083,7 @@ class VersionBuilder::Rep {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
version_updated = true;
|
||||
}
|
||||
|
||||
// Add new table files
|
||||
@@ -892,6 +1095,7 @@ class VersionBuilder::Rep {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
version_updated = true;
|
||||
}
|
||||
|
||||
// Populate compact cursors for round-robin compaction, leave
|
||||
@@ -904,6 +1108,13 @@ class VersionBuilder::Rep {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (track_found_and_missing_files_ && version_updated) {
|
||||
version_updated_since_last_check_ = true;
|
||||
if (!edited_in_atomic_group_ && edit->IsInAtomicGroup()) {
|
||||
edited_in_atomic_group_ = true;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1046,14 +1257,35 @@ class VersionBuilder::Rep {
|
||||
mutable_meta.GetGarbageBlobCount(), mutable_meta.GetGarbageBlobBytes());
|
||||
}
|
||||
|
||||
bool OnlyLinkedToMissingL0Files(
|
||||
const std::unordered_set<uint64_t>& linked_ssts) const {
|
||||
return std::all_of(
|
||||
linked_ssts.begin(), linked_ssts.end(), [&](const uint64_t& element) {
|
||||
return l0_missing_files_.find(element) != l0_missing_files_.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Add the blob file specified by meta to *vstorage if it is determined to
|
||||
// contain valid data (blobs).
|
||||
template <typename Meta>
|
||||
static void AddBlobFileIfNeeded(VersionStorageInfo* vstorage, Meta&& meta) {
|
||||
void AddBlobFileIfNeeded(VersionStorageInfo* vstorage, Meta&& meta,
|
||||
uint64_t blob_file_number) const {
|
||||
assert(vstorage);
|
||||
assert(meta);
|
||||
|
||||
if (meta->GetLinkedSsts().empty() &&
|
||||
const auto& linked_ssts = meta->GetLinkedSsts();
|
||||
if (track_found_and_missing_files_) {
|
||||
if (missing_blob_files_.find(blob_file_number) !=
|
||||
missing_blob_files_.end()) {
|
||||
return;
|
||||
}
|
||||
// Leave the empty case for the below blob garbage collection logic.
|
||||
if (!linked_ssts.empty() && OnlyLinkedToMissingL0Files(linked_ssts)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (linked_ssts.empty() &&
|
||||
meta->GetGarbageBlobCount() >= meta->GetTotalBlobCount()) {
|
||||
return;
|
||||
}
|
||||
@@ -1065,6 +1297,7 @@ class VersionBuilder::Rep {
|
||||
// applied, and save the result into *vstorage.
|
||||
void SaveBlobFilesTo(VersionStorageInfo* vstorage) const {
|
||||
assert(vstorage);
|
||||
assert(!track_found_and_missing_files_ || valid_version_available_);
|
||||
|
||||
assert(base_vstorage_);
|
||||
vstorage->ReserveBlob(base_vstorage_->GetBlobFiles().size() +
|
||||
@@ -1080,22 +1313,24 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
auto process_base =
|
||||
[vstorage](const std::shared_ptr<BlobFileMetaData>& base_meta) {
|
||||
[this, vstorage](const std::shared_ptr<BlobFileMetaData>& base_meta) {
|
||||
assert(base_meta);
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, base_meta);
|
||||
AddBlobFileIfNeeded(vstorage, base_meta,
|
||||
base_meta->GetBlobFileNumber());
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto process_mutable =
|
||||
[vstorage](const MutableBlobFileMetaData& mutable_meta) {
|
||||
AddBlobFileIfNeeded(vstorage, CreateBlobFileMetaData(mutable_meta));
|
||||
[this, vstorage](const MutableBlobFileMetaData& mutable_meta) {
|
||||
AddBlobFileIfNeeded(vstorage, CreateBlobFileMetaData(mutable_meta),
|
||||
mutable_meta.GetBlobFileNumber());
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto process_both = [vstorage](
|
||||
auto process_both = [this, vstorage](
|
||||
const std::shared_ptr<BlobFileMetaData>& base_meta,
|
||||
const MutableBlobFileMetaData& mutable_meta) {
|
||||
assert(base_meta);
|
||||
@@ -1108,12 +1343,14 @@ class VersionBuilder::Rep {
|
||||
mutable_meta.GetGarbageBlobBytes());
|
||||
assert(base_meta->GetLinkedSsts() == mutable_meta.GetLinkedSsts());
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, base_meta);
|
||||
AddBlobFileIfNeeded(vstorage, base_meta,
|
||||
base_meta->GetBlobFileNumber());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, CreateBlobFileMetaData(mutable_meta));
|
||||
AddBlobFileIfNeeded(vstorage, CreateBlobFileMetaData(mutable_meta),
|
||||
mutable_meta.GetBlobFileNumber());
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -1125,6 +1362,10 @@ class VersionBuilder::Rep {
|
||||
void MaybeAddFile(VersionStorageInfo* vstorage, int level,
|
||||
FileMetaData* f) const {
|
||||
const uint64_t file_number = f->fd.GetNumber();
|
||||
if (track_found_and_missing_files_ && level == 0 &&
|
||||
l0_missing_files_.find(file_number) != l0_missing_files_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& level_state = levels_[level];
|
||||
|
||||
@@ -1148,6 +1389,29 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainsCompleteVersion() const {
|
||||
assert(track_found_and_missing_files_);
|
||||
return l0_missing_files_.empty() && non_l0_missing_files_.empty() &&
|
||||
(missing_blob_files_high_ == kInvalidBlobFileNumber ||
|
||||
missing_blob_files_high_ < GetMinOldestBlobFileNumber());
|
||||
}
|
||||
|
||||
bool HasMissingFiles() const {
|
||||
assert(track_found_and_missing_files_);
|
||||
return !l0_missing_files_.empty() || !non_l0_missing_files_.empty() ||
|
||||
missing_blob_files_high_ != kInvalidBlobFileNumber;
|
||||
}
|
||||
|
||||
std::vector<std::string>& GetAndClearIntermediateFiles() {
|
||||
assert(track_found_and_missing_files_);
|
||||
return intermediate_files_;
|
||||
}
|
||||
|
||||
void ClearFoundFiles() {
|
||||
assert(track_found_and_missing_files_);
|
||||
found_files_.clear();
|
||||
}
|
||||
|
||||
template <typename Cmp>
|
||||
void SaveSSTFilesTo(VersionStorageInfo* vstorage, int level, Cmp cmp) const {
|
||||
// Merge the set of added files with the set of pre-existing files.
|
||||
@@ -1156,6 +1420,16 @@ class VersionBuilder::Rep {
|
||||
const auto& unordered_added_files = levels_[level].added_files;
|
||||
vstorage->Reserve(level, base_files.size() + unordered_added_files.size());
|
||||
|
||||
MergeUnorderdAddedFilesWithBase(
|
||||
base_files, unordered_added_files, cmp,
|
||||
[&](FileMetaData* file) { MaybeAddFile(vstorage, level, file); });
|
||||
}
|
||||
|
||||
template <typename Cmp, typename AddFileFunc>
|
||||
void MergeUnorderdAddedFilesWithBase(
|
||||
const std::vector<FileMetaData*>& base_files,
|
||||
const std::unordered_map<uint64_t, FileMetaData*>& unordered_added_files,
|
||||
Cmp cmp, AddFileFunc add_file_func) const {
|
||||
// Sort added files for the level.
|
||||
std::vector<FileMetaData*> added_files;
|
||||
added_files.reserve(unordered_added_files.size());
|
||||
@@ -1171,9 +1445,9 @@ class VersionBuilder::Rep {
|
||||
while (added_iter != added_end || base_iter != base_end) {
|
||||
if (base_iter == base_end ||
|
||||
(added_iter != added_end && cmp(*added_iter, *base_iter))) {
|
||||
MaybeAddFile(vstorage, level, *added_iter++);
|
||||
add_file_func(*added_iter++);
|
||||
} else {
|
||||
MaybeAddFile(vstorage, level, *base_iter++);
|
||||
add_file_func(*base_iter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1215,13 +1489,13 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
|
||||
if (epoch_number_requirement == EpochNumberRequirement::kMightMissing) {
|
||||
SaveSSTFilesTo(vstorage, /* level */ 0, level_zero_cmp_by_seqno_);
|
||||
SaveSSTFilesTo(vstorage, /* level */ 0, *level_zero_cmp_by_seqno_);
|
||||
} else {
|
||||
SaveSSTFilesTo(vstorage, /* level */ 0, level_zero_cmp_by_epochno_);
|
||||
SaveSSTFilesTo(vstorage, /* level */ 0, *level_zero_cmp_by_epochno_);
|
||||
}
|
||||
|
||||
for (int level = 1; level < num_levels_; ++level) {
|
||||
SaveSSTFilesTo(vstorage, level, level_nonzero_cmp_);
|
||||
SaveSSTFilesTo(vstorage, level, *level_nonzero_cmp_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1232,8 +1506,110 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidVersionAvailable() {
|
||||
assert(track_found_and_missing_files_);
|
||||
if (version_updated_since_last_check_) {
|
||||
valid_version_available_ = ContainsCompleteVersion();
|
||||
if (!valid_version_available_ && !edited_in_atomic_group_ &&
|
||||
allow_incomplete_valid_version_) {
|
||||
valid_version_available_ = OnlyMissingL0Suffix();
|
||||
}
|
||||
version_updated_since_last_check_ = false;
|
||||
}
|
||||
return valid_version_available_;
|
||||
}
|
||||
|
||||
bool OnlyMissingL0Suffix() const {
|
||||
if (!non_l0_missing_files_.empty()) {
|
||||
return false;
|
||||
}
|
||||
assert(!(l0_missing_files_.empty() && missing_blob_files_.empty()));
|
||||
|
||||
if (!l0_missing_files_.empty() && !MissingL0FilesAreL0Suffix()) {
|
||||
return false;
|
||||
}
|
||||
if (!missing_blob_files_.empty() &&
|
||||
!RemainingSstFilesNotMissingBlobFiles()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check missing L0 files are a suffix of expected sorted L0 files.
|
||||
bool MissingL0FilesAreL0Suffix() const {
|
||||
assert(non_l0_missing_files_.empty());
|
||||
assert(!l0_missing_files_.empty());
|
||||
std::vector<FileMetaData*> expected_sorted_l0_files;
|
||||
const auto& base_files = base_vstorage_->LevelFiles(0);
|
||||
const auto& unordered_added_files = levels_[0].added_files;
|
||||
expected_sorted_l0_files.reserve(base_files.size() +
|
||||
unordered_added_files.size());
|
||||
EpochNumberRequirement epoch_number_requirement =
|
||||
base_vstorage_->GetEpochNumberRequirement();
|
||||
|
||||
if (epoch_number_requirement == EpochNumberRequirement::kMightMissing) {
|
||||
MergeUnorderdAddedFilesWithBase(
|
||||
base_files, unordered_added_files, *level_zero_cmp_by_seqno_,
|
||||
[&](FileMetaData* file) {
|
||||
expected_sorted_l0_files.push_back(file);
|
||||
});
|
||||
} else {
|
||||
MergeUnorderdAddedFilesWithBase(
|
||||
base_files, unordered_added_files, *level_zero_cmp_by_epochno_,
|
||||
[&](FileMetaData* file) {
|
||||
expected_sorted_l0_files.push_back(file);
|
||||
});
|
||||
}
|
||||
assert(expected_sorted_l0_files.size() >= l0_missing_files_.size());
|
||||
std::unordered_set<uint64_t> unaddressed_missing_files = l0_missing_files_;
|
||||
for (auto iter = expected_sorted_l0_files.begin();
|
||||
iter != expected_sorted_l0_files.end(); iter++) {
|
||||
uint64_t file_number = (*iter)->fd.GetNumber();
|
||||
if (l0_missing_files_.find(file_number) != l0_missing_files_.end()) {
|
||||
assert(unaddressed_missing_files.find(file_number) !=
|
||||
unaddressed_missing_files.end());
|
||||
unaddressed_missing_files.erase(file_number);
|
||||
} else if (!unaddressed_missing_files.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for each of the missing blob file missing, it either is older than
|
||||
// the minimum oldest blob file required by this Version or only linked to
|
||||
// the missing L0 files.
|
||||
bool RemainingSstFilesNotMissingBlobFiles() const {
|
||||
assert(non_l0_missing_files_.empty());
|
||||
assert(!missing_blob_files_.empty());
|
||||
bool no_l0_files_missing = l0_missing_files_.empty();
|
||||
uint64_t min_oldest_blob_file_num = GetMinOldestBlobFileNumber();
|
||||
for (const auto& missing_blob_file : missing_blob_files_) {
|
||||
if (missing_blob_file < min_oldest_blob_file_num) {
|
||||
continue;
|
||||
}
|
||||
auto iter = mutable_blob_file_metas_.find(missing_blob_file);
|
||||
assert(iter != mutable_blob_file_metas_.end());
|
||||
auto linked_ssts = iter->second.GetLinkedSsts();
|
||||
// TODO(yuzhangyu): In theory, if no L0 SST files ara missing, and only
|
||||
// blob files exclusively linked to a L0 suffix are missing, we can
|
||||
// recover to a valid point in time too. We don't recover that type of
|
||||
// incomplete Version yet.
|
||||
if (!linked_ssts.empty() && no_l0_files_missing) {
|
||||
return false;
|
||||
}
|
||||
if (!OnlyLinkedToMissingL0Files(linked_ssts)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save the current state in *vstorage.
|
||||
Status SaveTo(VersionStorageInfo* vstorage) const {
|
||||
assert(!track_found_and_missing_files_ || valid_version_available_);
|
||||
Status s;
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -1266,6 +1642,7 @@ class VersionBuilder::Rep {
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
assert(table_cache_ != nullptr);
|
||||
assert(!track_found_and_missing_files_ || valid_version_available_);
|
||||
|
||||
size_t table_cache_capacity =
|
||||
table_cache_->get_cache().get()->GetCapacity();
|
||||
@@ -1305,6 +1682,11 @@ class VersionBuilder::Rep {
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
for (auto& file_meta_pair : levels_[level].added_files) {
|
||||
auto* file_meta = file_meta_pair.second;
|
||||
uint64_t file_number = file_meta->fd.GetNumber();
|
||||
if (track_found_and_missing_files_ && level == 0 &&
|
||||
l0_missing_files_.find(file_number) != l0_missing_files_.end()) {
|
||||
continue;
|
||||
}
|
||||
// If the file has been opened before, just skip it.
|
||||
if (!file_meta->table_reader_handle) {
|
||||
files_meta.emplace_back(file_meta, level);
|
||||
@@ -1369,9 +1751,13 @@ VersionBuilder::VersionBuilder(
|
||||
const FileOptions& file_options, const ImmutableCFOptions* ioptions,
|
||||
TableCache* table_cache, VersionStorageInfo* base_vstorage,
|
||||
VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr)
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr,
|
||||
ColumnFamilyData* cfd, VersionEditHandler* version_edit_handler,
|
||||
bool track_found_and_missing_files, bool allow_incomplete_valid_version)
|
||||
: rep_(new Rep(file_options, ioptions, table_cache, base_vstorage,
|
||||
version_set, file_metadata_cache_res_mgr)) {}
|
||||
version_set, file_metadata_cache_res_mgr, cfd,
|
||||
version_edit_handler, track_found_and_missing_files,
|
||||
allow_incomplete_valid_version)) {}
|
||||
|
||||
VersionBuilder::~VersionBuilder() = default;
|
||||
|
||||
@@ -1399,27 +1785,71 @@ Status VersionBuilder::LoadTableHandlers(
|
||||
read_options, block_protection_bytes_per_key);
|
||||
}
|
||||
|
||||
uint64_t VersionBuilder::GetMinOldestBlobFileNumber() const {
|
||||
return rep_->GetMinOldestBlobFileNumber();
|
||||
void VersionBuilder::CreateOrReplaceSavePoint() {
|
||||
assert(rep_);
|
||||
savepoint_ = std::move(rep_);
|
||||
rep_ = std::make_unique<Rep>(*savepoint_);
|
||||
}
|
||||
|
||||
bool VersionBuilder::ValidVersionAvailable() {
|
||||
return rep_->ValidVersionAvailable();
|
||||
}
|
||||
|
||||
bool VersionBuilder::HasMissingFiles() const { return rep_->HasMissingFiles(); }
|
||||
|
||||
std::vector<std::string>& VersionBuilder::GetAndClearIntermediateFiles() {
|
||||
return rep_->GetAndClearIntermediateFiles();
|
||||
}
|
||||
|
||||
void VersionBuilder::ClearFoundFiles() { return rep_->ClearFoundFiles(); }
|
||||
|
||||
Status VersionBuilder::SaveSavePointTo(VersionStorageInfo* vstorage) const {
|
||||
if (!savepoint_ || !savepoint_->ValidVersionAvailable()) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
return savepoint_->SaveTo(vstorage);
|
||||
}
|
||||
|
||||
Status VersionBuilder::LoadSavePointTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
if (!savepoint_ || !savepoint_->ValidVersionAvailable()) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
return savepoint_->LoadTableHandlers(
|
||||
internal_stats, max_threads, prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, prefix_extractor, max_file_size_for_l0_meta_pin,
|
||||
read_options, block_protection_bytes_per_key);
|
||||
}
|
||||
|
||||
void VersionBuilder::ClearSavePoint() { savepoint_.reset(nullptr); }
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd)
|
||||
ColumnFamilyData* cfd, VersionEditHandler* version_edit_handler,
|
||||
bool track_found_and_missing_files, bool allow_incomplete_valid_version)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->ioptions(),
|
||||
cfd->table_cache(), cfd->current()->storage_info(),
|
||||
cfd->current()->version_set(),
|
||||
cfd->GetFileMetadataCacheReservationManager())),
|
||||
cfd->GetFileMetadataCacheReservationManager(), cfd,
|
||||
version_edit_handler, track_found_and_missing_files,
|
||||
allow_incomplete_valid_version)),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, Version* v)
|
||||
ColumnFamilyData* cfd, Version* v, VersionEditHandler* version_edit_handler,
|
||||
bool track_found_and_missing_files, bool allow_incomplete_valid_version)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->ioptions(),
|
||||
cfd->table_cache(), v->storage_info(), v->version_set(),
|
||||
cfd->GetFileMetadataCacheReservationManager())),
|
||||
cfd->GetFileMetadataCacheReservationManager(), cfd,
|
||||
version_edit_handler, track_found_and_missing_files,
|
||||
allow_incomplete_valid_version)),
|
||||
version_(v) {
|
||||
assert(version_ != cfd->current());
|
||||
}
|
||||
|
||||
+70
-23
@@ -26,6 +26,7 @@ struct FileMetaData;
|
||||
class InternalStats;
|
||||
class Version;
|
||||
class VersionSet;
|
||||
class VersionEditHandler;
|
||||
class ColumnFamilyData;
|
||||
class CacheReservationManager;
|
||||
|
||||
@@ -38,22 +39,80 @@ class VersionBuilder {
|
||||
const ImmutableCFOptions* ioptions, TableCache* table_cache,
|
||||
VersionStorageInfo* base_vstorage, VersionSet* version_set,
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
file_metadata_cache_res_mgr = nullptr);
|
||||
file_metadata_cache_res_mgr = nullptr,
|
||||
ColumnFamilyData* cfd = nullptr,
|
||||
VersionEditHandler* version_edit_handler = nullptr,
|
||||
bool track_found_and_missing_files = false,
|
||||
bool allow_incomplete_valid_version = false);
|
||||
~VersionBuilder();
|
||||
|
||||
bool CheckConsistencyForNumLevels();
|
||||
|
||||
Status Apply(const VersionEdit* edit);
|
||||
|
||||
// Save the current Version to the provided `vstorage`.
|
||||
Status SaveTo(VersionStorageInfo* vstorage) const;
|
||||
|
||||
// Load all the table handlers for the current Version in the builder.
|
||||
Status LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key);
|
||||
uint64_t GetMinOldestBlobFileNumber() const;
|
||||
|
||||
//============APIs only used by VersionEditHandlerPointInTime ============//
|
||||
|
||||
// Creates a save point for the Version that has been built so far. Subsequent
|
||||
// VersionEdits applied to the builder will not affect the Version in this
|
||||
// save point. VersionBuilder currently only supports creating one save point,
|
||||
// so when `CreateOrReplaceSavePoint` is called again, the previous save point
|
||||
// is cleared. `ClearSavePoint` can be called explicitly to clear
|
||||
// the save point too.
|
||||
void CreateOrReplaceSavePoint();
|
||||
|
||||
// The builder can find all the files to build a `Version`. Or if
|
||||
// `allow_incomplete_valid_version_` is true and the version history is never
|
||||
// edited in an atomic group, and only a suffix of L0 SST files and their
|
||||
// associated blob files are missing.
|
||||
// From the users' perspective, missing a suffix of L0 files means missing the
|
||||
// user's most recently written data. So the remaining available files still
|
||||
// presents a valid point in time view, although for some previous time.
|
||||
// This validity check result will be cached and reused if the Version is not
|
||||
// updated between two validity checks.
|
||||
bool ValidVersionAvailable();
|
||||
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
// When applying a sequence of VersionEdit, intermediate files are the ones
|
||||
// that are added and then deleted. The caller should clear this intermediate
|
||||
// files tracking after calling this API. So that the tracking for subsequent
|
||||
// VersionEdits can start over with a clean state.
|
||||
std::vector<std::string>& GetAndClearIntermediateFiles();
|
||||
|
||||
// Clearing all the found files in this Version.
|
||||
void ClearFoundFiles();
|
||||
|
||||
// Save the Version in the save point to the provided `vstorage`.
|
||||
// Non-OK status will be returned if there is not a valid save point.
|
||||
Status SaveSavePointTo(VersionStorageInfo* vstorage) const;
|
||||
|
||||
// Load all the table handlers for the Version in the save point.
|
||||
// Non-OK status will be returned if there is not a valid save point.
|
||||
Status LoadSavePointTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key);
|
||||
|
||||
void ClearSavePoint();
|
||||
|
||||
//======= End of APIs only used by VersionEditPointInTime==========//
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
std::unique_ptr<Rep> savepoint_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
};
|
||||
|
||||
@@ -62,8 +121,15 @@ class VersionBuilder {
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
class BaseReferencedVersionBuilder {
|
||||
public:
|
||||
explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd);
|
||||
BaseReferencedVersionBuilder(ColumnFamilyData* cfd, Version* v);
|
||||
explicit BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, VersionEditHandler* version_edit_handler = nullptr,
|
||||
bool track_found_and_missing_files = false,
|
||||
bool allow_incomplete_valid_version = false);
|
||||
BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, Version* v,
|
||||
VersionEditHandler* version_edit_handler = nullptr,
|
||||
bool track_found_and_missing_files = false,
|
||||
bool allow_incomplete_valid_version = false);
|
||||
~BaseReferencedVersionBuilder();
|
||||
VersionBuilder* version_builder() const { return version_builder_.get(); }
|
||||
|
||||
@@ -71,23 +137,4 @@ class BaseReferencedVersionBuilder {
|
||||
std::unique_ptr<VersionBuilder> version_builder_;
|
||||
Version* version_;
|
||||
};
|
||||
|
||||
class NewestFirstBySeqNo {
|
||||
public:
|
||||
bool operator()(const FileMetaData* lhs, const FileMetaData* rhs) const {
|
||||
assert(lhs);
|
||||
assert(rhs);
|
||||
|
||||
if (lhs->fd.largest_seqno != rhs->fd.largest_seqno) {
|
||||
return lhs->fd.largest_seqno > rhs->fd.largest_seqno;
|
||||
}
|
||||
|
||||
if (lhs->fd.smallest_seqno != rhs->fd.smallest_seqno) {
|
||||
return lhs->fd.smallest_seqno > rhs->fd.smallest_seqno;
|
||||
}
|
||||
|
||||
// Break ties by file number
|
||||
return lhs->fd.GetNumber() > rhs->fd.GetNumber();
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -255,7 +255,6 @@ bool VersionEdit::EncodeTo(std::string* dst,
|
||||
char p = static_cast<char>(0);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
|
||||
dst);
|
||||
|
||||
|
||||
+95
-207
@@ -155,6 +155,7 @@ VersionEditHandler::VersionEditHandler(
|
||||
VersionSet* version_set, bool track_found_and_missing_files,
|
||||
bool no_error_if_files_missing, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ReadOptions& read_options, bool skip_load_table_files,
|
||||
bool allow_incomplete_valid_version,
|
||||
EpochNumberRequirement epoch_number_requirement)
|
||||
: VersionEditHandlerBase(read_options),
|
||||
read_only_(read_only),
|
||||
@@ -165,6 +166,7 @@ VersionEditHandler::VersionEditHandler(
|
||||
io_tracer_(io_tracer),
|
||||
skip_load_table_files_(skip_load_table_files),
|
||||
initialized_(false),
|
||||
allow_incomplete_valid_version_(allow_incomplete_valid_version),
|
||||
epoch_number_requirement_(epoch_number_requirement) {
|
||||
assert(version_set_ != nullptr);
|
||||
}
|
||||
@@ -218,15 +220,15 @@ Status VersionEditHandler::ApplyVersionEdit(VersionEdit& edit,
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool do_not_open_cf = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
CheckColumnFamilyId(edit, &do_not_open_cf, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
const std::string& cf_name = edit.GetColumnFamilyName();
|
||||
Status s;
|
||||
if (cf_in_builders || cf_in_not_found) {
|
||||
if (cf_in_builders || do_not_open_cf) {
|
||||
s = Status::Corruption("MANIFEST adding the same column family twice: " +
|
||||
cf_name);
|
||||
}
|
||||
@@ -239,7 +241,7 @@ Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
cf_name.compare(kPersistentStatsColumnFamilyName) == 0;
|
||||
if (cf_options == name_to_options_.end() &&
|
||||
!is_persistent_stats_column_family) {
|
||||
column_families_not_found_.emplace(edit.GetColumnFamily(), cf_name);
|
||||
do_not_open_column_families_.emplace(edit.GetColumnFamily(), cf_name);
|
||||
} else {
|
||||
if (is_persistent_stats_column_family) {
|
||||
ColumnFamilyOptions cfo;
|
||||
@@ -256,9 +258,9 @@ Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool do_not_open_cf = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
CheckColumnFamilyId(edit, &do_not_open_cf, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
@@ -266,8 +268,8 @@ Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
|
||||
Status s;
|
||||
if (cf_in_builders) {
|
||||
tmp_cfd = DestroyCfAndCleanup(edit);
|
||||
} else if (cf_in_not_found) {
|
||||
column_families_not_found_.erase(edit.GetColumnFamily());
|
||||
} else if (do_not_open_cf) {
|
||||
do_not_open_column_families_.erase(edit.GetColumnFamily());
|
||||
} else {
|
||||
s = Status::Corruption("MANIFEST - dropping non-existing column family");
|
||||
}
|
||||
@@ -288,22 +290,20 @@ Status VersionEditHandler::OnWalDeletion(VersionEdit& edit) {
|
||||
|
||||
Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool do_not_open_cf = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
CheckColumnFamilyId(edit, &do_not_open_cf, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
Status s;
|
||||
if (!cf_in_not_found) {
|
||||
if (!do_not_open_cf) {
|
||||
if (!cf_in_builders) {
|
||||
s = Status::Corruption(
|
||||
"MANIFEST record referencing unknown column family");
|
||||
}
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
if (s.ok()) {
|
||||
auto builder_iter = builders_.find(edit.GetColumnFamily());
|
||||
assert(builder_iter != builders_.end());
|
||||
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
|
||||
edit.GetColumnFamily());
|
||||
assert(tmp_cfd != nullptr);
|
||||
@@ -318,56 +318,33 @@ Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = MaybeCreateVersion(edit, tmp_cfd, /*force_create_version=*/false);
|
||||
if (s.ok()) {
|
||||
s = builder_iter->second->version_builder()->Apply(&edit);
|
||||
}
|
||||
s = MaybeCreateVersionBeforeApplyEdit(edit, tmp_cfd,
|
||||
/*force_create_version=*/false);
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// TODO maybe cache the computation result
|
||||
bool VersionEditHandler::HasMissingFiles() const {
|
||||
bool ret = false;
|
||||
for (const auto& elem : cf_to_missing_files_) {
|
||||
const auto& missing_files = elem.second;
|
||||
if (!missing_files.empty()) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
for (const auto& elem : cf_to_missing_blob_files_high_) {
|
||||
if (elem.second != kInvalidBlobFileNumber) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VersionEditHandler::CheckColumnFamilyId(const VersionEdit& edit,
|
||||
bool* cf_in_not_found,
|
||||
bool* do_not_open_cf,
|
||||
bool* cf_in_builders) const {
|
||||
assert(cf_in_not_found != nullptr);
|
||||
assert(do_not_open_cf != nullptr);
|
||||
assert(cf_in_builders != nullptr);
|
||||
// Not found means that user didn't supply that column
|
||||
// family option AND we encountered column family add
|
||||
// record. Once we encounter column family drop record,
|
||||
// we will delete the column family from
|
||||
// column_families_not_found.
|
||||
// do_not_open_column_families_.
|
||||
uint32_t cf_id = edit.GetColumnFamily();
|
||||
bool in_not_found = column_families_not_found_.find(cf_id) !=
|
||||
column_families_not_found_.end();
|
||||
bool in_do_not_open = do_not_open_column_families_.find(cf_id) !=
|
||||
do_not_open_column_families_.end();
|
||||
// in builders means that user supplied that column family
|
||||
// option AND that we encountered column family add record
|
||||
bool in_builders = builders_.find(cf_id) != builders_.end();
|
||||
// They cannot both be true
|
||||
assert(!(in_not_found && in_builders));
|
||||
*cf_in_not_found = in_not_found;
|
||||
assert(!(in_do_not_open && in_builders));
|
||||
*do_not_open_cf = in_do_not_open;
|
||||
*cf_in_builders = in_builders;
|
||||
}
|
||||
|
||||
@@ -396,9 +373,9 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
// There were some column families in the MANIFEST that weren't specified
|
||||
// in the argument. This is OK in read_only mode
|
||||
if (s->ok() && MustOpenAllColumnFamilies() &&
|
||||
!column_families_not_found_.empty()) {
|
||||
!do_not_open_column_families_.empty()) {
|
||||
std::string msg;
|
||||
for (const auto& cf : column_families_not_found_) {
|
||||
for (const auto& cf : do_not_open_column_families_) {
|
||||
msg.append(", ");
|
||||
msg.append(cf.second);
|
||||
}
|
||||
@@ -453,7 +430,8 @@ void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
VersionEdit edit;
|
||||
*s = MaybeCreateVersion(edit, cfd, /*force_create_version=*/true);
|
||||
*s = MaybeCreateVersionBeforeApplyEdit(edit, cfd,
|
||||
/*force_create_version=*/true);
|
||||
if (!s->ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -498,13 +476,9 @@ ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
assert(cfd != nullptr);
|
||||
cfd->set_initialized();
|
||||
assert(builders_.find(cf_id) == builders_.end());
|
||||
builders_.emplace(cf_id,
|
||||
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
|
||||
if (track_found_and_missing_files_) {
|
||||
cf_to_found_files_.emplace(cf_id, std::unordered_set<uint64_t>());
|
||||
cf_to_missing_files_.emplace(cf_id, std::unordered_set<uint64_t>());
|
||||
cf_to_missing_blob_files_high_.emplace(cf_id, kInvalidBlobFileNumber);
|
||||
}
|
||||
builders_.emplace(cf_id, VersionBuilderUPtr(new BaseReferencedVersionBuilder(
|
||||
cfd, this, track_found_and_missing_files_,
|
||||
allow_incomplete_valid_version_)));
|
||||
return cfd;
|
||||
}
|
||||
|
||||
@@ -514,21 +488,6 @@ ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
auto builder_iter = builders_.find(cf_id);
|
||||
assert(builder_iter != builders_.end());
|
||||
builders_.erase(builder_iter);
|
||||
if (track_found_and_missing_files_) {
|
||||
auto found_files_iter = cf_to_found_files_.find(cf_id);
|
||||
assert(found_files_iter != cf_to_found_files_.end());
|
||||
cf_to_found_files_.erase(found_files_iter);
|
||||
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cf_id);
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
cf_to_missing_files_.erase(missing_files_iter);
|
||||
|
||||
auto missing_blob_files_high_iter =
|
||||
cf_to_missing_blob_files_high_.find(cf_id);
|
||||
assert(missing_blob_files_high_iter !=
|
||||
cf_to_missing_blob_files_high_.end());
|
||||
cf_to_missing_blob_files_high_.erase(missing_blob_files_high_iter);
|
||||
}
|
||||
ColumnFamilyData* ret =
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(cf_id);
|
||||
assert(ret != nullptr);
|
||||
@@ -538,15 +497,14 @@ ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
return ret;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::MaybeCreateVersion(const VersionEdit& /*edit*/,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version) {
|
||||
Status VersionEditHandler::MaybeCreateVersionBeforeApplyEdit(
|
||||
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
|
||||
assert(cfd->initialized());
|
||||
Status s;
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
if (force_create_version) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
auto* v = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), io_tracer_,
|
||||
version_set_->current_version_number_++,
|
||||
@@ -562,6 +520,7 @@ Status VersionEditHandler::MaybeCreateVersion(const VersionEdit& /*edit*/,
|
||||
delete v;
|
||||
}
|
||||
}
|
||||
s = builder->Apply(&edit);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -731,12 +690,13 @@ Status VersionEditHandler::MaybeHandleFileBoundariesForNewFiles(
|
||||
VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
VersionSet* version_set, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ReadOptions& read_options,
|
||||
const ReadOptions& read_options, bool allow_incomplete_valid_version,
|
||||
EpochNumberRequirement epoch_number_requirement)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
/*track_found_and_missing_files=*/true,
|
||||
/*no_error_if_files_missing=*/true, io_tracer,
|
||||
read_options, epoch_number_requirement) {}
|
||||
read_options, allow_incomplete_valid_version,
|
||||
epoch_number_requirement) {}
|
||||
|
||||
VersionEditHandlerPointInTime::~VersionEditHandlerPointInTime() {
|
||||
for (const auto& cfid_and_version : atomic_update_versions_) {
|
||||
@@ -762,7 +722,8 @@ Status VersionEditHandlerPointInTime::OnAtomicGroupReplayBegin() {
|
||||
assert(!cfd->IsDropped());
|
||||
assert(cfd->initialized());
|
||||
VersionEdit edit;
|
||||
Status s = MaybeCreateVersion(edit, cfd, true /* force_create_version */);
|
||||
Status s = MaybeCreateVersionBeforeApplyEdit(
|
||||
edit, cfd, true /* force_create_version */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -824,17 +785,17 @@ void VersionEditHandlerPointInTime::CheckIterationResult(
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
auto v_iter = versions_.find(cfd->GetID());
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
if (v_iter != versions_.end()) {
|
||||
assert(v_iter->second != nullptr);
|
||||
assert(builder_iter != builders_.end());
|
||||
|
||||
version_set_->AppendVersion(cfd, v_iter->second);
|
||||
versions_.erase(v_iter);
|
||||
// Let's clear found_files, since any files in that are part of the
|
||||
// installed Version. Any files that got obsoleted would have already
|
||||
// been moved to intermediate_files_
|
||||
auto found_files_iter = cf_to_found_files_.find(cfd->GetID());
|
||||
assert(found_files_iter != cf_to_found_files_.end());
|
||||
found_files_iter->second.clear();
|
||||
builder_iter->second->version_builder()->ClearFoundFiles();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -863,147 +824,50 @@ ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
|
||||
return cfd;
|
||||
}
|
||||
|
||||
Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
Status VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit(
|
||||
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
|
||||
TEST_SYNC_POINT("VersionEditHandlerPointInTime::MaybeCreateVersion:Begin1");
|
||||
TEST_SYNC_POINT("VersionEditHandlerPointInTime::MaybeCreateVersion:Begin2");
|
||||
TEST_SYNC_POINT(
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin1");
|
||||
TEST_SYNC_POINT(
|
||||
"VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit:"
|
||||
"Begin2");
|
||||
assert(cfd != nullptr);
|
||||
if (!force_create_version) {
|
||||
assert(edit.GetColumnFamily() == cfd->GetID());
|
||||
}
|
||||
auto found_files_iter = cf_to_found_files_.find(cfd->GetID());
|
||||
assert(found_files_iter != cf_to_found_files_.end());
|
||||
std::unordered_set<uint64_t>& found_files = found_files_iter->second;
|
||||
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
std::unordered_set<uint64_t>& missing_files = missing_files_iter->second;
|
||||
|
||||
auto missing_blob_files_high_iter =
|
||||
cf_to_missing_blob_files_high_.find(cfd->GetID());
|
||||
assert(missing_blob_files_high_iter != cf_to_missing_blob_files_high_.end());
|
||||
const uint64_t prev_missing_blob_file_high =
|
||||
missing_blob_files_high_iter->second;
|
||||
|
||||
VersionBuilder* builder = nullptr;
|
||||
|
||||
if (prev_missing_blob_file_high != kInvalidBlobFileNumber) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
builder = builder_iter->second->version_builder();
|
||||
assert(builder != nullptr);
|
||||
}
|
||||
|
||||
// At this point, we have not yet applied the new version edits read from the
|
||||
// MANIFEST. We check whether we have any missing table and blob files.
|
||||
const bool prev_has_missing_files =
|
||||
!missing_files.empty() ||
|
||||
(prev_missing_blob_file_high != kInvalidBlobFileNumber &&
|
||||
prev_missing_blob_file_high >= builder->GetMinOldestBlobFileNumber());
|
||||
|
||||
for (const auto& file : edit.GetDeletedFiles()) {
|
||||
uint64_t file_num = file.second;
|
||||
auto fiter = missing_files.find(file_num);
|
||||
if (fiter != missing_files.end()) {
|
||||
missing_files.erase(fiter);
|
||||
} else {
|
||||
fiter = found_files.find(file_num);
|
||||
// Only mark new files added during this catchup attempt for deletion.
|
||||
// These files were never installed in VersionStorageInfo.
|
||||
// Already referenced files that are deleted by a VersionEdit will
|
||||
// be added to the VersionStorageInfo's obsolete files when the old
|
||||
// version is dereferenced.
|
||||
if (fiter != found_files.end()) {
|
||||
intermediate_files_.emplace_back(
|
||||
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num));
|
||||
found_files.erase(fiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(!cfd->ioptions()->cf_paths.empty());
|
||||
Status s;
|
||||
for (const auto& elem : edit.GetNewFiles()) {
|
||||
int level = elem.first;
|
||||
const FileMetaData& meta = elem.second;
|
||||
const FileDescriptor& fd = meta.fd;
|
||||
uint64_t file_num = fd.GetNumber();
|
||||
const std::string fpath =
|
||||
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num);
|
||||
s = VerifyFile(cfd, fpath, level, meta);
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
missing_files.insert(file_num);
|
||||
if (s.IsCorruption()) {
|
||||
found_files.insert(file_num);
|
||||
}
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
break;
|
||||
} else {
|
||||
found_files.insert(file_num);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t missing_blob_file_num = prev_missing_blob_file_high;
|
||||
for (const auto& elem : edit.GetBlobFileAdditions()) {
|
||||
uint64_t file_num = elem.GetBlobFileNumber();
|
||||
s = VerifyBlobFile(cfd, file_num, elem);
|
||||
if (s.IsPathNotFound() || s.IsNotFound() || s.IsCorruption()) {
|
||||
missing_blob_file_num = std::max(missing_blob_file_num, file_num);
|
||||
s = Status::OK();
|
||||
} else if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_missing_blob_files = false;
|
||||
if (missing_blob_file_num != kInvalidBlobFileNumber &&
|
||||
missing_blob_file_num >= prev_missing_blob_file_high) {
|
||||
missing_blob_files_high_iter->second = missing_blob_file_num;
|
||||
has_missing_blob_files = true;
|
||||
} else if (missing_blob_file_num < prev_missing_blob_file_high) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// We still have not applied the new version edit, but have tried to add new
|
||||
// table and blob files after verifying their presence and consistency.
|
||||
// Therefore, we know whether we will see new missing table and blob files
|
||||
// later after actually applying the version edit. We perform the check here
|
||||
// and record the result.
|
||||
const bool has_missing_files =
|
||||
!missing_files.empty() || has_missing_blob_files;
|
||||
|
||||
bool missing_info = !version_edit_params_.HasLogNumber() ||
|
||||
!version_edit_params_.HasNextFile() ||
|
||||
!version_edit_params_.HasLastSequence();
|
||||
|
||||
// Create version before apply edit. The version will represent the state
|
||||
// before applying the version edit.
|
||||
Status s;
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
VersionBuilder* builder = builder_iter->second->version_builder();
|
||||
const bool valid_pit_before_edit = builder->ValidVersionAvailable();
|
||||
builder->CreateOrReplaceSavePoint();
|
||||
s = builder->Apply(&edit);
|
||||
const bool valid_pit_after_edit = builder->ValidVersionAvailable();
|
||||
|
||||
// A new version will be created if:
|
||||
// 1) no error has occurred so far, and
|
||||
// 2) log_number_, next_file_number_ and last_sequence_ are known, and
|
||||
// 3) not in an AtomicGroup
|
||||
// 4) any of the following:
|
||||
// a) no missing file before, but will have missing file(s) after applying
|
||||
// this version edit.
|
||||
// b) no missing file after applying the version edit, and the caller
|
||||
// explicitly request that a new version be created.
|
||||
// a) a valid Version is available before applying the edit
|
||||
// and a valid Version is not available after the edit.
|
||||
// b) a valid Version is available after the edit and the
|
||||
// caller explicitly request that a new version be created.
|
||||
if (s.ok() && !missing_info && !in_atomic_group_ &&
|
||||
((has_missing_files && !prev_has_missing_files) ||
|
||||
(!has_missing_files && force_create_version))) {
|
||||
if (!builder) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
builder = builder_iter->second->version_builder();
|
||||
assert(builder);
|
||||
}
|
||||
|
||||
((!valid_pit_after_edit && valid_pit_before_edit) ||
|
||||
(valid_pit_after_edit && force_create_version))) {
|
||||
const MutableCFOptions* cf_opts_ptr = cfd->GetLatestMutableCFOptions();
|
||||
auto* version = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cf_opts_ptr, io_tracer_,
|
||||
version_set_->current_version_number_++,
|
||||
epoch_number_requirement_);
|
||||
s = builder->LoadTableHandlers(
|
||||
s = builder->LoadSavePointTableHandlers(
|
||||
cfd->internal_stats(),
|
||||
version_set_->db_options_->max_file_opening_threads, false, true,
|
||||
cf_opts_ptr->prefix_extractor, MaxFileSizeForL0MetaPin(*cf_opts_ptr),
|
||||
@@ -1015,7 +879,7 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
s = builder->SaveTo(version->storage_info());
|
||||
s = builder->SaveSavePointTo(version->storage_info());
|
||||
if (s.ok()) {
|
||||
if (AtomicUpdateVersionsContains(cfd->GetID())) {
|
||||
AtomicUpdateVersionsPut(version);
|
||||
@@ -1038,6 +902,8 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
delete version;
|
||||
}
|
||||
}
|
||||
|
||||
builder->ClearSavePoint();
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1072,6 +938,15 @@ Status VersionEditHandlerPointInTime::LoadTables(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool VersionEditHandlerPointInTime::HasMissingFiles() const {
|
||||
for (const auto& builder : builders_) {
|
||||
if (builder.second->version_builder()->HasMissingFiles()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VersionEditHandlerPointInTime::AtomicUpdateVersionsCompleted() {
|
||||
return atomic_update_versions_missing_ == 0;
|
||||
}
|
||||
@@ -1145,8 +1020,9 @@ Status ManifestTailer::Initialize() {
|
||||
Version* base_version = dummy_version->Next();
|
||||
assert(base_version);
|
||||
base_version->Ref();
|
||||
VersionBuilderUPtr new_builder(
|
||||
new BaseReferencedVersionBuilder(default_cfd, base_version));
|
||||
VersionBuilderUPtr new_builder(new BaseReferencedVersionBuilder(
|
||||
default_cfd, base_version, this, track_found_and_missing_files_,
|
||||
allow_incomplete_valid_version_));
|
||||
builder_iter->second = std::move(new_builder);
|
||||
|
||||
initialized_ = true;
|
||||
@@ -1189,8 +1065,8 @@ Status ManifestTailer::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
Version* base_version = dummy_version->Next();
|
||||
assert(base_version);
|
||||
base_version->Ref();
|
||||
VersionBuilderUPtr new_builder(
|
||||
new BaseReferencedVersionBuilder(tmp_cfd, base_version));
|
||||
VersionBuilderUPtr new_builder(new BaseReferencedVersionBuilder(
|
||||
tmp_cfd, base_version, this, track_found_and_missing_files_));
|
||||
builder_iter->second = std::move(new_builder);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -1213,6 +1089,18 @@ void ManifestTailer::CheckIterationResult(const log::Reader& reader,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ManifestTailer::GetAndClearIntermediateFiles() {
|
||||
std::vector<std::string> res;
|
||||
for (const auto& builder : builders_) {
|
||||
auto files =
|
||||
builder.second->version_builder()->GetAndClearIntermediateFiles();
|
||||
res.insert(res.end(), std::make_move_iterator(files.begin()),
|
||||
std::make_move_iterator(files.end()));
|
||||
files.erase(files.begin(), files.end());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Status ManifestTailer::VerifyFile(ColumnFamilyData* cfd,
|
||||
const std::string& fpath, int level,
|
||||
const FileMetaData& fmeta) {
|
||||
|
||||
+56
-37
@@ -119,13 +119,14 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
VersionSet* version_set, bool track_found_and_missing_files,
|
||||
bool no_error_if_files_missing,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ReadOptions& read_options,
|
||||
const ReadOptions& read_options, bool allow_incomplete_valid_version,
|
||||
EpochNumberRequirement epoch_number_requirement =
|
||||
EpochNumberRequirement::kMustPresent)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
track_found_and_missing_files,
|
||||
no_error_if_files_missing, io_tracer, read_options,
|
||||
/*skip_load_table_files=*/false,
|
||||
allow_incomplete_valid_version,
|
||||
epoch_number_requirement) {}
|
||||
|
||||
~VersionEditHandler() override {}
|
||||
@@ -134,14 +135,24 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
return version_edit_params_;
|
||||
}
|
||||
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
void GetDbId(std::string* db_id) const {
|
||||
if (db_id && version_edit_params_.HasDbId()) {
|
||||
*db_id = version_edit_params_.GetDbId();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status VerifyFile(ColumnFamilyData* /*cfd*/,
|
||||
const std::string& /*fpath*/, int /*level*/,
|
||||
const FileMetaData& /*fmeta*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status VerifyBlobFile(ColumnFamilyData* /*cfd*/,
|
||||
uint64_t /*blob_file_num*/,
|
||||
const BlobFileAddition& /*blob_addition*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit VersionEditHandler(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
@@ -149,6 +160,7 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
bool no_error_if_files_missing,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ReadOptions& read_options, bool skip_load_table_files,
|
||||
bool allow_incomplete_valid_version,
|
||||
EpochNumberRequirement epoch_number_requirement =
|
||||
EpochNumberRequirement::kMustPresent);
|
||||
|
||||
@@ -166,7 +178,7 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
|
||||
Status Initialize() override;
|
||||
|
||||
void CheckColumnFamilyId(const VersionEdit& edit, bool* cf_in_not_found,
|
||||
void CheckColumnFamilyId(const VersionEdit& edit, bool* do_not_open_cf,
|
||||
bool* cf_in_builders) const;
|
||||
|
||||
void CheckIterationResult(const log::Reader& reader, Status* s) override;
|
||||
@@ -176,9 +188,9 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
|
||||
virtual ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit);
|
||||
|
||||
virtual Status MaybeCreateVersion(const VersionEdit& edit,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version);
|
||||
virtual Status MaybeCreateVersionBeforeApplyEdit(const VersionEdit& edit,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version);
|
||||
|
||||
virtual Status LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
@@ -191,21 +203,23 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
VersionSet* version_set_;
|
||||
std::unordered_map<uint32_t, VersionBuilderUPtr> builders_;
|
||||
std::unordered_map<std::string, ColumnFamilyOptions> name_to_options_;
|
||||
// Keeps track of column families in manifest that were not found in
|
||||
// column families parameters. if those column families are not dropped
|
||||
// by subsequent manifest records, Recover() will return failure status.
|
||||
std::unordered_map<uint32_t, std::string> column_families_not_found_;
|
||||
VersionEditParams version_edit_params_;
|
||||
const bool track_found_and_missing_files_;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint64_t>> cf_to_found_files_;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint64_t>>
|
||||
cf_to_missing_files_;
|
||||
std::unordered_map<uint32_t, uint64_t> cf_to_missing_blob_files_high_;
|
||||
// Keeps track of column families in manifest that were not found in
|
||||
// column families parameters. Namely, the user asks to not open these column
|
||||
// families. In non read only mode, if those column families are not dropped
|
||||
// by subsequent manifest records, Recover() will return failure status.
|
||||
std::unordered_map<uint32_t, std::string> do_not_open_column_families_;
|
||||
VersionEditParams version_edit_params_;
|
||||
bool no_error_if_files_missing_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
bool skip_load_table_files_;
|
||||
bool initialized_;
|
||||
std::unique_ptr<std::unordered_map<uint32_t, std::string>> cf_to_cmp_names_;
|
||||
// If false, only a complete Version for which all files consisting it can be
|
||||
// found is considered a valid Version. If true, besides complete Version, an
|
||||
// incomplete Version with only a suffix of L0 files missing is also
|
||||
// considered valid if the Version is never edited in an atomic group.
|
||||
const bool allow_incomplete_valid_version_;
|
||||
EpochNumberRequirement epoch_number_requirement_;
|
||||
std::unordered_set<uint32_t> cfds_to_mark_no_udt_;
|
||||
|
||||
@@ -227,7 +241,9 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
|
||||
// A class similar to its base class, i.e. VersionEditHandler.
|
||||
// VersionEditHandlerPointInTime restores the versions to the most recent point
|
||||
// in time such that at this point, the version does not have missing files.
|
||||
// in time such that at this point, the version does not have missing files. Or
|
||||
// if `allow_incomplete_valid_version` is true, only a suffix of L0 files (and
|
||||
// their associated blob files) are missing.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandlerPointInTime is shared by multiple threads.
|
||||
@@ -236,28 +252,32 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
VersionEditHandlerPointInTime(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
VersionSet* version_set, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ReadOptions& read_options,
|
||||
const ReadOptions& read_options, bool allow_incomplete_valid_version,
|
||||
EpochNumberRequirement epoch_number_requirement =
|
||||
EpochNumberRequirement::kMustPresent);
|
||||
~VersionEditHandlerPointInTime() override;
|
||||
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
virtual Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath,
|
||||
int level, const FileMetaData& fmeta) override;
|
||||
virtual Status VerifyBlobFile(ColumnFamilyData* cfd, uint64_t blob_file_num,
|
||||
const BlobFileAddition& blob_addition) override;
|
||||
|
||||
protected:
|
||||
Status OnAtomicGroupReplayBegin() override;
|
||||
Status OnAtomicGroupReplayEnd() override;
|
||||
void CheckIterationResult(const log::Reader& reader, Status* s) override;
|
||||
|
||||
ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit) override;
|
||||
// `MaybeCreateVersion(..., false)` creates a version upon a negative edge
|
||||
// trigger (transition from valid to invalid).
|
||||
// `MaybeCreateVersionBeforeApplyEdit(..., false)` creates a version upon a
|
||||
// negative edge trigger (transition from valid to invalid).
|
||||
//
|
||||
// `MaybeCreateVersion(..., true)` creates a version on a positive level
|
||||
// trigger (state is valid).
|
||||
Status MaybeCreateVersion(const VersionEdit& edit, ColumnFamilyData* cfd,
|
||||
bool force_create_version) override;
|
||||
virtual Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath,
|
||||
int level, const FileMetaData& fmeta);
|
||||
virtual Status VerifyBlobFile(ColumnFamilyData* cfd, uint64_t blob_file_num,
|
||||
const BlobFileAddition& blob_addition);
|
||||
// `MaybeCreateVersionBeforeApplyEdit(..., true)` creates a version on a
|
||||
// positive level trigger (state is valid).
|
||||
Status MaybeCreateVersionBeforeApplyEdit(const VersionEdit& edit,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version) override;
|
||||
|
||||
Status LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
@@ -275,8 +295,6 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
|
||||
bool in_atomic_group_ = false;
|
||||
|
||||
std::vector<std::string> intermediate_files_;
|
||||
|
||||
private:
|
||||
bool AtomicUpdateVersionsCompleted();
|
||||
bool AtomicUpdateVersionsContains(uint32_t cfid);
|
||||
@@ -302,9 +320,13 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
|
||||
EpochNumberRequirement::kMustPresent)
|
||||
: VersionEditHandlerPointInTime(/*read_only=*/false, column_families,
|
||||
version_set, io_tracer, read_options,
|
||||
/*allow_incomplete_valid_version=*/false,
|
||||
epoch_number_requirement),
|
||||
mode_(Mode::kRecovery) {}
|
||||
|
||||
Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath, int level,
|
||||
const FileMetaData& fmeta) override;
|
||||
|
||||
void PrepareToReadNewManifest() {
|
||||
initialized_ = false;
|
||||
ClearReadBuffer();
|
||||
@@ -314,9 +336,7 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
|
||||
return cfds_changed_;
|
||||
}
|
||||
|
||||
std::vector<std::string>& GetIntermediateFiles() {
|
||||
return intermediate_files_;
|
||||
}
|
||||
std::vector<std::string> GetAndClearIntermediateFiles();
|
||||
|
||||
protected:
|
||||
Status Initialize() override;
|
||||
@@ -329,9 +349,6 @@ class ManifestTailer : public VersionEditHandlerPointInTime {
|
||||
|
||||
void CheckIterationResult(const log::Reader& reader, Status* s) override;
|
||||
|
||||
Status VerifyFile(ColumnFamilyData* cfd, const std::string& fpath, int level,
|
||||
const FileMetaData& fmeta) override;
|
||||
|
||||
enum Mode : uint8_t {
|
||||
kRecovery = 0,
|
||||
kCatchUp = 1,
|
||||
@@ -352,7 +369,9 @@ class DumpManifestHandler : public VersionEditHandler {
|
||||
/*read_only=*/true, column_families, version_set,
|
||||
/*track_found_and_missing_files=*/false,
|
||||
/*no_error_if_files_missing=*/false, io_tracer, read_options,
|
||||
/*skip_load_table_files=*/true),
|
||||
/*skip_load_table_files=*/true,
|
||||
/*allow_incomplete_valid_version=*/false,
|
||||
/*epoch_number_requirement=*/EpochNumberRequirement::kMustPresent),
|
||||
verbose_(verbose),
|
||||
hex_(hex),
|
||||
json_(json),
|
||||
|
||||
+47
-38
@@ -976,7 +976,8 @@ class LevelIterator final : public InternalIterator {
|
||||
const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries =
|
||||
nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
TruncatedRangeDelIterator**** range_tombstone_iter_ptr_ = nullptr)
|
||||
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
|
||||
nullptr)
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
file_options_(file_options),
|
||||
@@ -1116,9 +1117,8 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
|
||||
void ClearRangeTombstoneIter() {
|
||||
if (range_tombstone_iter_ && *range_tombstone_iter_) {
|
||||
delete *range_tombstone_iter_;
|
||||
*range_tombstone_iter_ = nullptr;
|
||||
if (range_tombstone_iter_) {
|
||||
range_tombstone_iter_->reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1201,7 +1201,7 @@ class LevelIterator final : public InternalIterator {
|
||||
// iterator end).
|
||||
//
|
||||
// *range_tombstone_iter_ points to range tombstones of the current SST file
|
||||
TruncatedRangeDelIterator** range_tombstone_iter_;
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* range_tombstone_iter_;
|
||||
|
||||
// The sentinel key to be returned
|
||||
Slice sentinel_;
|
||||
@@ -1280,11 +1280,9 @@ void LevelIterator::Seek(const Slice& target) {
|
||||
ts_sz);
|
||||
if (prefix_extractor_->InDomain(target_user_key_without_ts) &&
|
||||
(!prefix_extractor_->InDomain(next_file_first_user_key_without_ts) ||
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
prefix_extractor_->Transform(target_user_key_without_ts), false,
|
||||
prefix_extractor_->Transform(
|
||||
next_file_first_user_key_without_ts),
|
||||
false) != 0)) {
|
||||
prefix_extractor_->Transform(target_user_key_without_ts)
|
||||
.compare(prefix_extractor_->Transform(
|
||||
next_file_first_user_key_without_ts)) != 0)) {
|
||||
// SkipEmptyFileForward() will not advance to next file when this flag
|
||||
// is set for reason detailed below.
|
||||
//
|
||||
@@ -1929,7 +1927,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
|
||||
int level, bool allow_unprepared_value) {
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr;
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, file_options_,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
@@ -2029,7 +2027,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
auto* arena = merge_iter_builder->GetArena();
|
||||
if (level == 0) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
TruncatedRangeDelIterator* tombstone_iter = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator> tombstone_iter = nullptr;
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
auto table_iter = cfd_->table_cache()->NewIterator(
|
||||
@@ -2046,8 +2044,8 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(table_iter);
|
||||
} else {
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(table_iter,
|
||||
tombstone_iter);
|
||||
merge_iter_builder->AddPointAndTombstoneIterator(
|
||||
table_iter, std::move(tombstone_iter));
|
||||
}
|
||||
}
|
||||
if (should_sample) {
|
||||
@@ -2064,7 +2062,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
auto* mem = arena->AllocateAligned(sizeof(LevelIterator));
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr;
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
@@ -5510,8 +5508,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
IOStatus manifest_io_status;
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option (in file_options_) takes precedence when not kUnknown
|
||||
if (file_options_.temperature != Temperature::kUnknown) {
|
||||
opt_file_opts.temperature = file_options_.temperature;
|
||||
}
|
||||
mu->Unlock();
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart");
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr);
|
||||
@@ -5538,6 +5541,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
}
|
||||
|
||||
log::Writer* raw_desc_log_ptr = descriptor_log_.get();
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
@@ -5559,12 +5563,14 @@ Status VersionSet::ProcessManifestWrites(
|
||||
db_options_->listeners, nullptr,
|
||||
tmp_set.Contains(FileType::kDescriptorFile),
|
||||
tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
descriptor_log_.reset(
|
||||
new_desc_log_ptr.reset(
|
||||
new log::Writer(std::move(file_writer), 0, false));
|
||||
raw_desc_log_ptr = new_desc_log_ptr.get();
|
||||
s = WriteCurrentStateToManifest(write_options, curr_state,
|
||||
wal_additions, descriptor_log_.get(),
|
||||
io_s);
|
||||
} else {
|
||||
wal_additions, raw_desc_log_ptr, io_s);
|
||||
assert(s == io_s);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
manifest_io_status = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
@@ -5607,7 +5613,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
++idx;
|
||||
#endif /* !NDEBUG */
|
||||
io_s = descriptor_log_->AddRecord(write_options, record);
|
||||
io_s = raw_desc_log_ptr->AddRecord(write_options, record);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
manifest_io_status = io_s;
|
||||
@@ -5617,7 +5623,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
if (s.ok()) {
|
||||
io_s =
|
||||
SyncManifest(db_options_, write_options, descriptor_log_->file());
|
||||
SyncManifest(db_options_, write_options, raw_desc_log_ptr->file());
|
||||
manifest_io_status = io_s;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:AfterSyncManifest", &io_s);
|
||||
@@ -5635,9 +5641,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(manifest_io_status.ok());
|
||||
}
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
io_s = SetCurrentFile(write_options, fs_.get(), dbname_,
|
||||
pending_manifest_file_number_,
|
||||
dir_contains_current_file);
|
||||
io_s = SetCurrentFile(
|
||||
write_options, fs_.get(), dbname_, pending_manifest_file_number_,
|
||||
file_options_.temperature, dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
@@ -5650,7 +5656,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
if (s.ok()) {
|
||||
// find offset in manifest file where this version is stored.
|
||||
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
|
||||
new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize();
|
||||
}
|
||||
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
@@ -5696,6 +5702,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// Append the old manifest file to the obsolete_manifest_ list to be deleted
|
||||
// by PurgeObsoleteFiles later.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
descriptor_log_ = std::move(new_desc_log_ptr);
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
}
|
||||
@@ -5765,14 +5772,11 @@ Status VersionSet::ProcessManifestWrites(
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
if (manifest_io_status.ok()) {
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
}
|
||||
// If manifest append failed for whatever reason, the file could be
|
||||
// corrupted. So we need to force the next version update to start a
|
||||
// new manifest file.
|
||||
descriptor_log_.reset();
|
||||
new_desc_log_ptr.reset();
|
||||
// If manifest operations failed, then we know the CURRENT file still
|
||||
// points to the original MANIFEST. Therefore, we can safely delete the
|
||||
// new MANIFEST.
|
||||
@@ -5799,7 +5803,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
|
||||
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
|
||||
// also exists.
|
||||
if (new_descriptor_log && !manifest_io_status.ok()) {
|
||||
if (!manifest_io_status.ok() && new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Deleting manifest %" PRIu64 " current manifest %" PRIu64
|
||||
"\n",
|
||||
@@ -6080,7 +6084,8 @@ Status VersionSet::Recover(
|
||||
VersionEditHandler handler(
|
||||
read_only, column_families, const_cast<VersionSet*>(this),
|
||||
/*track_found_and_missing_files=*/false, no_error_if_files_missing,
|
||||
io_tracer_, read_options, EpochNumberRequirement::kMightMissing);
|
||||
io_tracer_, read_options, /*allow_incomplete_valid_version=*/false,
|
||||
EpochNumberRequirement::kMightMissing);
|
||||
handler.Iterate(reader, &log_read_status);
|
||||
s = handler.status();
|
||||
if (s.ok()) {
|
||||
@@ -6256,7 +6261,8 @@ Status VersionSet::TryRecoverFromOneManifest(
|
||||
/*checksum=*/true, /*log_num=*/0);
|
||||
VersionEditHandlerPointInTime handler_pit(
|
||||
read_only, column_families, const_cast<VersionSet*>(this), io_tracer_,
|
||||
read_options, EpochNumberRequirement::kMightMissing);
|
||||
read_options, /*allow_incomplete_valid_version=*/true,
|
||||
EpochNumberRequirement::kMightMissing);
|
||||
|
||||
handler_pit.Iterate(reader, &s);
|
||||
|
||||
@@ -7078,8 +7084,8 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
// that will be initialized to where CompactionMergingIterator stores
|
||||
// pointer to its range tombstones. This is used by LevelIterator
|
||||
// to update pointer to range tombstones as it traverse different SST files.
|
||||
std::vector<
|
||||
std::pair<TruncatedRangeDelIterator*, TruncatedRangeDelIterator***>>
|
||||
std::vector<std::pair<std::unique_ptr<TruncatedRangeDelIterator>,
|
||||
std::unique_ptr<TruncatedRangeDelIterator>**>>
|
||||
range_tombstones;
|
||||
size_t num = 0;
|
||||
for (size_t which = 0; which < c->num_input_levels(); which++) {
|
||||
@@ -7101,7 +7107,8 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
*end, fmd.smallest.user_key()) < 0) {
|
||||
continue;
|
||||
}
|
||||
TruncatedRangeDelIterator* range_tombstone_iter = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator> range_tombstone_iter =
|
||||
nullptr;
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, file_options_compactions,
|
||||
cfd->internal_comparator(), fmd, range_del_agg,
|
||||
@@ -7118,11 +7125,13 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
/*range_del_read_seqno=*/nullptr,
|
||||
/*range_del_iter=*/&range_tombstone_iter);
|
||||
range_tombstones.emplace_back(range_tombstone_iter, nullptr);
|
||||
range_tombstones.emplace_back(std::move(range_tombstone_iter),
|
||||
nullptr);
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
TruncatedRangeDelIterator*** tombstone_iter_ptr = nullptr;
|
||||
std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr =
|
||||
nullptr;
|
||||
list[num++] = new LevelIterator(
|
||||
cfd->table_cache(), read_options, file_options_compactions,
|
||||
cfd->internal_comparator(), c->input_levels(which),
|
||||
@@ -7474,7 +7483,7 @@ Status ReactiveVersionSet::ReadAndApply(
|
||||
*cfds_changed = std::move(manifest_tailer_->GetUpdatedColumnFamilies());
|
||||
}
|
||||
if (files_to_delete) {
|
||||
*files_to_delete = std::move(manifest_tailer_->GetIntermediateFiles());
|
||||
*files_to_delete = manifest_tailer_->GetAndClearIntermediateFiles();
|
||||
}
|
||||
|
||||
return s;
|
||||
|
||||
@@ -1503,7 +1503,6 @@ class VersionSet {
|
||||
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata);
|
||||
|
||||
void AddObsoleteBlobFile(uint64_t blob_file_number, std::string path) {
|
||||
// TODO: Erase file from BlobFileCache?
|
||||
obsolete_blob_files_.emplace_back(blob_file_number, std::move(path));
|
||||
}
|
||||
|
||||
|
||||
+313
-59
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -1345,18 +1346,27 @@ class VersionSetTestBase {
|
||||
std::string key; // the only key
|
||||
int level = 0;
|
||||
uint64_t epoch_number;
|
||||
bool file_missing = false;
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber;
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key,
|
||||
uint64_t _epoch_number = kUnknownEpochNumber)
|
||||
: SstInfo(file_num, cf_name, _key, 0, _epoch_number) {}
|
||||
uint64_t _epoch_number = kUnknownEpochNumber,
|
||||
bool _file_missing = false,
|
||||
uint64_t _oldest_blob_file_number = kInvalidBlobFileNumber)
|
||||
: SstInfo(file_num, cf_name, _key, 0, _epoch_number, _file_missing,
|
||||
_oldest_blob_file_number) {}
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key, int lvl,
|
||||
uint64_t _epoch_number = kUnknownEpochNumber)
|
||||
uint64_t _epoch_number = kUnknownEpochNumber,
|
||||
bool _file_missing = false,
|
||||
uint64_t _oldest_blob_file_number = kInvalidBlobFileNumber)
|
||||
: file_number(file_num),
|
||||
column_family(cf_name),
|
||||
key(_key),
|
||||
level(lvl),
|
||||
epoch_number(_epoch_number) {}
|
||||
epoch_number(_epoch_number),
|
||||
file_missing(_file_missing),
|
||||
oldest_blob_file_number(_oldest_blob_file_number) {}
|
||||
};
|
||||
|
||||
// Create dummy sst, return their metadata. Note that only file name and size
|
||||
@@ -1395,22 +1405,32 @@ class VersionSetTestBase {
|
||||
ASSERT_NE(0, file_size);
|
||||
file_metas->emplace_back(
|
||||
file_num, /*file_path_id=*/0, file_size, ikey, ikey, 0, 0, false,
|
||||
Temperature::kUnknown, 0, 0, 0, info.epoch_number,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
0, 0, /* user_defined_timestamps_persisted */ true);
|
||||
Temperature::kUnknown, info.oldest_blob_file_number, 0, 0,
|
||||
info.epoch_number, kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kNullUniqueId64x2, 0, 0,
|
||||
/* user_defined_timestamps_persisted */ true);
|
||||
if (info.file_missing) {
|
||||
ASSERT_OK(fs_->DeleteFile(fname, IOOptions(), nullptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateCurrentFile() {
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown,
|
||||
/* dir_contains_current_file */ nullptr));
|
||||
}
|
||||
|
||||
// Create DB with 3 column families.
|
||||
void NewDB() {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
PrepareManifest(&column_families_, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families_, false));
|
||||
EXPECT_EQ(column_families_.size(),
|
||||
@@ -2586,7 +2606,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) {
|
||||
@@ -2598,7 +2618,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupCorruptedAtomicGroup(int atomic_group_size) {
|
||||
@@ -2612,7 +2632,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncorrectAtomicGroup(int atomic_group_size) {
|
||||
@@ -2628,7 +2648,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupTestSyncPoints() {
|
||||
@@ -3394,8 +3414,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
EXPECT_EQ(column_families.size(),
|
||||
@@ -3417,7 +3436,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
cfd_to_drop->Ref();
|
||||
drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID());
|
||||
mutex_.Lock();
|
||||
s = versions_->LogAndApply(
|
||||
Status s = versions_->LogAndApply(
|
||||
cfd_to_drop, *cfd_to_drop->GetLatestMutableCFOptions(), read_options,
|
||||
write_options, &drop_cf_edit, &mutex_, nullptr);
|
||||
mutex_.Unlock();
|
||||
@@ -3527,9 +3546,7 @@ class EmptyDefaultCfNewManifest : public VersionSetTestBase,
|
||||
TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -3538,7 +3555,7 @@ TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
cf_options_);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
@@ -3559,7 +3576,8 @@ class VersionSetTestEmptyDb
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
|
||||
Temperature::kUnknown));
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -3592,9 +3610,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3609,9 +3625,9 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
@@ -3637,9 +3653,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3685,9 +3699,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3744,9 +3756,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3802,9 +3812,7 @@ TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3869,8 +3877,9 @@ INSTANTIATE_TEST_CASE_P(
|
||||
class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
VersionSetTestMissingFiles()
|
||||
: VersionSetTestBase("version_set_test_missing_files"),
|
||||
explicit VersionSetTestMissingFiles(
|
||||
const std::string& test_name = "version_set_test_missing_files")
|
||||
: VersionSetTestBase(test_name),
|
||||
internal_comparator_(
|
||||
std::make_shared<InternalKeyComparator>(options_.comparator)) {}
|
||||
|
||||
@@ -3947,7 +3956,8 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
// This method updates last_sequence_.
|
||||
void WriteFileAdditionAndDeletionToManifest(
|
||||
uint32_t cf, const std::vector<std::pair<int, FileMetaData>>& added_files,
|
||||
const std::vector<std::pair<int, uint64_t>>& deleted_files) {
|
||||
const std::vector<std::pair<int, uint64_t>>& deleted_files,
|
||||
const std::vector<BlobFileAddition>& blob_files = {}) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cf);
|
||||
for (const auto& elem : added_files) {
|
||||
@@ -3958,6 +3968,9 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
int level = elem.first;
|
||||
edit.DeleteFile(level, elem.second);
|
||||
}
|
||||
for (const auto& elem : blob_files) {
|
||||
edit.AddBlobFile(elem);
|
||||
}
|
||||
edit.SetLastSequence(last_seqno_);
|
||||
++last_seqno_;
|
||||
assert(log_writer_.get() != nullptr);
|
||||
@@ -4006,15 +4019,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4064,15 +4076,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4118,15 +4129,14 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4171,6 +4181,250 @@ TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) {
|
||||
}
|
||||
}
|
||||
|
||||
class BestEffortsRecoverIncompleteVersionTest
|
||||
: public VersionSetTestMissingFiles {
|
||||
public:
|
||||
BestEffortsRecoverIncompleteVersionTest()
|
||||
: VersionSetTestMissingFiles("best_efforts_recover_incomplete_version") {}
|
||||
|
||||
struct BlobInfo {
|
||||
uint64_t file_number;
|
||||
bool file_missing;
|
||||
std::string key;
|
||||
std::string blob;
|
||||
BlobInfo(uint64_t _file_number, bool _file_missing, std::string _key,
|
||||
std::string _blob)
|
||||
: file_number(_file_number),
|
||||
file_missing(_file_missing),
|
||||
key(_key),
|
||||
blob(_blob) {}
|
||||
};
|
||||
|
||||
void CreateDummyBlobFiles(const std::vector<BlobInfo>& infos,
|
||||
std::vector<BlobFileAddition>* blob_metas) {
|
||||
for (const auto& info : infos) {
|
||||
if (!info.file_missing) {
|
||||
WriteDummyBlobFile(info.file_number, info.key, info.blob);
|
||||
}
|
||||
blob_metas->emplace_back(
|
||||
info.file_number, 1 /*total_blob_count*/,
|
||||
info.key.size() + info.blob.size() /*total_blob_bytes*/,
|
||||
"" /*checksum_method*/, "" /*check_sum_value*/);
|
||||
}
|
||||
}
|
||||
// Creates a test blob file that is valid so it can pass the
|
||||
// `VersionEditHandlerPointInTime::VerifyBlobFile` check.
|
||||
void WriteDummyBlobFile(uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob) {
|
||||
ImmutableOptions options;
|
||||
std::string blob_file_path = BlobFileName(dbname_, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(
|
||||
fs_->NewWritableFile(blob_file_path, FileOptions(), &file, nullptr));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), options.clock));
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), options.clock,
|
||||
/*statistics*/ nullptr, blob_file_number,
|
||||
/*use_fsync*/ true,
|
||||
/*do_flush*/ false);
|
||||
|
||||
constexpr ExpirationRange expiration_range;
|
||||
BlobLogHeader header(/*column_family_id*/ 0, kNoCompression,
|
||||
/*has_ttl*/ false, expiration_range);
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(WriteOptions(), header));
|
||||
std::string compressed_blob;
|
||||
uint64_t key_offset = 0;
|
||||
uint64_t blob_offset = 0;
|
||||
ASSERT_OK(blob_log_writer.AddRecord(WriteOptions(), key, blob, &key_offset,
|
||||
&blob_offset));
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = 1;
|
||||
footer.expiration_range = expiration_range;
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
ASSERT_OK(blob_log_writer.AppendFooter(WriteOptions(), footer,
|
||||
&checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
void RecoverFromManifestWithMissingFiles(
|
||||
const std::vector<std::pair<int, FileMetaData>>& added_files,
|
||||
const std::vector<BlobFileAddition>& blob_files) {
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>(),
|
||||
blob_files);
|
||||
log_writer_.reset();
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BestEffortsRecoverIncompleteVersionTest, NonL0MissingFiles) {
|
||||
std::vector<SstInfo> sst_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a", 1 /* level */,
|
||||
100 /* epoch_number */, true /* file_missing */),
|
||||
SstInfo(101, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
101 /* epoch_number */, false /* file_missing */),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
102 /* epoch_number */, false /* file_missing */),
|
||||
};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(sst_files, &file_metas);
|
||||
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 0; i < sst_files.size(); i++) {
|
||||
const auto& info = sst_files[i];
|
||||
const auto& meta = file_metas[i];
|
||||
added_files.emplace_back(info.level, meta);
|
||||
}
|
||||
RecoverFromManifestWithMissingFiles(added_files,
|
||||
std::vector<BlobFileAddition>());
|
||||
std::vector<uint64_t> all_table_files;
|
||||
std::vector<uint64_t> all_blob_files;
|
||||
versions_->AddLiveFiles(&all_table_files, &all_blob_files);
|
||||
ASSERT_TRUE(all_table_files.empty());
|
||||
}
|
||||
|
||||
TEST_F(BestEffortsRecoverIncompleteVersionTest, MissingNonSuffixL0Files) {
|
||||
std::vector<SstInfo> sst_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a", 1 /* level */,
|
||||
100 /* epoch_number */, false /* file_missing */),
|
||||
SstInfo(101, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
101 /* epoch_number */, true /* file_missing */),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
102 /* epoch_number */, false /* file_missing */),
|
||||
};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(sst_files, &file_metas);
|
||||
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 0; i < sst_files.size(); i++) {
|
||||
const auto& info = sst_files[i];
|
||||
const auto& meta = file_metas[i];
|
||||
added_files.emplace_back(info.level, meta);
|
||||
}
|
||||
RecoverFromManifestWithMissingFiles(added_files,
|
||||
std::vector<BlobFileAddition>());
|
||||
std::vector<uint64_t> all_table_files;
|
||||
std::vector<uint64_t> all_blob_files;
|
||||
versions_->AddLiveFiles(&all_table_files, &all_blob_files);
|
||||
ASSERT_TRUE(all_table_files.empty());
|
||||
}
|
||||
|
||||
TEST_F(BestEffortsRecoverIncompleteVersionTest, MissingBlobFiles) {
|
||||
std::vector<SstInfo> sst_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
100 /* epoch_number */, false /* file_missing */,
|
||||
102 /*oldest_blob_file_number*/),
|
||||
SstInfo(101, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
101 /* epoch_number */, false /* file_missing */,
|
||||
103 /*oldest_blob_file_number*/),
|
||||
};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(sst_files, &file_metas);
|
||||
|
||||
std::vector<BlobInfo> blob_files = {
|
||||
BlobInfo(102, true /*file_missing*/, "a", "blob1"),
|
||||
BlobInfo(103, true /*file_missing*/, "a", "blob2"),
|
||||
};
|
||||
std::vector<BlobFileAddition> blob_meta;
|
||||
CreateDummyBlobFiles(blob_files, &blob_meta);
|
||||
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 0; i < sst_files.size(); i++) {
|
||||
const auto& info = sst_files[i];
|
||||
const auto& meta = file_metas[i];
|
||||
added_files.emplace_back(info.level, meta);
|
||||
}
|
||||
RecoverFromManifestWithMissingFiles(added_files, blob_meta);
|
||||
std::vector<uint64_t> all_table_files;
|
||||
std::vector<uint64_t> all_blob_files;
|
||||
versions_->AddLiveFiles(&all_table_files, &all_blob_files);
|
||||
ASSERT_TRUE(all_table_files.empty());
|
||||
}
|
||||
|
||||
TEST_F(BestEffortsRecoverIncompleteVersionTest, MissingL0SuffixOnly) {
|
||||
std::vector<SstInfo> sst_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a", 1 /* level */,
|
||||
100 /* epoch_number */, false /* file_missing */),
|
||||
SstInfo(101, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
101 /* epoch_number */, false /* file_missing */),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
102 /* epoch_number */, true /* file_missing */),
|
||||
};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(sst_files, &file_metas);
|
||||
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 0; i < sst_files.size(); i++) {
|
||||
const auto& info = sst_files[i];
|
||||
const auto& meta = file_metas[i];
|
||||
added_files.emplace_back(info.level, meta);
|
||||
}
|
||||
RecoverFromManifestWithMissingFiles(added_files,
|
||||
std::vector<BlobFileAddition>());
|
||||
std::vector<uint64_t> all_table_files;
|
||||
std::vector<uint64_t> all_blob_files;
|
||||
versions_->AddLiveFiles(&all_table_files, &all_blob_files);
|
||||
ASSERT_EQ(2, all_table_files.size());
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
ASSERT_EQ(1, vstorage->LevelFiles(0).size());
|
||||
ASSERT_EQ(1, vstorage->LevelFiles(1).size());
|
||||
}
|
||||
|
||||
TEST_F(BestEffortsRecoverIncompleteVersionTest,
|
||||
MissingL0SuffixAndTheirBlobFiles) {
|
||||
std::vector<SstInfo> sst_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a", 1 /* level */,
|
||||
100 /* epoch_number */, false /* file_missing */),
|
||||
SstInfo(101, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
101 /* epoch_number */, false /* file_missing */,
|
||||
103 /*oldest_blob_file_number*/),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
102 /* epoch_number */, true /* file_missing */,
|
||||
104 /*oldest_blob_file_number*/),
|
||||
};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(sst_files, &file_metas);
|
||||
|
||||
std::vector<BlobInfo> blob_files = {
|
||||
BlobInfo(103, false /*file_missing*/, "a", "blob1"),
|
||||
BlobInfo(104, true /*file_missing*/, "a", "blob2"),
|
||||
};
|
||||
std::vector<BlobFileAddition> blob_meta;
|
||||
CreateDummyBlobFiles(blob_files, &blob_meta);
|
||||
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 0; i < sst_files.size(); i++) {
|
||||
const auto& info = sst_files[i];
|
||||
const auto& meta = file_metas[i];
|
||||
added_files.emplace_back(info.level, meta);
|
||||
}
|
||||
RecoverFromManifestWithMissingFiles(added_files, blob_meta);
|
||||
std::vector<uint64_t> all_table_files;
|
||||
std::vector<uint64_t> all_blob_files;
|
||||
versions_->AddLiveFiles(&all_table_files, &all_blob_files);
|
||||
ASSERT_EQ(2, all_table_files.size());
|
||||
ASSERT_EQ(1, all_blob_files.size());
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
ASSERT_EQ(1, vstorage->LevelFiles(0).size());
|
||||
ASSERT_EQ(1, vstorage->LevelFiles(1).size());
|
||||
ASSERT_EQ(1, vstorage->GetBlobFiles().size());
|
||||
}
|
||||
|
||||
class ChargeFileMetadataTest : public DBTestBase {
|
||||
public:
|
||||
ChargeFileMetadataTest()
|
||||
|
||||
+24
-15
@@ -43,16 +43,17 @@ Status WalManager::DeleteFile(const std::string& fname, uint64_t number) {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WalManager::GetSortedWalFiles(VectorWalPtr& files) {
|
||||
Status WalManager::GetSortedWalFiles(VectorWalPtr& files, bool need_seqnos,
|
||||
bool include_archived) {
|
||||
// First get sorted files in db dir, then get sorted files from archived
|
||||
// dir, to avoid a race condition where a log file is moved to archived
|
||||
// dir in between.
|
||||
Status s;
|
||||
// list wal files in main db dir.
|
||||
VectorWalPtr logs;
|
||||
s = GetSortedWalsOfType(wal_dir_, logs, kAliveLogFile);
|
||||
if (!s.ok()) {
|
||||
s = GetSortedWalsOfType(wal_dir_, logs, kAliveLogFile, need_seqnos);
|
||||
|
||||
if (!include_archived || !s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ Status WalManager::GetSortedWalFiles(VectorWalPtr& files) {
|
||||
std::string archivedir = ArchivalDirectory(wal_dir_);
|
||||
Status exists = env_->FileExists(archivedir);
|
||||
if (exists.ok()) {
|
||||
s = GetSortedWalsOfType(archivedir, files, kArchivedLogFile);
|
||||
s = GetSortedWalsOfType(archivedir, files, kArchivedLogFile, need_seqnos);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -249,7 +250,8 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
|
||||
size_t files_del_num = log_files_num - files_keep_num;
|
||||
VectorWalPtr archived_logs;
|
||||
s = GetSortedWalsOfType(archival_dir, archived_logs, kArchivedLogFile);
|
||||
s = GetSortedWalsOfType(archival_dir, archived_logs, kArchivedLogFile,
|
||||
/*need_seqno=*/false);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Unable to get archived WALs from: %s: %s",
|
||||
@@ -284,6 +286,9 @@ void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) {
|
||||
Status s = env_->RenameFile(fname, archived_log_name);
|
||||
// The sync point below is used in (DBTest,TransactionLogIteratorRace)
|
||||
TEST_SYNC_POINT("WalManager::PurgeObsoleteFiles:2");
|
||||
// The sync point below is used in
|
||||
// (CheckPointTest, CheckpointWithArchievedLog)
|
||||
TEST_SYNC_POINT("WalManager::ArchiveWALFile");
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "Move log file %s to %s -- %s\n",
|
||||
fname.c_str(), archived_log_name.c_str(),
|
||||
s.ToString().c_str());
|
||||
@@ -291,7 +296,7 @@ void WalManager::ArchiveWALFile(const std::string& fname, uint64_t number) {
|
||||
|
||||
Status WalManager::GetSortedWalsOfType(const std::string& path,
|
||||
VectorWalPtr& log_files,
|
||||
WalFileType log_type) {
|
||||
WalFileType log_type, bool need_seqnos) {
|
||||
std::vector<std::string> all_files;
|
||||
const Status status = env_->GetChildren(path, &all_files);
|
||||
if (!status.ok()) {
|
||||
@@ -303,13 +308,17 @@ Status WalManager::GetSortedWalsOfType(const std::string& path,
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == kWalFile) {
|
||||
SequenceNumber sequence;
|
||||
Status s = ReadFirstRecord(log_type, number, &sequence);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (sequence == 0) {
|
||||
// empty file
|
||||
continue;
|
||||
if (need_seqnos) {
|
||||
Status s = ReadFirstRecord(log_type, number, &sequence);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (sequence == 0) {
|
||||
// empty file
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
sequence = 0;
|
||||
}
|
||||
|
||||
// Reproduce the race condition where a log file is moved
|
||||
@@ -319,7 +328,7 @@ Status WalManager::GetSortedWalsOfType(const std::string& path,
|
||||
TEST_SYNC_POINT("WalManager::GetSortedWalsOfType:2");
|
||||
|
||||
uint64_t size_bytes;
|
||||
s = env_->GetFileSize(LogFileName(path, number), &size_bytes);
|
||||
Status s = env_->GetFileSize(LogFileName(path, number), &size_bytes);
|
||||
// re-try in case the alive log file has been moved to archive.
|
||||
if (!s.ok() && log_type == kAliveLogFile) {
|
||||
std::string archived_file = ArchivedLogFileName(path, number);
|
||||
|
||||
+3
-2
@@ -49,7 +49,8 @@ class WalManager {
|
||||
wal_in_db_path_(db_options_.IsWalDirSameAsDBPath()),
|
||||
io_tracer_(io_tracer) {}
|
||||
|
||||
Status GetSortedWalFiles(VectorWalPtr& files);
|
||||
Status GetSortedWalFiles(VectorWalPtr& files, bool need_seqnos = true,
|
||||
bool include_archived = true);
|
||||
|
||||
// Allow user to tail transaction log to find all recent changes to the
|
||||
// database that are newer than `seq_number`.
|
||||
@@ -78,7 +79,7 @@ class WalManager {
|
||||
|
||||
private:
|
||||
Status GetSortedWalsOfType(const std::string& path, VectorWalPtr& log_files,
|
||||
WalFileType type);
|
||||
WalFileType type, bool need_seqnos);
|
||||
// Requires: all_logs should be sorted with earliest log file first
|
||||
// Retains all log files in all_logs which contain updates with seq no.
|
||||
// Greater Than or Equal to the requested SequenceNumber.
|
||||
|
||||
+124
-64
@@ -929,15 +929,19 @@ Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::Put(this, cf_id, key, value);
|
||||
s = WriteBatchInternal::Put(this, cf_id, key, value);
|
||||
} else {
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
s = WriteBatchInternal::Put(this, cf_id, SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(&value, 1));
|
||||
}
|
||||
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
return WriteBatchInternal::Put(this, cf_id, SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(&value, 1));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::TimedPut(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -962,7 +966,7 @@ Status WriteBatch::TimedPut(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
|
||||
Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& ts, const Slice& value) {
|
||||
const Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -970,8 +974,12 @@ Status WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
assert(column_family);
|
||||
uint32_t cf_id = column_family->GetID();
|
||||
std::array<Slice, 2> key_with_ts{{key, ts}};
|
||||
return WriteBatchInternal::Put(this, cf_id, SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(&value, 1));
|
||||
s = WriteBatchInternal::Put(this, cf_id, SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(&value, 1));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts.size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::CheckSlicePartsLength(const SliceParts& key,
|
||||
@@ -1039,7 +1047,11 @@ Status WriteBatch::Put(ColumnFamilyHandle* column_family, const SliceParts& key,
|
||||
}
|
||||
|
||||
if (ts_sz == 0) {
|
||||
return WriteBatchInternal::Put(this, cf_id, key, value);
|
||||
s = WriteBatchInternal::Put(this, cf_id, key, value);
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::InvalidArgument(
|
||||
@@ -1246,20 +1258,24 @@ Status WriteBatch::Delete(ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::Delete(this, cf_id, key);
|
||||
s = WriteBatchInternal::Delete(this, cf_id, key);
|
||||
} else {
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
s = WriteBatchInternal::Delete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
}
|
||||
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
return WriteBatchInternal::Delete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::Delete(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& ts) {
|
||||
const Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1267,8 +1283,12 @@ Status WriteBatch::Delete(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
has_key_with_ts_ = true;
|
||||
uint32_t cf_id = column_family->GetID();
|
||||
std::array<Slice, 2> key_with_ts{{key, ts}};
|
||||
return WriteBatchInternal::Delete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
s = WriteBatchInternal::Delete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts.size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::Delete(WriteBatch* b, uint32_t column_family_id,
|
||||
@@ -1313,7 +1333,11 @@ Status WriteBatch::Delete(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::Delete(this, cf_id, key);
|
||||
s = WriteBatchInternal::Delete(this, cf_id, key);
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::InvalidArgument(
|
||||
@@ -1361,20 +1385,24 @@ Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::SingleDelete(this, cf_id, key);
|
||||
s = WriteBatchInternal::SingleDelete(this, cf_id, key);
|
||||
} else {
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
s = WriteBatchInternal::SingleDelete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
}
|
||||
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
return WriteBatchInternal::SingleDelete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& ts) {
|
||||
const Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1382,8 +1410,12 @@ Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
assert(column_family);
|
||||
uint32_t cf_id = column_family->GetID();
|
||||
std::array<Slice, 2> key_with_ts{{key, ts}};
|
||||
return WriteBatchInternal::SingleDelete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
s = WriteBatchInternal::SingleDelete(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts.size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::SingleDelete(WriteBatch* b,
|
||||
@@ -1430,7 +1462,11 @@ Status WriteBatch::SingleDelete(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::SingleDelete(this, cf_id, key);
|
||||
s = WriteBatchInternal::SingleDelete(this, cf_id, key);
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::InvalidArgument(
|
||||
@@ -1480,23 +1516,27 @@ Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::DeleteRange(this, cf_id, begin_key, end_key);
|
||||
s = WriteBatchInternal::DeleteRange(this, cf_id, begin_key, end_key);
|
||||
} else {
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> begin_key_with_ts{{begin_key, dummy_ts}};
|
||||
std::array<Slice, 2> end_key_with_ts{{end_key, dummy_ts}};
|
||||
s = WriteBatchInternal::DeleteRange(this, cf_id,
|
||||
SliceParts(begin_key_with_ts.data(), 2),
|
||||
SliceParts(end_key_with_ts.data(), 2));
|
||||
}
|
||||
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> begin_key_with_ts{{begin_key, dummy_ts}};
|
||||
std::array<Slice, 2> end_key_with_ts{{end_key, dummy_ts}};
|
||||
return WriteBatchInternal::DeleteRange(
|
||||
this, cf_id, SliceParts(begin_key_with_ts.data(), 2),
|
||||
SliceParts(end_key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
const Slice& ts) {
|
||||
const Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1505,9 +1545,13 @@ Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
|
||||
uint32_t cf_id = column_family->GetID();
|
||||
std::array<Slice, 2> key_with_ts{{begin_key, ts}};
|
||||
std::array<Slice, 2> end_key_with_ts{{end_key, ts}};
|
||||
return WriteBatchInternal::DeleteRange(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(end_key_with_ts.data(), 2));
|
||||
s = WriteBatchInternal::DeleteRange(this, cf_id,
|
||||
SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(end_key_with_ts.data(), 2));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts.size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::DeleteRange(WriteBatch* b, uint32_t column_family_id,
|
||||
@@ -1554,7 +1598,11 @@ Status WriteBatch::DeleteRange(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::DeleteRange(this, cf_id, begin_key, end_key);
|
||||
s = WriteBatchInternal::DeleteRange(this, cf_id, begin_key, end_key);
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::InvalidArgument(
|
||||
@@ -1608,21 +1656,25 @@ Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::Merge(this, cf_id, key, value);
|
||||
s = WriteBatchInternal::Merge(this, cf_id, key, value);
|
||||
} else {
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
|
||||
s = WriteBatchInternal::Merge(
|
||||
this, cf_id, SliceParts(key_with_ts.data(), 2), SliceParts(&value, 1));
|
||||
}
|
||||
|
||||
needs_in_place_update_ts_ = true;
|
||||
has_key_with_ts_ = true;
|
||||
std::string dummy_ts(ts_sz, '\0');
|
||||
std::array<Slice, 2> key_with_ts{{key, dummy_ts}};
|
||||
|
||||
return WriteBatchInternal::Merge(
|
||||
this, cf_id, SliceParts(key_with_ts.data(), 2), SliceParts(&value, 1));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& ts, const Slice& value) {
|
||||
const Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
Status s = CheckColumnFamilyTimestampSize(column_family, ts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1630,8 +1682,12 @@ Status WriteBatch::Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
assert(column_family);
|
||||
uint32_t cf_id = column_family->GetID();
|
||||
std::array<Slice, 2> key_with_ts{{key, ts}};
|
||||
return WriteBatchInternal::Merge(
|
||||
this, cf_id, SliceParts(key_with_ts.data(), 2), SliceParts(&value, 1));
|
||||
s = WriteBatchInternal::Merge(this, cf_id, SliceParts(key_with_ts.data(), 2),
|
||||
SliceParts(&value, 1));
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts.size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteBatchInternal::Merge(WriteBatch* b, uint32_t column_family_id,
|
||||
@@ -1680,7 +1736,11 @@ Status WriteBatch::Merge(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
|
||||
if (0 == ts_sz) {
|
||||
return WriteBatchInternal::Merge(this, cf_id, key, value);
|
||||
s = WriteBatchInternal::Merge(this, cf_id, key, value);
|
||||
if (s.ok()) {
|
||||
MaybeTrackTimestampSize(cf_id, ts_sz);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::InvalidArgument(
|
||||
|
||||
@@ -73,16 +73,13 @@ class CfConsistencyStressTest : public StressTest {
|
||||
status = db_->Write(write_opts, &batch);
|
||||
}
|
||||
|
||||
if (IsRetryableInjectedError(status)) {
|
||||
fprintf(stdout, "multi put or merge error: %s\n",
|
||||
status.ToString().c_str());
|
||||
} else if (!status.ok()) {
|
||||
if (status.ok()) {
|
||||
auto num = static_cast<long>(rand_column_families.size());
|
||||
thread->stats.AddBytesForWrites(num, (sz + 1) * num);
|
||||
} else if (!IsErrorInjectedAndRetryable(status)) {
|
||||
fprintf(stderr, "multi put or merge error: %s\n",
|
||||
status.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;
|
||||
@@ -99,13 +96,11 @@ class CfConsistencyStressTest : public StressTest {
|
||||
batch.Delete(cfh, key);
|
||||
}
|
||||
Status s = db_->Write(write_opts, &batch);
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "multidel error: %s\n", s.ToString().c_str());
|
||||
} else if (!s.ok()) {
|
||||
if (s.ok()) {
|
||||
thread->stats.AddDeletes(static_cast<long>(rand_column_families.size()));
|
||||
} else if (!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "multidel error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
} else {
|
||||
thread->stats.AddDeletes(static_cast<long>(rand_column_families.size()));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -130,14 +125,12 @@ class CfConsistencyStressTest : public StressTest {
|
||||
batch.DeleteRange(cfh, key, end_key);
|
||||
}
|
||||
Status s = db_->Write(write_opts, &batch);
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "multi del range error: %s\n", s.ToString().c_str());
|
||||
} else if (!s.ok()) {
|
||||
fprintf(stderr, "multi del range error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
} else {
|
||||
if (s.ok()) {
|
||||
thread->stats.AddRangeDeletions(
|
||||
static_cast<long>(rand_column_families.size()));
|
||||
} else if (!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "multi del range error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -248,9 +241,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
thread->stats.AddGets(1, 1);
|
||||
} else if (s.IsNotFound()) {
|
||||
thread->stats.AddGets(1, 0);
|
||||
} else if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestGet error: %s\n", s.ToString().c_str());
|
||||
} else {
|
||||
} else if (!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "TestGet error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
}
|
||||
@@ -286,9 +277,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
} else if (s.IsNotFound()) {
|
||||
// not found case
|
||||
thread->stats.AddGets(1, 0);
|
||||
} else if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "MultiGet error: %s\n", s.ToString().c_str());
|
||||
} else {
|
||||
} else if (!IsErrorInjectedAndRetryable(s)) {
|
||||
// errors case
|
||||
fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
@@ -324,12 +313,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
PinnableWideColumns result;
|
||||
s = db_->GetEntity(read_opts, cfh, key, &result);
|
||||
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout,
|
||||
"GetEntity error: inconsistent columns for key %s, entity %s\n",
|
||||
StringToHex(key).c_str(),
|
||||
WideColumnsToHex(result.columns()).c_str());
|
||||
} else if (s.ok()) {
|
||||
if (s.ok()) {
|
||||
if (!VerifyWideColumns(result.columns())) {
|
||||
fprintf(
|
||||
stderr,
|
||||
@@ -359,10 +343,10 @@ class CfConsistencyStressTest : public StressTest {
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsNotFound()) {
|
||||
const bool cmp_found = s.ok();
|
||||
|
||||
@@ -517,9 +501,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
thread->stats.AddGets(1, 1);
|
||||
} else if (s.IsNotFound()) {
|
||||
thread->stats.AddGets(1, 0);
|
||||
} else if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestGetEntity error: %s\n", s.ToString().c_str());
|
||||
} else {
|
||||
} else if (!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "TestGetEntity error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
}
|
||||
@@ -590,9 +572,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
for (size_t j = 0; j < num_cfs; ++j) {
|
||||
const Status& s = result[j].status();
|
||||
const WideColumns& columns = result[j].columns();
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestMultiGetEntity (AttributeGroup) error: %s\n",
|
||||
s.ToString().c_str());
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s)) {
|
||||
break;
|
||||
} else if (!s.ok() && !s.IsNotFound()) {
|
||||
fprintf(stderr, "TestMultiGetEntity (AttributeGroup) error: %s\n",
|
||||
@@ -699,9 +679,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
const Status& s = statuses[j];
|
||||
const WideColumns& columns = results[j].columns();
|
||||
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestMultiGetEntity error: %s\n",
|
||||
s.ToString().c_str());
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s)) {
|
||||
break;
|
||||
} else if (!s.ok() && !s.IsNotFound()) {
|
||||
fprintf(stderr, "TestMultiGetEntity error: %s\n",
|
||||
@@ -838,9 +816,7 @@ class CfConsistencyStressTest : public StressTest {
|
||||
s = iter->status();
|
||||
}
|
||||
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestPrefixScan error: %s\n", s.ToString().c_str());
|
||||
} else if (!s.ok()) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
ROCKSDB_NAMESPACE::Env* db_stress_listener_env = nullptr;
|
||||
ROCKSDB_NAMESPACE::Env* db_stress_env = nullptr;
|
||||
// If non-null, injects read error at a rate specified by the
|
||||
// read_fault_one_in or write_fault_one_in flag
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::FaultInjectionTestFS> fault_fs_guard;
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> compressed_secondary_cache;
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::Cache> block_cache;
|
||||
|
||||
@@ -125,7 +125,6 @@ DECLARE_int32(level0_stop_writes_trigger);
|
||||
DECLARE_int32(block_size);
|
||||
DECLARE_int32(format_version);
|
||||
DECLARE_int32(index_block_restart_interval);
|
||||
DECLARE_bool(disable_auto_compactions);
|
||||
DECLARE_int32(max_background_compactions);
|
||||
DECLARE_int32(num_bottom_pri_threads);
|
||||
DECLARE_int32(compaction_thread_pool_adjust_interval);
|
||||
@@ -151,6 +150,7 @@ DECLARE_bool(charge_filter_construction);
|
||||
DECLARE_bool(charge_table_reader);
|
||||
DECLARE_bool(charge_file_metadata);
|
||||
DECLARE_bool(charge_blob_cache);
|
||||
DECLARE_bool(decouple_partitioned_filters);
|
||||
DECLARE_int32(top_level_index_pinning);
|
||||
DECLARE_int32(partition_pinning);
|
||||
DECLARE_int32(unpartitioned_pinning);
|
||||
@@ -197,7 +197,6 @@ DECLARE_int64(target_file_size_base);
|
||||
DECLARE_int32(target_file_size_multiplier);
|
||||
DECLARE_uint64(max_bytes_for_level_base);
|
||||
DECLARE_double(max_bytes_for_level_multiplier);
|
||||
DECLARE_int32(range_deletion_width);
|
||||
DECLARE_uint64(rate_limiter_bytes_per_sec);
|
||||
DECLARE_bool(rate_limit_bg_reads);
|
||||
DECLARE_bool(rate_limit_user_ops);
|
||||
@@ -275,6 +274,7 @@ DECLARE_bool(verification_only);
|
||||
DECLARE_string(last_level_temperature);
|
||||
DECLARE_string(default_write_temperature);
|
||||
DECLARE_string(default_temperature);
|
||||
DECLARE_bool(paranoid_memory_checks);
|
||||
|
||||
// Options for transaction dbs.
|
||||
// Use TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
@@ -319,7 +319,6 @@ DECLARE_int32(prepopulate_blob_cache);
|
||||
DECLARE_int32(approximate_size_one_in);
|
||||
DECLARE_bool(best_efforts_recovery);
|
||||
DECLARE_bool(skip_verifydb);
|
||||
DECLARE_bool(enable_compaction_filter);
|
||||
DECLARE_bool(paranoid_file_checks);
|
||||
DECLARE_bool(fail_if_options_file_error);
|
||||
DECLARE_uint64(batch_protection_bytes_per_key);
|
||||
|
||||
@@ -49,7 +49,7 @@ class DbStressCompactionFilter : public CompactionFilter {
|
||||
return Decision::kKeep;
|
||||
}
|
||||
// Reaching here means we acquired the lock.
|
||||
|
||||
key_mutex->AssertHeld();
|
||||
bool key_exists = state_->Exists(cf_id_, key_num);
|
||||
const bool allow_overwrite = state_->AllowsOverwrite(key_num);
|
||||
|
||||
|
||||
@@ -159,15 +159,18 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
stress->TrackExpectedState(shared);
|
||||
}
|
||||
|
||||
// Since wrie fault and sync fault implementations are coupled with each
|
||||
// other in `TestFSWritableFile()`, we can not enable or disable only one
|
||||
// of the two.
|
||||
// TODO(hx235): decouple implementations of write fault injection and sync
|
||||
// fault injection.
|
||||
if (FLAGS_sync_fault_injection || FLAGS_write_fault_one_in > 0) {
|
||||
fault_fs_guard->SetFilesystemDirectWritable(false);
|
||||
fault_fs_guard->SetInjectUnsyncedDataLoss(FLAGS_sync_fault_injection);
|
||||
if (FLAGS_exclude_wal_from_write_fault_injection) {
|
||||
fault_fs_guard->SetFileTypesExcludedFromWriteFaultInjection(
|
||||
{FileType::kWalFile});
|
||||
}
|
||||
}
|
||||
if (ShouldDisableAutoCompactionsBeforeVerifyDb()) {
|
||||
Status s = stress->EnableAutoCompaction();
|
||||
assert(s.ok());
|
||||
}
|
||||
now = clock->NowMicros();
|
||||
fprintf(stdout, "%s Starting database operations\n",
|
||||
clock->TimeToString(now / 1000000).c_str());
|
||||
|
||||
|
||||
@@ -13,6 +13,20 @@
|
||||
#include "monitoring/thread_status_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
void CheckIOActivity(const IOOptions& options) {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#else
|
||||
(void)options;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
class DbStressRandomAccessFileWrapper : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
explicit DbStressRandomAccessFileWrapper(
|
||||
@@ -83,144 +97,67 @@ class DbStressWritableFileWrapper : public FSWritableFileOwnerWrapper {
|
||||
|
||||
IOStatus Append(const Slice& data, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Append(data, options, dbg);
|
||||
}
|
||||
IOStatus Append(const Slice& data, const IOOptions& options,
|
||||
const DataVerificationInfo& verification_info,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Append(data, options, verification_info, dbg);
|
||||
}
|
||||
IOStatus PositionedAppend(const Slice& data, uint64_t offset,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->PositionedAppend(data, offset, options, dbg);
|
||||
}
|
||||
IOStatus PositionedAppend(const Slice& data, uint64_t offset,
|
||||
const IOOptions& options,
|
||||
const DataVerificationInfo& verification_info,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->PositionedAppend(data, offset, options, verification_info,
|
||||
dbg);
|
||||
}
|
||||
|
||||
IOStatus Truncate(uint64_t size, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Truncate(size, options, dbg);
|
||||
}
|
||||
|
||||
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Close(options, dbg);
|
||||
}
|
||||
|
||||
IOStatus Flush(const IOOptions& options, IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Flush(options, dbg);
|
||||
}
|
||||
|
||||
IOStatus Sync(const IOOptions& options, IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Sync(options, dbg);
|
||||
}
|
||||
|
||||
IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Fsync(options, dbg);
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_FALLOCATE_PRESENT
|
||||
IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->Allocate(offset, len, options, dbg);
|
||||
}
|
||||
#endif
|
||||
|
||||
IOStatus RangeSync(uint64_t offset, uint64_t nbytes, const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
#ifndef NDEBUG
|
||||
const ThreadStatus::OperationType thread_op =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
Env::IOActivity io_activity =
|
||||
ThreadStatusUtil::TEST_GetExpectedIOActivity(thread_op);
|
||||
assert(io_activity == Env::IOActivity::kUnknown ||
|
||||
io_activity == options.io_activity);
|
||||
#endif
|
||||
CheckIOActivity(options);
|
||||
return target()->RangeSync(offset, nbytes, options, dbg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -380,6 +380,11 @@ DEFINE_bool(charge_blob_cache, false,
|
||||
"CacheEntryRoleOptions::charged of "
|
||||
"kBlobCache");
|
||||
|
||||
DEFINE_bool(
|
||||
decouple_partitioned_filters,
|
||||
ROCKSDB_NAMESPACE::BlockBasedTableOptions().decouple_partitioned_filters,
|
||||
"Decouple filter partitioning from index partitioning.");
|
||||
|
||||
DEFINE_int32(
|
||||
top_level_index_pinning,
|
||||
static_cast<int32_t>(ROCKSDB_NAMESPACE::PinningTier::kFallback),
|
||||
@@ -1095,6 +1100,9 @@ DEFINE_int32(write_fault_one_in, 0,
|
||||
"On non-zero, enables fault injection on write. Currently only"
|
||||
"injects write error when writing to SST files.");
|
||||
|
||||
DEFINE_bool(exclude_wal_from_write_fault_injection, false,
|
||||
"If true, we won't inject write fault when writing to WAL file");
|
||||
|
||||
DEFINE_int32(metadata_write_fault_one_in, 1000,
|
||||
"On non-zero, enables fault injection on metadata write (i.e, "
|
||||
"directory and file metadata write)");
|
||||
@@ -1440,4 +1448,8 @@ DEFINE_uint32(uncache_aggressiveness,
|
||||
"obsolete. 0 = disabled, 1 = minimum, 100 = moderate, 10000 = "
|
||||
"normal max");
|
||||
|
||||
DEFINE_bool(paranoid_memory_checks,
|
||||
ROCKSDB_NAMESPACE::Options().paranoid_memory_checks,
|
||||
"Sets CF option paranoid_memory_checks.");
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -72,14 +72,7 @@ class DbStressListener : public EventListener {
|
||||
// pretending doing some work here
|
||||
RandomSleep();
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,14 +173,7 @@ class DbStressListener : public EventListener {
|
||||
|
||||
void OnSubcompactionCompleted(const SubcompactionJobInfo& /* si */) override {
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,33 +260,20 @@ class DbStressListener : public EventListener {
|
||||
bool* /* auto_recovery */) override {
|
||||
RandomSleep();
|
||||
if (FLAGS_error_recovery_with_no_fault_injection && fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
// TODO(hx235): only exempt the flush thread during error recovery instead
|
||||
// of all the flush threads from error injection
|
||||
fault_fs_guard->SetIOActivtiesExemptedFromFaultInjection(
|
||||
fault_fs_guard->SetIOActivtiesExcludedFromFaultInjection(
|
||||
{Env::IOActivity::kFlush});
|
||||
}
|
||||
}
|
||||
|
||||
void OnErrorRecoveryCompleted(Status /* old_bg_error */) override {
|
||||
void OnErrorRecoveryEnd(
|
||||
const BackgroundErrorRecoveryInfo& /*info*/) override {
|
||||
RandomSleep();
|
||||
if (FLAGS_error_recovery_with_no_fault_injection && fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->SetIOActivtiesExemptedFromFaultInjection({});
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
fault_fs_guard->SetIOActivtiesExcludedFromFaultInjection({});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,18 +31,22 @@ DECLARE_int32(compaction_thread_pool_adjust_interval);
|
||||
DECLARE_int32(continuous_verification_interval);
|
||||
DECLARE_bool(error_recovery_with_no_fault_injection);
|
||||
DECLARE_bool(sync_fault_injection);
|
||||
DECLARE_int32(range_deletion_width);
|
||||
DECLARE_bool(disable_wal);
|
||||
DECLARE_int32(manual_wal_flush_one_in);
|
||||
DECLARE_int32(metadata_read_fault_one_in);
|
||||
DECLARE_int32(metadata_write_fault_one_in);
|
||||
DECLARE_int32(read_fault_one_in);
|
||||
DECLARE_int32(write_fault_one_in);
|
||||
DECLARE_bool(exclude_wal_from_write_fault_injection);
|
||||
DECLARE_int32(open_metadata_read_fault_one_in);
|
||||
DECLARE_int32(open_metadata_write_fault_one_in);
|
||||
DECLARE_int32(open_write_fault_one_in);
|
||||
DECLARE_int32(open_read_fault_one_in);
|
||||
|
||||
DECLARE_int32(inject_error_severity);
|
||||
DECLARE_bool(disable_auto_compactions);
|
||||
DECLARE_bool(enable_compaction_filter);
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class StressTest;
|
||||
@@ -146,7 +150,8 @@ class SharedState {
|
||||
|
||||
~SharedState() {
|
||||
#ifndef NDEBUG
|
||||
if (FLAGS_read_fault_one_in) {
|
||||
if (FLAGS_read_fault_one_in || FLAGS_write_fault_one_in ||
|
||||
FLAGS_metadata_write_fault_one_in) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -259,8 +264,8 @@ class SharedState {
|
||||
// This is useful for crash-recovery testing when the process may crash
|
||||
// before updating the corresponding expected value
|
||||
//
|
||||
// Requires external locking covering `key` in `cf` to prevent concurrent
|
||||
// write or delete to the same `key`.
|
||||
// Requires external locking covering `key` in `cf` to prevent
|
||||
// concurrent write or delete to the same `key`.
|
||||
PendingExpectedValue PreparePut(int cf, int64_t key) {
|
||||
return expected_state_manager_->PreparePut(cf, key);
|
||||
}
|
||||
|
||||
@@ -103,12 +103,16 @@ StressTest::StressTest()
|
||||
}
|
||||
}
|
||||
|
||||
StressTest::~StressTest() {
|
||||
void StressTest::CleanUp() {
|
||||
for (auto cf : column_families_) {
|
||||
delete cf;
|
||||
}
|
||||
column_families_.clear();
|
||||
if (db_) {
|
||||
db_->Close();
|
||||
}
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
|
||||
for (auto* cf : cmp_cfhs_) {
|
||||
delete cf;
|
||||
@@ -286,19 +290,12 @@ bool StressTest::BuildOptionsTable() {
|
||||
std::to_string(options_.write_buffer_size / 8),
|
||||
}},
|
||||
{"memtable_huge_page_size", {"0", std::to_string(2 * 1024 * 1024)}},
|
||||
{"max_successive_merges", {"0", "2", "4"}},
|
||||
{"strict_max_successive_merges", {"false", "true"}},
|
||||
{"inplace_update_num_locks", {"100", "200", "300"}},
|
||||
// TODO: re-enable once internal task T124324915 is fixed.
|
||||
// {"experimental_mempurge_threshold", {"0.0", "1.0"}},
|
||||
// TODO(ljin): enable test for this option
|
||||
// {"disable_auto_compactions", {"100", "200", "300"}},
|
||||
{"level0_file_num_compaction_trigger",
|
||||
{
|
||||
std::to_string(options_.level0_file_num_compaction_trigger),
|
||||
std::to_string(options_.level0_file_num_compaction_trigger + 2),
|
||||
std::to_string(options_.level0_file_num_compaction_trigger + 4),
|
||||
}},
|
||||
{"level0_slowdown_writes_trigger",
|
||||
{
|
||||
std::to_string(options_.level0_slowdown_writes_trigger),
|
||||
@@ -343,6 +340,35 @@ bool StressTest::BuildOptionsTable() {
|
||||
}},
|
||||
{"max_sequential_skip_in_iterations", {"4", "8", "12"}},
|
||||
};
|
||||
if (FLAGS_compaction_style == kCompactionStyleUniversal &&
|
||||
FLAGS_universal_max_read_amp > 0) {
|
||||
// level0_file_num_compaction_trigger needs to be at most max_read_amp
|
||||
options_tbl.emplace(
|
||||
"level0_file_num_compaction_trigger",
|
||||
std::vector<std::string>{
|
||||
std::to_string(options_.level0_file_num_compaction_trigger),
|
||||
std::to_string(
|
||||
std::min(options_.level0_file_num_compaction_trigger + 2,
|
||||
FLAGS_universal_max_read_amp)),
|
||||
std::to_string(
|
||||
std::min(options_.level0_file_num_compaction_trigger + 4,
|
||||
FLAGS_universal_max_read_amp)),
|
||||
});
|
||||
} else {
|
||||
options_tbl.emplace(
|
||||
"level0_file_num_compaction_trigger",
|
||||
std::vector<std::string>{
|
||||
std::to_string(options_.level0_file_num_compaction_trigger),
|
||||
std::to_string(options_.level0_file_num_compaction_trigger + 2),
|
||||
std::to_string(options_.level0_file_num_compaction_trigger + 4),
|
||||
});
|
||||
}
|
||||
if (FLAGS_unordered_write) {
|
||||
options_tbl.emplace("max_successive_merges", std::vector<std::string>{"0"});
|
||||
} else {
|
||||
options_tbl.emplace("max_successive_merges",
|
||||
std::vector<std::string>{"0", "2", "4"});
|
||||
}
|
||||
|
||||
if (FLAGS_allow_setting_blob_options_dynamically) {
|
||||
options_tbl.emplace("enable_blob_files",
|
||||
@@ -436,7 +462,7 @@ void StressTest::FinishInitDb(SharedState* shared) {
|
||||
void StressTest::TrackExpectedState(SharedState* shared) {
|
||||
// When data loss is simulated, recovery from potential data loss is a prefix
|
||||
// recovery that requires tracing
|
||||
if (MightHaveDataLoss() && IsStateTracked()) {
|
||||
if (MightHaveUnsyncedDataLoss() && IsStateTracked()) {
|
||||
Status s = shared->SaveAtAndAfter(db_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error enabling history tracing: %s\n",
|
||||
@@ -516,9 +542,7 @@ void StressTest::ProcessStatus(SharedState* shared, std::string opname,
|
||||
if (s.ok()) {
|
||||
return;
|
||||
}
|
||||
if (ignore_injected_error && IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "%s failed: %s\n", opname.c_str(), s.ToString().c_str());
|
||||
} else {
|
||||
if (!ignore_injected_error || !IsErrorInjectedAndRetryable(s)) {
|
||||
std::ostringstream oss;
|
||||
oss << opname << " failed: " << s.ToString();
|
||||
VerificationAbort(shared, oss.str());
|
||||
@@ -608,7 +632,6 @@ void StressTest::PreloadDbAndReopenAsReadOnly(int64_t number_of_keys,
|
||||
for (auto cfh : column_families_) {
|
||||
for (int64_t k = 0; k != number_of_keys; ++k) {
|
||||
const std::string key = Key(k);
|
||||
|
||||
PendingExpectedValue pending_expected_value =
|
||||
shared->PreparePut(cf_idx, k);
|
||||
const uint32_t value_base = pending_expected_value.GetFinalValueBase();
|
||||
@@ -987,26 +1010,12 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
thread->rand.OneIn(FLAGS_verify_db_one_in)) {
|
||||
// Temporarily disable error injection for verification
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
ContinuouslyVerifyDb(thread);
|
||||
// Enable back error injection disabled for verification
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
if (thread->shared->ShouldStopTest()) {
|
||||
break;
|
||||
@@ -1018,10 +1027,8 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
if (thread->rand.OneInOpt(FLAGS_manual_wal_flush_one_in)) {
|
||||
bool sync = thread->rand.OneIn(2) ? true : false;
|
||||
Status s = db_->FlushWAL(sync);
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "FlushWAL(sync=%s) failed: %s\n",
|
||||
(sync ? "true" : "false"), s.ToString().c_str());
|
||||
} else if (!s.ok() && !(sync && s.IsNotSupported())) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s) &&
|
||||
!(sync && s.IsNotSupported())) {
|
||||
fprintf(stderr, "FlushWAL(sync=%s) failed: %s\n",
|
||||
(sync ? "true" : "false"), s.ToString().c_str());
|
||||
}
|
||||
@@ -1029,17 +1036,12 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_lock_wal_one_in)) {
|
||||
Status s = db_->LockWAL();
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "LockWAL() failed: %s\n", s.ToString().c_str());
|
||||
} else if (!s.ok()) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "LockWAL() failed: %s\n", s.ToString().c_str());
|
||||
} else {
|
||||
} else if (s.ok()) {
|
||||
// Temporarily disable error injection for verification
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
// Verify no writes during LockWAL
|
||||
@@ -1094,17 +1096,14 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
|
||||
// Enable back error injection disabled for verification
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_sync_wal_one_in)) {
|
||||
Status s = db_->SyncWAL();
|
||||
if (!s.ok() && !s.IsNotSupported() && !IsRetryableInjectedError(s)) {
|
||||
if (!s.ok() && !s.IsNotSupported() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "SyncWAL() failed: %s\n", s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
@@ -1144,8 +1143,12 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
ProcessStatus(shared, "GetLiveFiles", s_1);
|
||||
Status s_2 = TestGetLiveFilesMetaData();
|
||||
ProcessStatus(shared, "GetLiveFilesMetaData", s_2);
|
||||
Status s_3 = TestGetLiveFilesStorageInfo();
|
||||
ProcessStatus(shared, "GetLiveFilesStorageInfo", s_3);
|
||||
// TODO: enable again after making `GetLiveFilesStorageInfo()`
|
||||
// compatible with `Options::recycle_log_file_num`
|
||||
if (FLAGS_recycle_log_file_num == 0) {
|
||||
Status s_3 = TestGetLiveFilesStorageInfo();
|
||||
ProcessStatus(shared, "GetLiveFilesStorageInfo", s_3);
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_get_all_column_family_metadata_one_in)) {
|
||||
@@ -1202,7 +1205,18 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_get_property_one_in)) {
|
||||
// TestGetProperty doesn't return status for us to tell whether it has
|
||||
// failed due to injected error. So we disable fault injection to avoid
|
||||
// false positive
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
TestGetProperty(thread);
|
||||
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
|
||||
if (thread->rand.OneInOpt(FLAGS_get_properties_of_all_tables_one_in)) {
|
||||
@@ -1229,17 +1243,15 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
}
|
||||
|
||||
if (total_size <= FLAGS_backup_max_size) {
|
||||
// TODO(hx235): enable metadata error injection with
|
||||
// TODO(hx235): enable error injection with
|
||||
// backup/restore after fixing the various issues it surfaces
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
Status s = TestBackupRestore(thread, rand_column_families, rand_keys);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
ProcessStatus(shared, "Backup/restore", s);
|
||||
}
|
||||
}
|
||||
@@ -1275,7 +1287,13 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
if (thread->rand.OneInOpt(FLAGS_key_may_exist_one_in)) {
|
||||
TestKeyMayExist(thread, read_opts, rand_column_families, rand_keys);
|
||||
}
|
||||
|
||||
// Prefix-recoverability relies on tracing successful user writes.
|
||||
// Currently we trace all user writes regardless of whether it later
|
||||
// succeeds or not. To simplify, we disable any fault injection during
|
||||
// user write.
|
||||
// TODO(hx235): support tracing user writes with fault injection.
|
||||
bool disable_fault_injection_during_user_write =
|
||||
fault_fs_guard && MightHaveUnsyncedDataLoss();
|
||||
int prob_op = thread->rand.Uniform(100);
|
||||
// Reset this in case we pick something other than a read op. We don't
|
||||
// want to use a stale value when deciding at the beginning of the loop
|
||||
@@ -1334,16 +1352,34 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
} else if (prob_op < write_bound) {
|
||||
assert(prefix_bound <= prob_op);
|
||||
// OPERATION write
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
TestPut(thread, write_opts, read_opts, rand_column_families, rand_keys,
|
||||
value);
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
} else if (prob_op < del_bound) {
|
||||
assert(write_bound <= prob_op);
|
||||
// OPERATION delete
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
TestDelete(thread, write_opts, rand_column_families, rand_keys);
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
} else if (prob_op < delrange_bound) {
|
||||
assert(del_bound <= prob_op);
|
||||
// OPERATION delete range
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
TestDeleteRange(thread, write_opts, rand_column_families, rand_keys);
|
||||
if (disable_fault_injection_during_user_write) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
} else if (prob_op < iterate_bound) {
|
||||
assert(delrange_bound <= prob_op);
|
||||
// OPERATION iterate
|
||||
@@ -1387,14 +1423,7 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
@@ -1674,7 +1703,7 @@ Status StressTest::TestIterateImpl(ThreadState* thread,
|
||||
if (!(FLAGS_use_txn && FLAGS_txn_write_policy != 0 /* write committed */) &&
|
||||
!FLAGS_use_multi_cf_iterator && thread->rand.OneIn(4)) {
|
||||
Status s = iter->Refresh(snapshot_guard.snapshot());
|
||||
if (!s.ok() && IsRetryableInjectedError(s)) {
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s)) {
|
||||
return s;
|
||||
}
|
||||
assert(s.ok());
|
||||
@@ -1704,10 +1733,13 @@ Status StressTest::TestIterateImpl(ThreadState* thread,
|
||||
op_logs += "S " + key.ToString(true) + " ";
|
||||
}
|
||||
|
||||
if (IsRetryableInjectedError(iter->status()) ||
|
||||
IsRetryableInjectedError(cmp_iter->status())) {
|
||||
return !iter->status().ok() ? iter->status() : cmp_iter->status();
|
||||
if (!iter->status().ok() && IsErrorInjectedAndRetryable(iter->status())) {
|
||||
return iter->status();
|
||||
} else if (!cmp_iter->status().ok() &&
|
||||
IsErrorInjectedAndRetryable(cmp_iter->status())) {
|
||||
return cmp_iter->status();
|
||||
}
|
||||
|
||||
VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op,
|
||||
key, op_logs, verify_func, &diverged);
|
||||
|
||||
@@ -1732,10 +1764,13 @@ Status StressTest::TestIterateImpl(ThreadState* thread,
|
||||
|
||||
last_op = kLastOpNextOrPrev;
|
||||
|
||||
if (IsRetryableInjectedError(iter->status()) ||
|
||||
IsRetryableInjectedError(cmp_iter->status())) {
|
||||
return !iter->status().ok() ? iter->status() : cmp_iter->status();
|
||||
if (!iter->status().ok() && IsErrorInjectedAndRetryable(iter->status())) {
|
||||
return iter->status();
|
||||
} else if (!cmp_iter->status().ok() &&
|
||||
IsErrorInjectedAndRetryable(cmp_iter->status())) {
|
||||
return cmp_iter->status();
|
||||
}
|
||||
|
||||
VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op,
|
||||
key, op_logs, verify_func, &diverged);
|
||||
}
|
||||
@@ -2269,17 +2304,10 @@ Status StressTest::TestBackupRestore(
|
||||
|
||||
// Temporarily disable error injection for clean up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
if (s.ok() || IsRetryableInjectedError(s)) {
|
||||
if (s.ok() || IsErrorInjectedAndRetryable(s)) {
|
||||
// Preserve directories on failure, or allowed persistent backup
|
||||
if (!allow_persistent) {
|
||||
s = DestroyDir(db_stress_env, backup_dir);
|
||||
@@ -2289,7 +2317,7 @@ Status StressTest::TestBackupRestore(
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() || IsRetryableInjectedError(s)) {
|
||||
if (s.ok() || IsErrorInjectedAndRetryable(s)) {
|
||||
s = DestroyDir(db_stress_env, restore_dir);
|
||||
if (!s.ok()) {
|
||||
from = "Destroy restore dir";
|
||||
@@ -2298,17 +2326,10 @@ Status StressTest::TestBackupRestore(
|
||||
|
||||
// Enable back error injection disabled for clean up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
if (!s.ok() && !IsRetryableInjectedError(s)) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr,
|
||||
"Failure in %s with: %s under specified BackupEngineOptions: %s, "
|
||||
"CreateBackupOptions: %s, RestoreOptions: %s (Empty string or "
|
||||
@@ -2445,48 +2466,43 @@ Status StressTest::TestCheckpoint(ThreadState* thread,
|
||||
tmp_opts.sst_file_manager.reset();
|
||||
// Temporarily disable error injection for clean-up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
DestroyDB(checkpoint_dir, tmp_opts);
|
||||
// Enable back error injection disabled for clean-up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
Checkpoint* checkpoint = nullptr;
|
||||
Status s = Checkpoint::Create(db_, &checkpoint);
|
||||
if (s.ok()) {
|
||||
s = checkpoint->CreateCheckpoint(checkpoint_dir);
|
||||
if (!s.ok() && !IsRetryableInjectedError(s)) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "Fail to create checkpoint to %s\n",
|
||||
checkpoint_dir.c_str());
|
||||
std::vector<std::string> files;
|
||||
|
||||
// Temporarily disable error injection to print debugging information
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
}
|
||||
|
||||
Status my_s = db_stress_env->GetChildren(checkpoint_dir, &files);
|
||||
|
||||
// Enable back disable error injection disabled for printing debugging
|
||||
// information
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
assert(my_s.ok());
|
||||
|
||||
for (const auto& f : files) {
|
||||
fprintf(stderr, " %s\n", f.c_str());
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
}
|
||||
if (!my_s.ok()) {
|
||||
fprintf(stderr, "Fail to GetChildren under %s due to %s\n",
|
||||
checkpoint_dir.c_str(), my_s.ToString().c_str());
|
||||
} else {
|
||||
for (const auto& f : files) {
|
||||
fprintf(stderr, " %s\n", f.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2563,17 +2579,10 @@ Status StressTest::TestCheckpoint(ThreadState* thread,
|
||||
|
||||
// Temporarily disable error injection for clean-up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
if (!s.ok() && !IsRetryableInjectedError(s)) {
|
||||
if (!s.ok() && !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "A checkpoint operation failed with: %s\n",
|
||||
s.ToString().c_str());
|
||||
} else {
|
||||
@@ -2582,14 +2591,7 @@ Status StressTest::TestCheckpoint(ThreadState* thread,
|
||||
|
||||
// Enable back error injection disabled for clean-up
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -2724,18 +2726,18 @@ void StressTest::TestCompactFiles(ThreadState* thread,
|
||||
auto s = db_->CompactFiles(compact_options, column_family, input_files,
|
||||
static_cast<int>(output_level));
|
||||
if (!s.ok()) {
|
||||
thread->stats.AddNumCompactFilesFailed(1);
|
||||
// TOOD (hx235): allow an exact list of tolerable failures under stress
|
||||
// test
|
||||
bool non_ok_status_allowed =
|
||||
s.IsManualCompactionPaused() || (IsRetryableInjectedError(s)) ||
|
||||
s.IsManualCompactionPaused() || IsErrorInjectedAndRetryable(s) ||
|
||||
s.IsAborted() || s.IsInvalidArgument() || s.IsNotSupported();
|
||||
fprintf(non_ok_status_allowed ? stdout : stderr,
|
||||
"Unable to perform CompactFiles(): %s under specified "
|
||||
"CompactionOptions: %s (Empty string or "
|
||||
"missing field indicates default option or value is used)\n",
|
||||
s.ToString().c_str(), compact_opt_oss.str().c_str());
|
||||
thread->stats.AddNumCompactFilesFailed(1);
|
||||
if (!non_ok_status_allowed) {
|
||||
fprintf(stderr,
|
||||
"Unable to perform CompactFiles(): %s under specified "
|
||||
"CompactionOptions: %s (Empty string or "
|
||||
"missing field indicates default option or value is used)\n",
|
||||
s.ToString().c_str(), compact_opt_oss.str().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
@@ -2759,11 +2761,12 @@ void StressTest::TestPromoteL0(ThreadState* thread,
|
||||
s.ToString().find("VersionBuilder: Cannot delete table file") !=
|
||||
std::string::npos &&
|
||||
s.ToString().find("since it is on level") != std::string::npos);
|
||||
fprintf(non_ok_status_allowed ? stdout : stderr,
|
||||
"Unable to perform PromoteL0(): %s under specified "
|
||||
"target_level: %d.\n",
|
||||
s.ToString().c_str(), target_level);
|
||||
|
||||
if (!non_ok_status_allowed) {
|
||||
fprintf(stderr,
|
||||
"Unable to perform PromoteL0(): %s under specified "
|
||||
"target_level: %d.\n",
|
||||
s.ToString().c_str(), target_level);
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
}
|
||||
@@ -2852,10 +2855,8 @@ void StressTest::TestAcquireSnapshot(ThreadState* thread,
|
||||
// will later read the same key before releasing the snapshot and
|
||||
// verify that the results are the same.
|
||||
Status status_at = db_->Get(ropt, column_family, key, &value_at);
|
||||
if (IsRetryableInjectedError(status_at)) {
|
||||
fprintf(stdout,
|
||||
"Unable to perform Get() during TestAcquireSnapshot(): %s)\n",
|
||||
status_at.ToString().c_str());
|
||||
if (!status_at.ok() && IsErrorInjectedAndRetryable(status_at)) {
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
return;
|
||||
}
|
||||
std::vector<bool>* key_vec = nullptr;
|
||||
@@ -2964,19 +2965,19 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
uint32_t pre_hash = 0;
|
||||
if (thread->rand.OneIn(2)) {
|
||||
// Temporarily disable error injection to for validation
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
|
||||
// Declare a snapshot and compare the data before and after the compaction
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
pre_snapshot = db_->GetSnapshot();
|
||||
pre_hash =
|
||||
GetRangeHash(thread, pre_snapshot, column_family, start_key, end_key);
|
||||
|
||||
// Enable back error injection disabled for validation
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
std::ostringstream compact_range_opt_oss;
|
||||
compact_range_opt_oss << "exclusive_manual_compaction: "
|
||||
@@ -2996,15 +2997,15 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
if (!status.ok()) {
|
||||
// TOOD (hx235): allow an exact list of tolerable failures under stress test
|
||||
bool non_ok_status_allowed =
|
||||
status.IsManualCompactionPaused() || IsRetryableInjectedError(status) ||
|
||||
status.IsAborted() || status.IsInvalidArgument() ||
|
||||
status.IsNotSupported();
|
||||
fprintf(non_ok_status_allowed ? stdout : stderr,
|
||||
"Unable to perform CompactRange(): %s under specified "
|
||||
"CompactRangeOptions: %s (Empty string or "
|
||||
"missing field indicates default option or value is used)\n",
|
||||
status.ToString().c_str(), compact_range_opt_oss.str().c_str());
|
||||
status.IsManualCompactionPaused() ||
|
||||
IsErrorInjectedAndRetryable(status) || status.IsAborted() ||
|
||||
status.IsInvalidArgument() || status.IsNotSupported();
|
||||
if (!non_ok_status_allowed) {
|
||||
fprintf(stderr,
|
||||
"Unable to perform CompactRange(): %s under specified "
|
||||
"CompactRangeOptions: %s (Empty string or "
|
||||
"missing field indicates default option or value is used)\n",
|
||||
status.ToString().c_str(), compact_range_opt_oss.str().c_str());
|
||||
// Fail fast to preserve the DB state.
|
||||
thread->shared->SetVerificationFailure();
|
||||
}
|
||||
@@ -3012,11 +3013,9 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
|
||||
if (pre_snapshot != nullptr) {
|
||||
// Temporarily disable error injection for validation
|
||||
// Declaring a snapshot and compare the data before and after the compaction
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
uint32_t post_hash =
|
||||
GetRangeHash(thread, pre_snapshot, column_family, start_key, end_key);
|
||||
if (pre_hash != post_hash) {
|
||||
@@ -3032,11 +3031,10 @@ void StressTest::TestCompactRange(ThreadState* thread, int64_t rand_key,
|
||||
thread->shared->SetVerificationFailure();
|
||||
}
|
||||
db_->ReleaseSnapshot(pre_snapshot);
|
||||
// Enable back error injection disabled for validation
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
if (fault_fs_guard) {
|
||||
// Enable back error injection disabled for validation
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3415,28 +3413,27 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
// If this is for DB reopen, error injection may have been enabled.
|
||||
// Disable it here in case there is no open fault injection.
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
// TODO; test transaction DB Open with fault injection
|
||||
if (!FLAGS_use_txn) {
|
||||
bool inject_sync_fault = FLAGS_sync_fault_injection;
|
||||
bool inject_open_meta_read_error =
|
||||
FLAGS_open_metadata_read_fault_one_in > 0;
|
||||
bool inject_open_meta_write_error =
|
||||
FLAGS_open_metadata_write_fault_one_in > 0;
|
||||
bool inject_open_read_error = FLAGS_open_read_fault_one_in > 0;
|
||||
bool inject_open_write_error = FLAGS_open_write_fault_one_in > 0;
|
||||
if ((inject_open_meta_read_error || inject_open_meta_write_error ||
|
||||
inject_open_read_error || inject_open_write_error) &&
|
||||
if ((inject_sync_fault || inject_open_meta_read_error ||
|
||||
inject_open_meta_write_error || inject_open_read_error ||
|
||||
inject_open_write_error) &&
|
||||
fault_fs_guard
|
||||
->FileExists(FLAGS_db + "/CURRENT", IOOptions(), nullptr)
|
||||
.ok()) {
|
||||
if (inject_sync_fault || inject_open_write_error) {
|
||||
fault_fs_guard->SetFilesystemDirectWritable(false);
|
||||
fault_fs_guard->SetInjectUnsyncedDataLoss(inject_sync_fault);
|
||||
}
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kMetadataRead,
|
||||
static_cast<uint32_t>(FLAGS_seed),
|
||||
@@ -3460,7 +3457,6 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
|
||||
fault_fs_guard->SetFilesystemDirectWritable(false);
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, static_cast<uint32_t>(FLAGS_seed),
|
||||
FLAGS_open_write_fault_one_in, false /* retryable */,
|
||||
@@ -3495,17 +3491,10 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
}
|
||||
}
|
||||
|
||||
if (inject_open_meta_read_error || inject_open_meta_write_error ||
|
||||
inject_open_read_error || inject_open_write_error) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->SetFilesystemDirectWritable(true);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
if (inject_sync_fault || inject_open_meta_read_error ||
|
||||
inject_open_meta_write_error || inject_open_read_error ||
|
||||
inject_open_write_error) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
|
||||
if (s.ok()) {
|
||||
// Injected errors might happen in background compactions. We
|
||||
@@ -3522,9 +3511,10 @@ void StressTest::Open(SharedState* shared, bool reopen) {
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// After failure to opening a DB due to IO error, retry should
|
||||
// successfully open the DB with correct data if no IO error shows
|
||||
// up.
|
||||
// After failure to opening a DB due to IO error or unsynced data
|
||||
// loss, retry should successfully open the DB with correct data if
|
||||
// no IO error shows up.
|
||||
inject_sync_fault = false;
|
||||
inject_open_meta_read_error = false;
|
||||
inject_open_meta_write_error = false;
|
||||
inject_open_read_error = false;
|
||||
@@ -3684,7 +3674,7 @@ void StressTest::Reopen(ThreadState* thread) {
|
||||
// crash-recovery verification does. Therefore it always expects no data loss
|
||||
// and we should ensure no data loss in testing.
|
||||
// TODO(hx235): eliminate the FlushWAL(true /* sync */)/SyncWAL() below
|
||||
if (!FLAGS_disable_wal) {
|
||||
if (!FLAGS_disable_wal && FLAGS_avoid_flush_during_shutdown) {
|
||||
Status s;
|
||||
if (FLAGS_manual_wal_flush_one_in > 0) {
|
||||
s = db_->FlushWAL(/*sync=*/true);
|
||||
@@ -3721,7 +3711,7 @@ void StressTest::Reopen(ThreadState* thread) {
|
||||
clock_->TimeToString(now / 1000000).c_str(), num_times_reopened_);
|
||||
Open(thread->shared, /*reopen=*/true);
|
||||
|
||||
if (thread->shared->GetStressTest()->MightHaveDataLoss() &&
|
||||
if (thread->shared->GetStressTest()->MightHaveUnsyncedDataLoss() &&
|
||||
IsStateTracked()) {
|
||||
Status s = thread->shared->SaveAtAndAfter(db_);
|
||||
if (!s.ok()) {
|
||||
@@ -3842,6 +3832,10 @@ void CheckAndSetOptionsForUserTimestamp(Options& options) {
|
||||
FLAGS_persist_user_defined_timestamps;
|
||||
}
|
||||
|
||||
bool ShouldDisableAutoCompactionsBeforeVerifyDb() {
|
||||
return !FLAGS_disable_auto_compactions && FLAGS_enable_compaction_filter;
|
||||
}
|
||||
|
||||
bool InitializeOptionsFromFile(Options& options) {
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
@@ -3869,6 +3863,8 @@ void InitializeOptionsFromFlags(
|
||||
const std::shared_ptr<const FilterPolicy>& filter_policy,
|
||||
Options& options) {
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.decouple_partitioned_filters =
|
||||
FLAGS_decouple_partitioned_filters;
|
||||
block_based_options.block_cache = cache;
|
||||
block_based_options.cache_index_and_filter_blocks =
|
||||
FLAGS_cache_index_and_filter_blocks;
|
||||
@@ -3955,7 +3951,11 @@ void InitializeOptionsFromFlags(
|
||||
new WriteBufferManager(FLAGS_db_write_buffer_size, block_cache));
|
||||
}
|
||||
options.memtable_whole_key_filtering = FLAGS_memtable_whole_key_filtering;
|
||||
options.disable_auto_compactions = FLAGS_disable_auto_compactions;
|
||||
if (ShouldDisableAutoCompactionsBeforeVerifyDb()) {
|
||||
options.disable_auto_compactions = true;
|
||||
} else {
|
||||
options.disable_auto_compactions = FLAGS_disable_auto_compactions;
|
||||
}
|
||||
options.max_background_compactions = FLAGS_max_background_compactions;
|
||||
options.max_background_flushes = FLAGS_max_background_flushes;
|
||||
options.compaction_style =
|
||||
@@ -4055,6 +4055,7 @@ void InitializeOptionsFromFlags(
|
||||
options.memtable_protection_bytes_per_key =
|
||||
FLAGS_memtable_protection_bytes_per_key;
|
||||
options.block_protection_bytes_per_key = FLAGS_block_protection_bytes_per_key;
|
||||
options.paranoid_memory_checks = FLAGS_paranoid_memory_checks;
|
||||
|
||||
// Integrated BlobDB
|
||||
options.enable_blob_files = FLAGS_enable_blob_files;
|
||||
@@ -4270,6 +4271,7 @@ void InitializeOptionsGeneral(
|
||||
options.disable_auto_compactions = true;
|
||||
}
|
||||
|
||||
options.table_properties_collector_factories.clear();
|
||||
options.table_properties_collector_factories.emplace_back(
|
||||
std::make_shared<DbStressTablePropertiesCollectorFactory>());
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class StressTest {
|
||||
public:
|
||||
StressTest();
|
||||
|
||||
virtual ~StressTest();
|
||||
virtual ~StressTest() {}
|
||||
|
||||
std::shared_ptr<Cache> NewCache(size_t capacity, int32_t num_shard_bits);
|
||||
|
||||
@@ -44,11 +44,16 @@ class StressTest {
|
||||
virtual void VerifyDb(ThreadState* thread) const = 0;
|
||||
virtual void ContinuouslyVerifyDb(ThreadState* /*thread*/) const = 0;
|
||||
void PrintStatistics();
|
||||
bool MightHaveDataLoss() {
|
||||
return FLAGS_sync_fault_injection || FLAGS_write_fault_one_in > 0 ||
|
||||
FLAGS_metadata_write_fault_one_in > 0 || FLAGS_disable_wal ||
|
||||
bool MightHaveUnsyncedDataLoss() {
|
||||
return FLAGS_sync_fault_injection || FLAGS_disable_wal ||
|
||||
FLAGS_manual_wal_flush_one_in > 0;
|
||||
}
|
||||
Status EnableAutoCompaction() {
|
||||
assert(options_.disable_auto_compactions);
|
||||
Status s = db_->EnableAutoCompaction(column_families_);
|
||||
return s;
|
||||
}
|
||||
void CleanUp();
|
||||
|
||||
protected:
|
||||
static int GetMinInjectedErrorCount(int error_count_1, int error_count_2) {
|
||||
@@ -62,6 +67,55 @@ class StressTest {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateIfInitialWriteFails(Env* db_stress_env, const Status& write_s,
|
||||
Status* initial_write_s,
|
||||
bool* initial_wal_write_may_succeed,
|
||||
uint64_t* wait_for_recover_start_time) {
|
||||
assert(db_stress_env && initial_write_s && initial_wal_write_may_succeed &&
|
||||
wait_for_recover_start_time);
|
||||
// Only update `initial_write_s`, `initial_wal_write_may_succeed` when the
|
||||
// first write fails
|
||||
if (!write_s.ok() && (*initial_write_s).ok()) {
|
||||
*initial_write_s = write_s;
|
||||
*initial_wal_write_may_succeed =
|
||||
!FaultInjectionTestFS::IsFailedToWriteToWALError(*initial_write_s);
|
||||
*wait_for_recover_start_time = db_stress_env->NowMicros();
|
||||
}
|
||||
}
|
||||
|
||||
void PrintWriteRecoveryWaitTimeIfNeeded(Env* db_stress_env,
|
||||
const Status& initial_write_s,
|
||||
bool initial_wal_write_may_succeed,
|
||||
uint64_t wait_for_recover_start_time,
|
||||
const std::string& thread_name) {
|
||||
assert(db_stress_env);
|
||||
bool waited_for_recovery = !initial_write_s.ok() &&
|
||||
IsErrorInjectedAndRetryable(initial_write_s) &&
|
||||
initial_wal_write_may_succeed;
|
||||
if (waited_for_recovery) {
|
||||
uint64_t elapsed_sec =
|
||||
(db_stress_env->NowMicros() - wait_for_recover_start_time) / 1000000;
|
||||
if (elapsed_sec > 10) {
|
||||
fprintf(stdout,
|
||||
"%s thread slept to wait for write recovery for "
|
||||
"%" PRIu64 " seconds\n",
|
||||
thread_name.c_str(), elapsed_sec);
|
||||
}
|
||||
}
|
||||
}
|
||||
void GetDeleteRangeKeyLocks(
|
||||
ThreadState* thread, int rand_column_family, int64_t rand_key,
|
||||
std::vector<std::unique_ptr<MutexLock>>* range_locks) {
|
||||
for (int j = 0; j < FLAGS_range_deletion_width; ++j) {
|
||||
if (j == 0 ||
|
||||
((rand_key + j) & ((1 << FLAGS_log2_keys_per_lock) - 1)) == 0) {
|
||||
range_locks->emplace_back(new MutexLock(
|
||||
thread->shared->GetMutexForKey(rand_column_family, rand_key + j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status AssertSame(DB* db, ColumnFamilyHandle* cf,
|
||||
ThreadState::SnapshotState& snap_state);
|
||||
|
||||
@@ -280,9 +334,11 @@ class StressTest {
|
||||
return Status::NotSupported("TestCustomOperations() must be overridden");
|
||||
}
|
||||
|
||||
bool IsRetryableInjectedError(const Status& s) const {
|
||||
return s.getState() && std::strstr(s.getState(), "inject") &&
|
||||
!status_to_io_status(Status(s)).GetDataLoss();
|
||||
bool IsErrorInjectedAndRetryable(const Status& error_s) const {
|
||||
assert(!error_s.ok());
|
||||
return error_s.getState() &&
|
||||
FaultInjectionTestFS::IsInjectedError(error_s) &&
|
||||
!status_to_io_status(Status(error_s)).GetDataLoss();
|
||||
}
|
||||
|
||||
void ProcessStatus(SharedState* shared, std::string msg, const Status& s,
|
||||
@@ -395,5 +451,6 @@ void InitializeOptionsGeneral(
|
||||
// user-defined timestamp.
|
||||
void CheckAndSetOptionsForUserTimestamp(Options& options);
|
||||
|
||||
bool ShouldDisableAutoCompactionsBeforeVerifyDb();
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // GFLAGS
|
||||
@@ -90,10 +90,10 @@ int db_stress_tool(int argc, char** argv) {
|
||||
FaultInjectionTestFS* fs =
|
||||
new FaultInjectionTestFS(raw_env->GetFileSystem());
|
||||
fault_fs_guard.reset(fs);
|
||||
// Set it to direct writable here to not lose files created during DB open
|
||||
// when no open fault injection is not enabled.
|
||||
// This will be overwritten in StressTest::Open() for open fault injection
|
||||
// and in RunStressTestImpl() for proper write fault injection setup.
|
||||
// Set it to direct writable here to initially bypass any fault injection
|
||||
// during DB open This will correspondingly be overwritten in
|
||||
// StressTest::Open() for open fault injection and in RunStressTestImpl()
|
||||
// for proper fault injection setup.
|
||||
fault_fs_guard->SetFilesystemDirectWritable(true);
|
||||
fault_env_guard =
|
||||
std::make_shared<CompositeEnvWrapper>(raw_env, fault_fs_guard);
|
||||
@@ -362,11 +362,11 @@ int db_stress_tool(int argc, char** argv) {
|
||||
// Initialize the Zipfian pre-calculated array
|
||||
InitializeHotKeyGenerator(FLAGS_hot_key_alpha);
|
||||
shared.reset(new SharedState(db_stress_env, stress.get()));
|
||||
if (RunStressTest(shared.get())) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
bool run_stress_test = RunStressTest(shared.get());
|
||||
// Close DB in CleanUp() before destructor to prevent race between destructor
|
||||
// and operations in listener callbacks (e.g. MultiOpsTxnsStressListener).
|
||||
stress->CleanUp();
|
||||
return run_stress_test ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -34,11 +34,19 @@ void ExpectedState::Precommit(int cf, int64_t key, const ExpectedValue& value) {
|
||||
|
||||
PendingExpectedValue ExpectedState::PreparePut(int cf, int64_t key) {
|
||||
ExpectedValue expected_value = Load(cf, key);
|
||||
|
||||
// Calculate the original expected value
|
||||
const ExpectedValue orig_expected_value = expected_value;
|
||||
|
||||
// Calculate the pending expected value
|
||||
expected_value.Put(true /* pending */);
|
||||
const ExpectedValue pending_expected_value = expected_value;
|
||||
|
||||
// Calculate the final expected value
|
||||
expected_value.Put(false /* pending */);
|
||||
const ExpectedValue final_expected_value = expected_value;
|
||||
|
||||
// Precommit
|
||||
Precommit(cf, key, pending_expected_value);
|
||||
return PendingExpectedValue(&Value(cf, key), orig_expected_value,
|
||||
final_expected_value);
|
||||
@@ -46,21 +54,26 @@ PendingExpectedValue ExpectedState::PreparePut(int cf, int64_t key) {
|
||||
|
||||
ExpectedValue ExpectedState::Get(int cf, int64_t key) { return Load(cf, key); }
|
||||
|
||||
PendingExpectedValue ExpectedState::PrepareDelete(int cf, int64_t key,
|
||||
bool* prepared) {
|
||||
PendingExpectedValue ExpectedState::PrepareDelete(int cf, int64_t key) {
|
||||
ExpectedValue expected_value = Load(cf, key);
|
||||
|
||||
// Calculate the original expected value
|
||||
const ExpectedValue orig_expected_value = expected_value;
|
||||
|
||||
// Calculate the pending expected value
|
||||
bool res = expected_value.Delete(true /* pending */);
|
||||
if (prepared) {
|
||||
*prepared = res;
|
||||
}
|
||||
if (!res) {
|
||||
return PendingExpectedValue(&Value(cf, key), orig_expected_value,
|
||||
orig_expected_value);
|
||||
PendingExpectedValue ret = PendingExpectedValue(
|
||||
&Value(cf, key), orig_expected_value, orig_expected_value);
|
||||
return ret;
|
||||
}
|
||||
const ExpectedValue pending_expected_value = expected_value;
|
||||
|
||||
// Calculate the final expected value
|
||||
expected_value.Delete(false /* pending */);
|
||||
const ExpectedValue final_expected_value = expected_value;
|
||||
|
||||
// Precommit
|
||||
Precommit(cf, key, pending_expected_value);
|
||||
return PendingExpectedValue(&Value(cf, key), orig_expected_value,
|
||||
final_expected_value);
|
||||
@@ -73,16 +86,11 @@ PendingExpectedValue ExpectedState::PrepareSingleDelete(int cf, int64_t key) {
|
||||
std::vector<PendingExpectedValue> ExpectedState::PrepareDeleteRange(
|
||||
int cf, int64_t begin_key, int64_t end_key) {
|
||||
std::vector<PendingExpectedValue> pending_expected_values;
|
||||
|
||||
for (int64_t key = begin_key; key < end_key; ++key) {
|
||||
bool prepared = false;
|
||||
PendingExpectedValue pending_expected_value =
|
||||
PrepareDelete(cf, key, &prepared);
|
||||
if (prepared) {
|
||||
pending_expected_values.push_back(pending_expected_value);
|
||||
} else {
|
||||
pending_expected_value.PermitUnclosedPendingState();
|
||||
}
|
||||
pending_expected_values.push_back(PrepareDelete(cf, key));
|
||||
}
|
||||
|
||||
return pending_expected_values;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,14 +49,13 @@ class ExpectedState {
|
||||
// Does not requires external locking.
|
||||
ExpectedValue Get(int cf, int64_t key);
|
||||
|
||||
// Prepare a Delete that will be started but not finished yet
|
||||
// Prepare a Delete that will be started but not finished yet.
|
||||
// This is useful for crash-recovery testing when the process may crash
|
||||
// before updating the corresponding expected value
|
||||
//
|
||||
// Requires external locking covering `key` in `cf` to prevent concurrent
|
||||
// write or delete to the same `key`.
|
||||
PendingExpectedValue PrepareDelete(int cf, int64_t key,
|
||||
bool* prepared = nullptr);
|
||||
PendingExpectedValue PrepareDelete(int cf, int64_t key);
|
||||
|
||||
// Requires external locking covering `key` in `cf` to prevent concurrent
|
||||
// write or delete to the same `key`.
|
||||
|
||||
@@ -21,7 +21,7 @@ void ExpectedValue::Put(bool pending) {
|
||||
}
|
||||
|
||||
bool ExpectedValue::Delete(bool pending) {
|
||||
if (!Exists()) {
|
||||
if (pending && !Exists()) {
|
||||
return false;
|
||||
}
|
||||
if (pending) {
|
||||
|
||||
@@ -37,7 +37,10 @@ class ExpectedValue {
|
||||
explicit ExpectedValue(uint32_t expected_value)
|
||||
: expected_value_(expected_value) {}
|
||||
|
||||
bool Exists() const { return PendingWrite() || !IsDeleted(); }
|
||||
bool Exists() const {
|
||||
assert(!PendingWrite() && !PendingDelete());
|
||||
return !IsDeleted();
|
||||
}
|
||||
|
||||
uint32_t Read() const { return expected_value_; }
|
||||
|
||||
|
||||
@@ -599,10 +599,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
key.ToString(true).c_str());
|
||||
}
|
||||
}
|
||||
} else if (injected_error_count > 0 && IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "error : Get() returns %s for key: %s.\n",
|
||||
s.ToString().c_str(), key.ToString(true).c_str());
|
||||
} else {
|
||||
} else if (injected_error_count == 0 || !IsErrorInjectedAndRetryable(s)) {
|
||||
thread->shared->SetVerificationFailure();
|
||||
fprintf(stderr, "error : Get() returns %s for key: %s.\n",
|
||||
s.ToString().c_str(), key.ToString(true).c_str());
|
||||
@@ -876,15 +873,10 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
} else if (s.IsMergeInProgress() && use_txn) {
|
||||
// With txn this is sometimes expected.
|
||||
thread->stats.AddGets(1, 1);
|
||||
} else {
|
||||
if (injected_error_count > 0 && IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "MultiGet error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddVerifiedErrors(1);
|
||||
} else {
|
||||
fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
shared->SetVerificationFailure();
|
||||
}
|
||||
} else if (injected_error_count == 0 || !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "MultiGet error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
shared->SetVerificationFailure();
|
||||
}
|
||||
|
||||
// Enable back error injection disbled for checking results
|
||||
@@ -1080,10 +1072,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
StringToHex(key_str).c_str());
|
||||
}
|
||||
}
|
||||
} else if (injected_error_count > 0 && IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "error : GetEntity() returns %s for key: %s.\n",
|
||||
s.ToString().c_str(), StringToHex(key_str).c_str());
|
||||
} else {
|
||||
} else if (injected_error_count == 0 || !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "error : GetEntity() returns %s for key: %s.\n",
|
||||
s.ToString().c_str(), StringToHex(key_str).c_str());
|
||||
thread->shared->SetVerificationFailure();
|
||||
@@ -1279,15 +1268,11 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
thread->stats.AddGets(1, 1);
|
||||
} else if (s.IsNotFound()) {
|
||||
thread->stats.AddGets(1, 0);
|
||||
} else {
|
||||
if (injected_error_count > 0 && IsRetryableInjectedError(s)) {
|
||||
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddVerifiedErrors(1);
|
||||
} else {
|
||||
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
thread->shared->SetVerificationFailure();
|
||||
}
|
||||
} else if (injected_error_count == 0 ||
|
||||
!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "MultiGetEntity error: %s\n", s.ToString().c_str());
|
||||
thread->stats.AddErrors(1);
|
||||
thread->shared->SetVerificationFailure();
|
||||
}
|
||||
}
|
||||
// Enable back error injection disbled for checking results
|
||||
@@ -1549,13 +1534,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
|
||||
if (s.ok()) {
|
||||
thread->stats.AddPrefixes(1, count);
|
||||
} else if (injected_error_count > 0 && IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "TestPrefixScan error: %s\n", s.ToString().c_str());
|
||||
} else {
|
||||
fprintf(stderr, "TestPrefixScan is retryable error? %s\n",
|
||||
IsRetryableInjectedError(s) ? "true" : "false");
|
||||
fprintf(stderr, "TestPrefixScan injected_error_count: %d\n",
|
||||
injected_error_count);
|
||||
} else if (injected_error_count == 0 || !IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SetVerificationFailure();
|
||||
}
|
||||
@@ -1607,14 +1586,13 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
|
||||
if (FLAGS_verify_before_write) {
|
||||
// Temporarily disable error injection for preparation
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
}
|
||||
|
||||
std::string from_db;
|
||||
Status s = db_->Get(read_opts, cfh, k, &from_db);
|
||||
bool res = VerifyOrSyncValue(
|
||||
@@ -1622,81 +1600,101 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
/* msg_prefix */ "Pre-Put Get verification", from_db, s);
|
||||
|
||||
// Enable back error injection disabled for preparation
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kWrite);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
}
|
||||
if (!res) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// To track the final write status
|
||||
Status s;
|
||||
// To track the initial write status
|
||||
Status initial_write_s;
|
||||
// To track whether WAL write may have succeeded during the initial failed
|
||||
// write
|
||||
bool initial_wal_write_may_succeed = true;
|
||||
|
||||
PendingExpectedValue pending_expected_value =
|
||||
shared->PreparePut(rand_column_family, rand_key);
|
||||
|
||||
const uint32_t value_base = pending_expected_value.GetFinalValueBase();
|
||||
const size_t sz = GenerateValue(value_base, value, sizeof(value));
|
||||
const Slice v(value, sz);
|
||||
|
||||
Status s;
|
||||
uint64_t wait_for_recover_start_time = 0;
|
||||
do {
|
||||
// In order to commit the expected state for the initial write failed with
|
||||
// injected retryable error and successful WAL write, retry the write
|
||||
// until it succeeds after the recovery finishes
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1 * 1000 * 1000));
|
||||
}
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_use_attribute_group) {
|
||||
s = db_->PutEntity(write_opts, k,
|
||||
GenerateAttributeGroups({cfh}, value_base, v));
|
||||
} else {
|
||||
s = db_->PutEntity(write_opts, cfh, k,
|
||||
GenerateWideColumns(value_base, v));
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.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) {
|
||||
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) {
|
||||
s = db_->Merge(write_opts, cfh, k, v);
|
||||
} else {
|
||||
s = db_->Merge(write_opts, cfh, k, write_ts, v);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Merge(cfh, k, v);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->Put(write_opts, cfh, k, v);
|
||||
} else {
|
||||
s = db_->Put(write_opts, cfh, k, write_ts, v);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Put(cfh, k, v);
|
||||
});
|
||||
}
|
||||
}
|
||||
UpdateIfInitialWriteFails(db_stress_env, s, &initial_write_s,
|
||||
&initial_wal_write_may_succeed,
|
||||
&wait_for_recover_start_time);
|
||||
|
||||
if (FLAGS_use_put_entity_one_in > 0 &&
|
||||
(value_base % FLAGS_use_put_entity_one_in) == 0) {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_use_attribute_group) {
|
||||
s = db_->PutEntity(write_opts, k,
|
||||
GenerateAttributeGroups({cfh}, value_base, v));
|
||||
} else {
|
||||
s = db_->PutEntity(write_opts, cfh, k,
|
||||
GenerateWideColumns(value_base, v));
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.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) {
|
||||
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) {
|
||||
s = db_->Merge(write_opts, cfh, k, v);
|
||||
} else {
|
||||
s = db_->Merge(write_opts, cfh, k, write_ts, v);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Merge(cfh, k, v);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->Put(write_opts, cfh, k, v);
|
||||
} else {
|
||||
s = db_->Put(write_opts, cfh, k, write_ts, v);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Put(cfh, k, v);
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed);
|
||||
|
||||
if (!s.ok()) {
|
||||
pending_expected_value.Rollback();
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "put or merge error: %s\n", s.ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(s)) {
|
||||
assert(!initial_wal_write_may_succeed);
|
||||
return s;
|
||||
} else if (FLAGS_inject_error_severity == 2) {
|
||||
if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) {
|
||||
@@ -1710,11 +1708,15 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
fprintf(stderr, "put or merge error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
PrintWriteRecoveryWaitTimeIfNeeded(
|
||||
db_stress_env, initial_write_s, initial_wal_write_may_succeed,
|
||||
wait_for_recover_start_time, "TestPut");
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddBytesForWrites(1, sz);
|
||||
PrintKeyValue(rand_column_family, static_cast<uint32_t>(rand_key), value,
|
||||
sz);
|
||||
}
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddBytesForWrites(1, sz);
|
||||
PrintKeyValue(rand_column_family, static_cast<uint32_t>(rand_key), value,
|
||||
sz);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1736,28 +1738,51 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
Slice key = key_str;
|
||||
auto cfh = column_families_[rand_column_family];
|
||||
|
||||
// To track the final write status
|
||||
Status s;
|
||||
// To track the initial write status
|
||||
Status initial_write_s;
|
||||
// To track whether WAL write may have succeeded during the initial failed
|
||||
// write
|
||||
bool initial_wal_write_may_succeed = true;
|
||||
|
||||
// Use delete if the key may be overwritten and a single deletion
|
||||
// otherwise.
|
||||
Status s;
|
||||
if (shared->AllowsOverwrite(rand_key)) {
|
||||
PendingExpectedValue pending_expected_value =
|
||||
shared->PrepareDelete(rand_column_family, rand_key);
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->Delete(write_opts, cfh, key);
|
||||
} else {
|
||||
s = db_->Delete(write_opts, cfh, key, write_ts);
|
||||
|
||||
uint64_t wait_for_recover_start_time = 0;
|
||||
do {
|
||||
// In order to commit the expected state for the initial write failed
|
||||
// with injected retryable error and successful WAL write, retry the
|
||||
// write until it succeeds after the recovery finishes
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::microseconds(1 * 1000 * 1000));
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Delete(cfh, key);
|
||||
});
|
||||
}
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->Delete(write_opts, cfh, key);
|
||||
} else {
|
||||
s = db_->Delete(write_opts, cfh, key, write_ts);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.Delete(cfh, key);
|
||||
});
|
||||
}
|
||||
UpdateIfInitialWriteFails(db_stress_env, s, &initial_write_s,
|
||||
&initial_wal_write_may_succeed,
|
||||
&wait_for_recover_start_time);
|
||||
} while (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed);
|
||||
|
||||
if (!s.ok()) {
|
||||
pending_expected_value.Rollback();
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "delete error: %s\n", s.ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(s)) {
|
||||
assert(!initial_wal_write_may_succeed);
|
||||
return s;
|
||||
} else if (FLAGS_inject_error_severity == 2) {
|
||||
if (!is_db_stopped_ &&
|
||||
@@ -1772,28 +1797,48 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
fprintf(stderr, "delete error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
PrintWriteRecoveryWaitTimeIfNeeded(
|
||||
db_stress_env, initial_write_s, initial_wal_write_may_succeed,
|
||||
wait_for_recover_start_time, "TestDelete");
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddDeletes(1);
|
||||
}
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddDeletes(1);
|
||||
} else {
|
||||
PendingExpectedValue pending_expected_value =
|
||||
shared->PrepareSingleDelete(rand_column_family, rand_key);
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->SingleDelete(write_opts, cfh, key);
|
||||
} else {
|
||||
s = db_->SingleDelete(write_opts, cfh, key, write_ts);
|
||||
|
||||
uint64_t wait_for_recover_start_time = 0;
|
||||
do {
|
||||
// In order to commit the expected state for the initial write failed
|
||||
// with injected retryable error and successful WAL write, retry the
|
||||
// write until it succeeds after the recovery finishes
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::microseconds(1 * 1000 * 1000));
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.SingleDelete(cfh, key);
|
||||
});
|
||||
}
|
||||
if (!FLAGS_use_txn) {
|
||||
if (FLAGS_user_timestamp_size == 0) {
|
||||
s = db_->SingleDelete(write_opts, cfh, key);
|
||||
} else {
|
||||
s = db_->SingleDelete(write_opts, cfh, key, write_ts);
|
||||
}
|
||||
} else {
|
||||
s = ExecuteTransaction(write_opts, thread, [&](Transaction& txn) {
|
||||
return txn.SingleDelete(cfh, key);
|
||||
});
|
||||
}
|
||||
UpdateIfInitialWriteFails(db_stress_env, s, &initial_write_s,
|
||||
&initial_wal_write_may_succeed,
|
||||
&wait_for_recover_start_time);
|
||||
} while (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed);
|
||||
|
||||
if (!s.ok()) {
|
||||
pending_expected_value.Rollback();
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "delete error: %s\n", s.ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(s)) {
|
||||
assert(!initial_wal_write_may_succeed);
|
||||
return s;
|
||||
} else if (FLAGS_inject_error_severity == 2) {
|
||||
if (!is_db_stopped_ &&
|
||||
@@ -1808,9 +1853,13 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
fprintf(stderr, "single delete error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
PrintWriteRecoveryWaitTimeIfNeeded(
|
||||
db_stress_env, initial_write_s, initial_wal_write_may_succeed,
|
||||
wait_for_recover_start_time, "TestDelete");
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddSingleDeletes(1);
|
||||
}
|
||||
pending_expected_value.Commit();
|
||||
thread->stats.AddSingleDeletes(1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1832,16 +1881,20 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
rand_key =
|
||||
thread->rand.Next() % (max_key - FLAGS_range_deletion_width + 1);
|
||||
}
|
||||
for (int j = 0; j < FLAGS_range_deletion_width; ++j) {
|
||||
if (j == 0 ||
|
||||
((rand_key + j) & ((1 << FLAGS_log2_keys_per_lock) - 1)) == 0) {
|
||||
range_locks.emplace_back(new MutexLock(
|
||||
shared->GetMutexForKey(rand_column_family, rand_key + j)));
|
||||
}
|
||||
}
|
||||
GetDeleteRangeKeyLocks(thread, rand_column_family, rand_key, &range_locks);
|
||||
|
||||
// To track the final write status
|
||||
Status s;
|
||||
// To track the initial write status
|
||||
Status initial_write_s;
|
||||
// To track whether WAL write may have succeeded during the initial failed
|
||||
// write
|
||||
bool initial_wal_write_may_succeed = true;
|
||||
|
||||
std::vector<PendingExpectedValue> pending_expected_values =
|
||||
shared->PrepareDeleteRange(rand_column_family, rand_key,
|
||||
rand_key + FLAGS_range_deletion_width);
|
||||
|
||||
const int covered = static_cast<int>(pending_expected_values.size());
|
||||
std::string keystr = Key(rand_key);
|
||||
Slice key = keystr;
|
||||
@@ -1850,21 +1903,36 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
Slice end_key = end_keystr;
|
||||
std::string write_ts_str;
|
||||
Slice write_ts;
|
||||
Status s;
|
||||
if (FLAGS_user_timestamp_size) {
|
||||
write_ts_str = GetNowNanos();
|
||||
write_ts = write_ts_str;
|
||||
s = db_->DeleteRange(write_opts, cfh, key, end_key, write_ts);
|
||||
} else {
|
||||
s = db_->DeleteRange(write_opts, cfh, key, end_key);
|
||||
}
|
||||
uint64_t wait_for_recover_start_time = 0;
|
||||
|
||||
do {
|
||||
// In order to commit the expected state for the initial write failed with
|
||||
// injected retryable error and successful WAL write, retry the write
|
||||
// until it succeeds after the recovery finishes
|
||||
if (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1 * 1000 * 1000));
|
||||
}
|
||||
if (FLAGS_user_timestamp_size) {
|
||||
write_ts_str = GetNowNanos();
|
||||
write_ts = write_ts_str;
|
||||
s = db_->DeleteRange(write_opts, cfh, key, end_key, write_ts);
|
||||
} else {
|
||||
s = db_->DeleteRange(write_opts, cfh, key, end_key);
|
||||
}
|
||||
UpdateIfInitialWriteFails(db_stress_env, s, &initial_write_s,
|
||||
&initial_wal_write_may_succeed,
|
||||
&wait_for_recover_start_time);
|
||||
} while (!s.ok() && IsErrorInjectedAndRetryable(s) &&
|
||||
initial_wal_write_may_succeed);
|
||||
|
||||
if (!s.ok()) {
|
||||
for (PendingExpectedValue& pending_expected_value :
|
||||
pending_expected_values) {
|
||||
pending_expected_value.Rollback();
|
||||
}
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "delete range error: %s\n", s.ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(s)) {
|
||||
assert(!initial_wal_write_may_succeed);
|
||||
return s;
|
||||
} else if (FLAGS_inject_error_severity == 2) {
|
||||
if (!is_db_stopped_ && s.severity() >= Status::Severity::kFatalError) {
|
||||
@@ -1878,13 +1946,17 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
fprintf(stderr, "delete range error: %s\n", s.ToString().c_str());
|
||||
thread->shared->SafeTerminate();
|
||||
}
|
||||
} else {
|
||||
PrintWriteRecoveryWaitTimeIfNeeded(
|
||||
db_stress_env, initial_write_s, initial_wal_write_may_succeed,
|
||||
wait_for_recover_start_time, "TestDeleteRange");
|
||||
for (PendingExpectedValue& pending_expected_value :
|
||||
pending_expected_values) {
|
||||
pending_expected_value.Commit();
|
||||
}
|
||||
thread->stats.AddRangeDeletions(1);
|
||||
thread->stats.AddCoveredByRangeDeletions(covered);
|
||||
}
|
||||
for (PendingExpectedValue& pending_expected_value :
|
||||
pending_expected_values) {
|
||||
pending_expected_value.Commit();
|
||||
}
|
||||
thread->stats.AddRangeDeletions(1);
|
||||
thread->stats.AddCoveredByRangeDeletions(covered);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1897,19 +1969,25 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
std::ostringstream ingest_options_oss;
|
||||
|
||||
// Temporarily disable error injection for preparation
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->DisableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
}
|
||||
|
||||
if (db_stress_env->FileExists(sst_filename).ok()) {
|
||||
// Maybe we terminated abnormally before, so cleanup to give this file
|
||||
// ingestion a clean slate
|
||||
s = db_stress_env->DeleteFile(sst_filename);
|
||||
}
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
}
|
||||
|
||||
SstFileWriter sst_file_writer(EnvOptions(options_), options_);
|
||||
if (s.ok()) {
|
||||
@@ -1946,6 +2024,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
|
||||
PendingExpectedValue pending_expected_value =
|
||||
shared->PreparePut(column_family, key);
|
||||
|
||||
const uint32_t value_base = pending_expected_value.GetFinalValueBase();
|
||||
values.push_back(value_base);
|
||||
pending_expected_values.push_back(pending_expected_value);
|
||||
@@ -1992,9 +2071,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
pending_expected_value.Rollback();
|
||||
}
|
||||
|
||||
if (IsRetryableInjectedError(s)) {
|
||||
fprintf(stdout, "file ingestion error: %s \n", s.ToString().c_str());
|
||||
} else {
|
||||
if (!IsErrorInjectedAndRetryable(s)) {
|
||||
fprintf(stderr,
|
||||
"file ingestion error: %s under specified "
|
||||
"IngestExternalFileOptions: %s (Empty string or "
|
||||
@@ -2183,11 +2260,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
assert(last_key < ub);
|
||||
if (!iter->Valid()) {
|
||||
if (!iter->status().ok()) {
|
||||
if (IsRetryableInjectedError(iter->status())) {
|
||||
fprintf(
|
||||
stdout,
|
||||
"TestIterate against expected state forward scan error: %s\n",
|
||||
iter->status().ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(iter->status())) {
|
||||
return iter->status();
|
||||
} else {
|
||||
thread->shared->SetVerificationFailure();
|
||||
@@ -2249,11 +2322,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
assert(lb < last_key);
|
||||
if (!iter->Valid()) {
|
||||
if (!iter->status().ok()) {
|
||||
if (IsRetryableInjectedError(iter->status())) {
|
||||
fprintf(
|
||||
stdout,
|
||||
"TestIterate against expected state backward scan error: %s\n",
|
||||
iter->status().ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(iter->status())) {
|
||||
return iter->status();
|
||||
} else {
|
||||
thread->shared->SetVerificationFailure();
|
||||
@@ -2317,10 +2386,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
shared->Get(rand_column_family, i + lb));
|
||||
}
|
||||
Status rs = iter->Refresh();
|
||||
if (!rs.ok() && IsRetryableInjectedError(rs)) {
|
||||
fprintf(stdout,
|
||||
"TestIterate against expected state refresh error: %s\n",
|
||||
iter->status().ToString().c_str());
|
||||
if (!rs.ok() && IsErrorInjectedAndRetryable(rs)) {
|
||||
return rs;
|
||||
}
|
||||
assert(rs.ok());
|
||||
@@ -2484,11 +2550,7 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
}
|
||||
|
||||
if (!iter->status().ok()) {
|
||||
if (IsRetryableInjectedError(iter->status())) {
|
||||
fprintf(
|
||||
stdout,
|
||||
"TestIterate against expected state scan from middle error: %s\n",
|
||||
iter->status().ToString().c_str());
|
||||
if (IsErrorInjectedAndRetryable(iter->status())) {
|
||||
return iter->status();
|
||||
} else {
|
||||
thread->shared->SetVerificationFailure();
|
||||
@@ -2525,6 +2587,8 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
// Value doesn't exist in db, update state to reflect that
|
||||
shared->SyncDelete(cf, key);
|
||||
return true;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
char expected_value_data[kValueMaxLen];
|
||||
@@ -2623,7 +2687,11 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
SharedState* const shared = thread->shared;
|
||||
assert(shared);
|
||||
|
||||
if (!shared->AllowsOverwrite(key) && shared->Exists(column_family, key)) {
|
||||
const ExpectedValue expected_value =
|
||||
thread->shared->Get(column_family, key);
|
||||
bool may_exist = !ExpectedValueHelper::MustHaveNotExisted(expected_value,
|
||||
expected_value);
|
||||
if (!shared->AllowsOverwrite(key) && may_exist) {
|
||||
// Just do read your write checks for keys that allow overwrites.
|
||||
return;
|
||||
}
|
||||
|
||||
Vendored
+21
@@ -18,6 +18,13 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// This class supports abstracting different types of an `Env`'s functionality
|
||||
// into separate interfaces. It is constructed with a `FileSystem` and a
|
||||
// `SystemClock` and delegates:
|
||||
// * File system operations to member `file_system_`.
|
||||
// * Time related misc operations to member `clock_`.
|
||||
// A subclass needs to inherit `CompositeEnv` and provide implementations for
|
||||
// the thread management related APIs.
|
||||
class CompositeEnv : public Env {
|
||||
public:
|
||||
// Initialize a CompositeEnvWrapper that delegates all thread/time related
|
||||
@@ -250,6 +257,20 @@ class CompositeEnv : public Env {
|
||||
}
|
||||
};
|
||||
|
||||
// A `CompositeEnvWrapper` is constructed with a target `Env` object, an
|
||||
// optional `FileSystem` object and an optional `SystemClock` object.
|
||||
// `Env::GetFileSystem()` is a fallback file system if no such object is
|
||||
// explicitly provided. Similarly, `Env::GetSystemClock()` is a fallback system
|
||||
// clock.
|
||||
// Besides delegating corresponding functionality to `file_system_` and `clock_`
|
||||
// which is inherited from `CompositeEnv`, it also implements the thread
|
||||
// management APIs by delegating them to the target `Env` object.
|
||||
//
|
||||
// Effectively, this class helps to support using customized file system
|
||||
// implementations such as a remote file system instead of the default file
|
||||
// system provided by the operating system.
|
||||
//
|
||||
// Also see public API `NewCompositeEnv` in rocksdb/include/env.h
|
||||
class CompositeEnvWrapper : public CompositeEnv {
|
||||
public:
|
||||
// Initialize a CompositeEnvWrapper that delegates all thread/time related
|
||||
|
||||
Vendored
+6
@@ -355,6 +355,12 @@ class LegacyDirectoryWrapper : public FSDirectory {
|
||||
std::unique_ptr<Directory> target_;
|
||||
};
|
||||
|
||||
// A helper class to make legacy `Env` implementations be backward compatible
|
||||
// now that all `Env` implementations are expected to have a `FileSystem` type
|
||||
// member `file_system_` and a `SystemClock` type member `clock_`.
|
||||
// This class wraps a legacy `Env` object and expose its file system related
|
||||
// APIs as a `FileSystem` interface. Also check `LegacySystemClock` that does
|
||||
// the same thing for the clock related APIs.
|
||||
class LegacyFileSystemWrapper : public FileSystem {
|
||||
public:
|
||||
// Initialize an EnvWrapper that delegates all calls to *t
|
||||
|
||||
Vendored
+3
-3
@@ -181,10 +181,10 @@ FileOptions FileSystem::OptimizeForBlobFileRead(
|
||||
|
||||
IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname, bool should_sync,
|
||||
const IOOptions& io_options) {
|
||||
const IOOptions& io_options,
|
||||
const FileOptions& file_options) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
EnvOptions soptions;
|
||||
IOStatus s = fs->NewWritableFile(fname, soptions, &file, nullptr);
|
||||
IOStatus s = fs->NewWritableFile(fname, file_options, &file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+124
-23
@@ -31,6 +31,7 @@ DeleteScheduler::DeleteScheduler(SystemClock* clock, FileSystem* fs,
|
||||
total_trash_size_(0),
|
||||
rate_bytes_per_sec_(rate_bytes_per_sec),
|
||||
pending_files_(0),
|
||||
next_trash_bucket_(0),
|
||||
bytes_max_delete_chunk_(bytes_max_delete_chunk),
|
||||
closing_(false),
|
||||
cv_(&mu_),
|
||||
@@ -66,10 +67,8 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
total_trash_size_.load() > total_size * 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");
|
||||
Status s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
Status s = DeleteFileImmediately(file_path, /*accounted=*/true);
|
||||
if (s.ok()) {
|
||||
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
|
||||
@@ -77,15 +76,57 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
file_path.c_str(), rate_bytes_per_sec_.load(),
|
||||
total_trash_size_.load(), total_size,
|
||||
max_trash_db_ratio_.load());
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
return AddFileToDeletionQueue(file_path, dir_to_sync, /*bucket=*/std::nullopt,
|
||||
/*accounted=*/true);
|
||||
}
|
||||
|
||||
Status DeleteScheduler::DeleteUnaccountedFile(const std::string& file_path,
|
||||
const std::string& dir_to_sync,
|
||||
const bool force_bg,
|
||||
std::optional<int32_t> bucket) {
|
||||
uint64_t num_hard_links = 1;
|
||||
fs_->NumFileLinks(file_path, IOOptions(), &num_hard_links, nullptr)
|
||||
.PermitUncheckedError();
|
||||
|
||||
// We can tolerate rare races where we might immediately delete both links
|
||||
// to a file.
|
||||
if (rate_bytes_per_sec_.load() <= 0 || (!force_bg && num_hard_links > 1)) {
|
||||
Status s = DeleteFileImmediately(file_path, /*accounted=*/false);
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(info_log_,
|
||||
"Deleted file %s immediately, rate_bytes_per_sec %" PRIi64,
|
||||
file_path.c_str(), rate_bytes_per_sec_.load());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
return AddFileToDeletionQueue(file_path, dir_to_sync, bucket,
|
||||
/*accounted=*/false);
|
||||
}
|
||||
|
||||
Status DeleteScheduler::DeleteFileImmediately(const std::string& file_path,
|
||||
bool accounted) {
|
||||
TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
|
||||
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::DeleteFile::cb",
|
||||
const_cast<std::string*>(&file_path));
|
||||
Status s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = OnDeleteFile(file_path, accounted);
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
RecordTick(stats_.get(), FILES_DELETED_IMMEDIATELY);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DeleteScheduler::AddFileToDeletionQueue(const std::string& file_path,
|
||||
const std::string& dir_to_sync,
|
||||
std::optional<int32_t> bucket,
|
||||
bool accounted) {
|
||||
// Move file to trash
|
||||
std::string trash_file;
|
||||
Status s = MarkAsTrash(file_path, &trash_file);
|
||||
Status s = MarkAsTrash(file_path, accounted, &trash_file);
|
||||
ROCKS_LOG_INFO(info_log_, "Mark file: %s as trash -- %s", trash_file.c_str(),
|
||||
s.ToString().c_str());
|
||||
|
||||
@@ -94,7 +135,7 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = sst_file_manager_->OnDeleteFile(file_path);
|
||||
s = OnDeleteFile(file_path, accounted);
|
||||
ROCKS_LOG_INFO(info_log_, "Deleted file %s immediately",
|
||||
trash_file.c_str());
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
@@ -104,11 +145,13 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
}
|
||||
|
||||
// Update the total trash size
|
||||
uint64_t trash_file_size = 0;
|
||||
IOStatus io_s =
|
||||
fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
|
||||
if (io_s.ok()) {
|
||||
total_trash_size_.fetch_add(trash_file_size);
|
||||
if (accounted) {
|
||||
uint64_t trash_file_size = 0;
|
||||
IOStatus io_s =
|
||||
fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
|
||||
if (io_s.ok()) {
|
||||
total_trash_size_.fetch_add(trash_file_size);
|
||||
}
|
||||
}
|
||||
//**TODO: What should we do if we failed to
|
||||
// get the file size?
|
||||
@@ -117,8 +160,15 @@ Status DeleteScheduler::DeleteFile(const std::string& file_path,
|
||||
{
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
RecordTick(stats_.get(), FILES_MARKED_TRASH);
|
||||
queue_.emplace(trash_file, dir_to_sync);
|
||||
queue_.emplace(trash_file, dir_to_sync, accounted, bucket);
|
||||
pending_files_++;
|
||||
if (bucket.has_value()) {
|
||||
auto iter = pending_files_in_buckets_.find(bucket.value());
|
||||
assert(iter != pending_files_in_buckets_.end());
|
||||
if (iter != pending_files_in_buckets_.end()) {
|
||||
iter->second++;
|
||||
}
|
||||
}
|
||||
if (pending_files_ == 1) {
|
||||
cv_.SignalAll();
|
||||
}
|
||||
@@ -177,7 +227,7 @@ Status DeleteScheduler::CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
|
||||
}
|
||||
|
||||
Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
|
||||
std::string* trash_file) {
|
||||
bool accounted, std::string* trash_file) {
|
||||
// Sanity check of the path
|
||||
size_t idx = file_path.rfind('/');
|
||||
if (idx == std::string::npos || idx == file_path.size() - 1) {
|
||||
@@ -211,7 +261,7 @@ Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (s.ok() && accounted) {
|
||||
s = sst_file_manager_->OnMoveFile(file_path, *trash_file);
|
||||
}
|
||||
return s;
|
||||
@@ -235,6 +285,8 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
uint64_t total_deleted_bytes = 0;
|
||||
int64_t current_delete_rate = rate_bytes_per_sec_.load();
|
||||
while (!queue_.empty() && !closing_) {
|
||||
// Satisfy static analysis.
|
||||
std::optional<int32_t> bucket = std::nullopt;
|
||||
if (current_delete_rate != rate_bytes_per_sec_.load()) {
|
||||
// User changed the delete rate
|
||||
current_delete_rate = rate_bytes_per_sec_.load();
|
||||
@@ -247,14 +299,17 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
// Get new file to delete
|
||||
const FileAndDir& fad = queue_.front();
|
||||
std::string path_in_trash = fad.fname;
|
||||
std::string dir_to_sync = fad.dir;
|
||||
bool accounted = fad.accounted;
|
||||
bucket = fad.bucket;
|
||||
|
||||
// We don't need to hold the lock while deleting the file
|
||||
mu_.Unlock();
|
||||
uint64_t deleted_bytes = 0;
|
||||
bool is_complete = true;
|
||||
// Delete file from trash and update total_penlty value
|
||||
Status s =
|
||||
DeleteTrashFile(path_in_trash, fad.dir, &deleted_bytes, &is_complete);
|
||||
Status s = DeleteTrashFile(path_in_trash, dir_to_sync, accounted,
|
||||
&deleted_bytes, &is_complete);
|
||||
total_deleted_bytes += deleted_bytes;
|
||||
mu_.Lock();
|
||||
if (is_complete) {
|
||||
@@ -288,12 +343,20 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
&total_penalty);
|
||||
|
||||
int32_t pending_files_in_bucket = std::numeric_limits<int32_t>::max();
|
||||
if (is_complete) {
|
||||
pending_files_--;
|
||||
if (bucket.has_value()) {
|
||||
auto iter = pending_files_in_buckets_.find(bucket.value());
|
||||
assert(iter != pending_files_in_buckets_.end());
|
||||
if (iter != pending_files_in_buckets_.end()) {
|
||||
pending_files_in_bucket = iter->second--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending_files_ == 0) {
|
||||
// Unblock WaitForEmptyTrash since there are no more files waiting
|
||||
// to be deleted
|
||||
if (pending_files_ == 0 || pending_files_in_bucket == 0) {
|
||||
// Unblock WaitForEmptyTrash or WaitForEmptyTrashBucket since there are
|
||||
// no more files waiting to be deleted
|
||||
cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
@@ -302,12 +365,14 @@ void DeleteScheduler::BackgroundEmptyTrash() {
|
||||
|
||||
Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
|
||||
const std::string& dir_to_sync,
|
||||
uint64_t* deleted_bytes,
|
||||
bool accounted, uint64_t* deleted_bytes,
|
||||
bool* is_complete) {
|
||||
uint64_t file_size;
|
||||
Status s = fs_->GetFileSize(path_in_trash, IOOptions(), &file_size, nullptr);
|
||||
*is_complete = true;
|
||||
TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:DeleteFile");
|
||||
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::DeleteTrashFile::cb",
|
||||
const_cast<std::string*>(&path_in_trash));
|
||||
if (s.ok()) {
|
||||
bool need_full_delete = true;
|
||||
if (bytes_max_delete_chunk_ != 0 && file_size > bytes_max_delete_chunk_) {
|
||||
@@ -374,7 +439,7 @@ Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
|
||||
}
|
||||
if (s.ok()) {
|
||||
*deleted_bytes = file_size;
|
||||
s = sst_file_manager_->OnDeleteFile(path_in_trash);
|
||||
s = OnDeleteFile(path_in_trash, accounted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,12 +449,24 @@ Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
|
||||
path_in_trash.c_str(), s.ToString().c_str());
|
||||
*deleted_bytes = 0;
|
||||
} else {
|
||||
total_trash_size_.fetch_sub(*deleted_bytes);
|
||||
if (accounted) {
|
||||
total_trash_size_.fetch_sub(*deleted_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DeleteScheduler::OnDeleteFile(const std::string& file_path,
|
||||
bool accounted) {
|
||||
if (accounted) {
|
||||
return sst_file_manager_->OnDeleteFile(file_path);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DeleteScheduler::OnDeleteFile",
|
||||
const_cast<std::string*>(&file_path));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DeleteScheduler::WaitForEmptyTrash() {
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
while (pending_files_ > 0 && !closing_) {
|
||||
@@ -397,6 +474,30 @@ void DeleteScheduler::WaitForEmptyTrash() {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<int32_t> DeleteScheduler::NewTrashBucket() {
|
||||
if (rate_bytes_per_sec_.load() <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
int32_t bucket_number = next_trash_bucket_++;
|
||||
pending_files_in_buckets_.emplace(bucket_number, 0);
|
||||
return bucket_number;
|
||||
}
|
||||
|
||||
void DeleteScheduler::WaitForEmptyTrashBucket(int32_t bucket) {
|
||||
InstrumentedMutexLock l(&mu_);
|
||||
if (bucket >= next_trash_bucket_) {
|
||||
return;
|
||||
}
|
||||
auto iter = pending_files_in_buckets_.find(bucket);
|
||||
while (iter != pending_files_in_buckets_.end() && iter->second > 0 &&
|
||||
!closing_) {
|
||||
cv_.Wait();
|
||||
iter = pending_files_in_buckets_.find(bucket);
|
||||
}
|
||||
pending_files_in_buckets_.erase(bucket);
|
||||
}
|
||||
|
||||
void DeleteScheduler::MaybeCreateBackgroundThread() {
|
||||
if (bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
|
||||
bg_thread_.reset(
|
||||
|
||||
+61
-8
@@ -7,6 +7,7 @@
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -48,16 +49,45 @@ class DeleteScheduler {
|
||||
MaybeCreateBackgroundThread();
|
||||
}
|
||||
|
||||
// Mark file as trash directory and schedule its deletion. If force_bg is
|
||||
// set, it forces the file to always be deleted in the background thread,
|
||||
// except when rate limiting is disabled
|
||||
// Delete an accounted file that is tracked by `SstFileManager` and should be
|
||||
// tracked by this `DeleteScheduler` when it's deleted.
|
||||
// The file is deleted immediately if slow deletion is disabled. If force_bg
|
||||
// is not set and trash to db size ratio exceeded the configured threshold,
|
||||
// it is immediately deleted too. In all other cases, the file will be moved
|
||||
// to a trash directory and scheduled for deletion by a background thread.
|
||||
Status DeleteFile(const std::string& fname, const std::string& dir_to_sync,
|
||||
const bool force_bg = false);
|
||||
|
||||
// Wait for all files being deleteing in the background to finish or for
|
||||
// Delete an unaccounted file that is not tracked by `SstFileManager` and
|
||||
// should not be tracked by this `DeleteScheduler` when it's deleted.
|
||||
// The file is deleted immediately if slow deletion is disabled. If force_bg
|
||||
// is not set and the file have more than 1 hard link, it is immediately
|
||||
// deleted too. In all other cases, the file will be moved to a trash
|
||||
// directory and scheduled for deletion by a background thread.
|
||||
// This API also supports assign a file to a specified bucket created by
|
||||
// `NewTrashBucket` when delete files in the background. So the caller can
|
||||
// wait for a specific bucket to be empty by checking the
|
||||
// `WaitForEmptyTrashBucket` API.
|
||||
Status DeleteUnaccountedFile(const std::string& file_path,
|
||||
const std::string& dir_to_sync,
|
||||
const bool force_bg = false,
|
||||
std::optional<int32_t> bucket = std::nullopt);
|
||||
|
||||
// Wait for all files being deleted in the background to finish or for
|
||||
// destructor to be called.
|
||||
void WaitForEmptyTrash();
|
||||
|
||||
// Creates a new trash bucket. A bucket is only created and returned when slow
|
||||
// deletion is enabled.
|
||||
// For each bucket that is created, the user should also call
|
||||
// `WaitForEmptyTrashBucket` after scheduling file deletions to make sure the
|
||||
// trash files are all cleared.
|
||||
std::optional<int32_t> NewTrashBucket();
|
||||
|
||||
// Wait for all the files in the specified bucket to be deleted in the
|
||||
// background or for the destructor to be called.
|
||||
void WaitForEmptyTrashBucket(int32_t bucket);
|
||||
|
||||
// Return a map containing errors that happened in BackgroundEmptyTrash
|
||||
// file_path => error status
|
||||
std::map<std::string, Status> GetBackgroundErrors();
|
||||
@@ -87,12 +117,21 @@ class DeleteScheduler {
|
||||
}
|
||||
|
||||
private:
|
||||
Status MarkAsTrash(const std::string& file_path, std::string* path_in_trash);
|
||||
Status DeleteFileImmediately(const std::string& file_path, bool accounted);
|
||||
|
||||
Status AddFileToDeletionQueue(const std::string& file_path,
|
||||
const std::string& dir_to_sync,
|
||||
std::optional<int32_t> bucket, bool accounted);
|
||||
|
||||
Status MarkAsTrash(const std::string& file_path, bool accounted,
|
||||
std::string* path_in_trash);
|
||||
|
||||
Status DeleteTrashFile(const std::string& path_in_trash,
|
||||
const std::string& dir_to_sync,
|
||||
const std::string& dir_to_sync, bool accounted,
|
||||
uint64_t* deleted_bytes, bool* is_complete);
|
||||
|
||||
Status OnDeleteFile(const std::string& file_path, bool accounted);
|
||||
|
||||
void BackgroundEmptyTrash();
|
||||
|
||||
void MaybeCreateBackgroundThread();
|
||||
@@ -104,19 +143,28 @@ class DeleteScheduler {
|
||||
std::atomic<uint64_t> total_trash_size_;
|
||||
// Maximum number of bytes that should be deleted per second
|
||||
std::atomic<int64_t> rate_bytes_per_sec_;
|
||||
// Mutex to protect queue_, pending_files_, bg_errors_, closing_, stats_
|
||||
// Mutex to protect queue_, pending_files_, next_trash_bucket_,
|
||||
// pending_files_in_buckets_, bg_errors_, closing_, stats_
|
||||
InstrumentedMutex mu_;
|
||||
|
||||
struct FileAndDir {
|
||||
FileAndDir(const std::string& f, const std::string& d) : fname(f), dir(d) {}
|
||||
FileAndDir(const std::string& _fname, const std::string& _dir,
|
||||
bool _accounted, std::optional<int32_t> _bucket)
|
||||
: fname(_fname), dir(_dir), accounted(_accounted), bucket(_bucket) {}
|
||||
std::string fname;
|
||||
std::string dir; // empty will be skipped.
|
||||
bool accounted;
|
||||
std::optional<int32_t> bucket;
|
||||
};
|
||||
|
||||
// Queue of trash files that need to be deleted
|
||||
std::queue<FileAndDir> queue_;
|
||||
// Number of trash files that are waiting to be deleted
|
||||
int32_t pending_files_;
|
||||
// Next trash bucket that can be created
|
||||
int32_t next_trash_bucket_;
|
||||
// A mapping from trash bucket to number of pending files in the bucket
|
||||
std::map<int32_t, int32_t> pending_files_in_buckets_;
|
||||
uint64_t bytes_max_delete_chunk_;
|
||||
// Errors that happened in BackgroundEmptyTrash (file_path => error)
|
||||
std::map<std::string, Status> bg_errors_;
|
||||
@@ -127,6 +175,7 @@ class DeleteScheduler {
|
||||
// Condition variable signaled in these conditions
|
||||
// - pending_files_ value change from 0 => 1
|
||||
// - pending_files_ value change from 1 => 0
|
||||
// - a value in pending_files_in_buckets change from 1 => 0
|
||||
// - closing_ value is set to true
|
||||
InstrumentedCondVar cv_;
|
||||
// Background thread running BackgroundEmptyTrash
|
||||
@@ -138,6 +187,10 @@ class DeleteScheduler {
|
||||
// If the trash size constitutes for more than this fraction of the total DB
|
||||
// size we will start deleting new files passed to DeleteScheduler
|
||||
// immediately
|
||||
// Unaccounted files passed for deletion will not cause change in
|
||||
// total_trash_size_ or affect the DeleteScheduler::total_trash_size_ over
|
||||
// SstFileManager::total_size_ ratio. Their slow deletion is not subject to
|
||||
// this configured threshold either.
|
||||
std::atomic<double> max_trash_db_ratio_;
|
||||
static const uint64_t kMicrosInSecond = 1000 * 1000LL;
|
||||
std::shared_ptr<Statistics> stats_;
|
||||
|
||||
@@ -78,7 +78,7 @@ class DeleteSchedulerTest : public testing::Test {
|
||||
}
|
||||
|
||||
std::string NewDummyFile(const std::string& file_name, uint64_t size = 1024,
|
||||
size_t dummy_files_dirs_idx = 0) {
|
||||
size_t dummy_files_dirs_idx = 0, bool track = true) {
|
||||
std::string file_path =
|
||||
dummy_files_dirs_[dummy_files_dirs_idx] + "/" + file_name;
|
||||
std::unique_ptr<WritableFile> f;
|
||||
@@ -86,7 +86,9 @@ class DeleteSchedulerTest : public testing::Test {
|
||||
std::string data(size, 'A');
|
||||
EXPECT_OK(f->Append(data));
|
||||
EXPECT_OK(f->Close());
|
||||
EXPECT_OK(sst_file_mgr_->OnAddFile(file_path));
|
||||
if (track) {
|
||||
EXPECT_OK(sst_file_mgr_->OnAddFile(file_path));
|
||||
}
|
||||
return file_path;
|
||||
}
|
||||
|
||||
@@ -353,6 +355,8 @@ TEST_F(DeleteSchedulerTest, DisableRateLimiting) {
|
||||
ASSERT_EQ(num_files,
|
||||
stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
|
||||
ASSERT_FALSE(delete_scheduler_->NewTrashBucket().has_value());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -718,6 +722,141 @@ TEST_F(DeleteSchedulerTest, IsTrashCheck) {
|
||||
ASSERT_FALSE(DeleteScheduler::IsTrashFile("abc.trashx"));
|
||||
}
|
||||
|
||||
TEST_F(DeleteSchedulerTest, DeleteAccountedAndUnaccountedFiles) {
|
||||
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / s
|
||||
NewDeleteScheduler();
|
||||
|
||||
// Create 100 files, every file is 1 KB
|
||||
int num_files = 100; // 100 files
|
||||
uint64_t file_size = 1024; // 1 KB as a file size
|
||||
std::vector<std::string> generated_files;
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
std::string file_name = "file" + std::to_string(i) + ".data";
|
||||
generated_files.push_back(NewDummyFile(file_name, file_size,
|
||||
/*dummy_files_dirs_idx*/ 0,
|
||||
/*track=*/false));
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
if (i % 2) {
|
||||
ASSERT_OK(sst_file_mgr_->OnAddFile(generated_files[i], file_size));
|
||||
ASSERT_OK(delete_scheduler_->DeleteFile(generated_files[i], ""));
|
||||
} else {
|
||||
ASSERT_OK(
|
||||
delete_scheduler_->DeleteUnaccountedFile(generated_files[i], ""));
|
||||
}
|
||||
}
|
||||
|
||||
delete_scheduler_->WaitForEmptyTrash();
|
||||
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
|
||||
ASSERT_EQ(0, sst_file_mgr_->GetTotalSize());
|
||||
}
|
||||
|
||||
TEST_F(DeleteSchedulerTest, ConcurrentlyDeleteUnaccountedFilesInBuckets) {
|
||||
int bg_delete_file = 0;
|
||||
int fg_delete_file = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; });
|
||||
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / s
|
||||
NewDeleteScheduler();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
// Create 1000 files, every file is 1 KB
|
||||
int num_files = 1000;
|
||||
uint64_t file_size = 1024; // 1 KB as a file size
|
||||
std::vector<std::string> generated_files;
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
std::string file_name = "file" + std::to_string(i) + ".data";
|
||||
generated_files.push_back(NewDummyFile(file_name, file_size,
|
||||
/*dummy_files_dirs_idx*/ 0,
|
||||
/*track=*/false));
|
||||
}
|
||||
// Concurrently delete files in different buckets and check all the buckets
|
||||
// are empty.
|
||||
int thread_cnt = 10;
|
||||
int files_per_thread = 100;
|
||||
std::atomic<int> thread_num(0);
|
||||
std::vector<port::Thread> threads;
|
||||
std::function<void()> delete_thread = [&]() {
|
||||
std::optional<int32_t> bucket = delete_scheduler_->NewTrashBucket();
|
||||
ASSERT_TRUE(bucket.has_value());
|
||||
int idx = thread_num.fetch_add(1);
|
||||
int range_start = idx * files_per_thread;
|
||||
int range_end = range_start + files_per_thread;
|
||||
for (int j = range_start; j < range_end; j++) {
|
||||
ASSERT_OK(delete_scheduler_->DeleteUnaccountedFile(
|
||||
generated_files[j], "", /*false_bg=*/false, bucket));
|
||||
}
|
||||
delete_scheduler_->WaitForEmptyTrashBucket(bucket.value());
|
||||
};
|
||||
|
||||
for (int i = 0; i < thread_cnt; i++) {
|
||||
threads.emplace_back(delete_thread);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < threads.size(); i++) {
|
||||
threads[i].join();
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
ASSERT_EQ(1000, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(0, fg_delete_file);
|
||||
ASSERT_EQ(1000, bg_delete_file);
|
||||
|
||||
// OK to re check an already empty bucket
|
||||
delete_scheduler_->WaitForEmptyTrashBucket(9);
|
||||
// Invalid bucket return too.
|
||||
delete_scheduler_->WaitForEmptyTrashBucket(100);
|
||||
std::optional<int32_t> next_bucket = delete_scheduler_->NewTrashBucket();
|
||||
ASSERT_TRUE(next_bucket.has_value());
|
||||
ASSERT_EQ(10, next_bucket.value());
|
||||
delete_scheduler_->WaitForEmptyTrashBucket(10);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DeleteSchedulerTest,
|
||||
ImmediatelyDeleteUnaccountedFilesWithRemainingLinks) {
|
||||
int bg_delete_file = 0;
|
||||
int fg_delete_file = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile", [&](void* /*arg*/) { fg_delete_file++; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
rate_bytes_per_sec_ = 1024 * 1024; // 1 MB / sec
|
||||
NewDeleteScheduler();
|
||||
|
||||
std::string file1 = NewDummyFile("data_1", 500 * 1024,
|
||||
/*dummy_files_dirs_idx*/ 0, /*track=*/false);
|
||||
std::string file2 = NewDummyFile("data_2", 100 * 1024,
|
||||
/*dummy_files_dirs_idx*/ 0, /*track=*/false);
|
||||
|
||||
ASSERT_OK(env_->LinkFile(file1, dummy_files_dirs_[0] + "/data_1b"));
|
||||
ASSERT_OK(env_->LinkFile(file2, dummy_files_dirs_[0] + "/data_2b"));
|
||||
|
||||
// Should delete in 4 batch if there is no hardlink
|
||||
ASSERT_OK(
|
||||
delete_scheduler_->DeleteUnaccountedFile(file1, "", /*force_bg=*/false));
|
||||
ASSERT_OK(
|
||||
delete_scheduler_->DeleteUnaccountedFile(file2, "", /*force_bg=*/false));
|
||||
|
||||
delete_scheduler_->WaitForEmptyTrash();
|
||||
|
||||
ASSERT_EQ(0, delete_scheduler_->GetTotalTrashSize());
|
||||
ASSERT_EQ(0, bg_delete_file);
|
||||
ASSERT_EQ(2, fg_delete_file);
|
||||
ASSERT_EQ(0, stats_->getAndResetTickerCount(FILES_MARKED_TRASH));
|
||||
ASSERT_EQ(2, stats_->getAndResetTickerCount(FILES_DELETED_IMMEDIATELY));
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+17
-2
@@ -125,8 +125,8 @@ IOStatus CreateFile(FileSystem* fs, const std::string& destination,
|
||||
Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
const std::string& fname, const std::string& dir_to_sync,
|
||||
const bool force_bg, const bool force_fg) {
|
||||
SstFileManagerImpl* sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options->sst_file_manager.get());
|
||||
SstFileManagerImpl* sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
db_options->sst_file_manager.get());
|
||||
if (sfm && !force_fg) {
|
||||
return sfm->ScheduleFileDeletion(fname, dir_to_sync, force_bg);
|
||||
} else {
|
||||
@@ -134,6 +134,21 @@ Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
}
|
||||
}
|
||||
|
||||
Status DeleteUnaccountedDBFile(const ImmutableDBOptions* db_options,
|
||||
const std::string& fname,
|
||||
const std::string& dir_to_sync,
|
||||
const bool force_bg, const bool force_fg,
|
||||
std::optional<int32_t> bucket) {
|
||||
SstFileManagerImpl* sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
db_options->sst_file_manager.get());
|
||||
if (sfm && !force_fg) {
|
||||
return sfm->ScheduleUnaccountedFileDeletion(fname, dir_to_sync, force_bg,
|
||||
bucket);
|
||||
} else {
|
||||
return db_options->env->DeleteFile(fname);
|
||||
}
|
||||
}
|
||||
|
||||
// requested_checksum_func_name brings the function name of the checksum
|
||||
// generator in checksum_factory. Empty string is permitted, in which case the
|
||||
// name of the generator created by the factory is unchecked. When
|
||||
|
||||
@@ -55,6 +55,16 @@ Status DeleteDBFile(const ImmutableDBOptions* db_options,
|
||||
const std::string& fname, const std::string& path_to_sync,
|
||||
const bool force_bg, const bool force_fg);
|
||||
|
||||
// Delete an unaccounted DB file that is not tracked by SstFileManager and will
|
||||
// not be tracked by its DeleteScheduler when getting deleted.
|
||||
// If a legitimate bucket is provided and this file is scheduled for slow
|
||||
// deletion, it will be assigned to the specified trash bucket.
|
||||
Status DeleteUnaccountedDBFile(const ImmutableDBOptions* db_options,
|
||||
const std::string& fname,
|
||||
const std::string& dir_to_sync,
|
||||
const bool force_bg, const bool force_fg,
|
||||
std::optional<int32_t> bucket);
|
||||
|
||||
// TODO(hx235): pass the whole DBOptions intead of its individual fields
|
||||
IOStatus GenerateOneFileChecksum(
|
||||
FileSystem* fs, const std::string& file_path,
|
||||
|
||||
+11
-3
@@ -388,6 +388,7 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file) {
|
||||
// Remove leading "dbname/" and add newline to manifest file name
|
||||
std::string manifest = DescriptorFileName(dbname, descriptor_number);
|
||||
@@ -397,8 +398,11 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
std::string tmp = TempFileName(dbname, descriptor_number);
|
||||
IOOptions opts;
|
||||
IOStatus s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts);
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts,
|
||||
file_opts);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("SetCurrentFile:BeforeRename", &s);
|
||||
if (s.ok()) {
|
||||
@@ -423,7 +427,8 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
}
|
||||
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname, const std::string& db_id) {
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id) {
|
||||
std::string id;
|
||||
if (db_id.empty()) {
|
||||
id = env->GenerateUniqueId();
|
||||
@@ -437,8 +442,11 @@ Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
Status s;
|
||||
IOOptions opts;
|
||||
s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(env, id, tmp, true, &opts);
|
||||
s = WriteStringToFile(env->GetFileSystem().get(), id, tmp,
|
||||
/*should_sync=*/true, opts, file_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp, identify_file_name);
|
||||
|
||||
+2
-1
@@ -161,11 +161,12 @@ bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
// when
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file);
|
||||
|
||||
// Make the IDENTITY file for the db
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname,
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id = {});
|
||||
|
||||
// Sync manifest file `file`.
|
||||
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user