Compare commits

...
Author SHA1 Message Date
Hui Xiao f4bf18a60b Update version.h for 10.5.5 2025-08-28 14:41:20 -07:00
Richard Barnes a7dc0e231e Del redundant-static-def in internal_repo_rocksdb/repo/db/db_with_timestamp_basic_test.cc +1 (#13794)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13794

LLVM has a warning `-Wdeprecated-redundant-constexpr-static-def` which raises the warning:

> warning: out-of-line definition of constexpr static data member is redundant in C++17 and is deprecated

Since we are now on C++20, we can remove the out-of-line definition of constexpr static data members. This diff does so.

 - If you approve of this diff, please use the "Accept & Ship" button :-)

Reviewed By: meyering

Differential Revision: D78635037

fbshipit-source-id: a90c68469947705c65f36588b2d575237689dbe8
2025-08-28 14:39:16 -07:00
Richard Barnes b0bd38001b Del redundant-static-def in internal_repo_rocksdb/repo/tools/sst_dump_test.cc +1 (#13793)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13793

LLVM has a warning `-Wdeprecated-redundant-constexpr-static-def` which raises the warning:

> warning: out-of-line definition of constexpr static data member is redundant in C++17 and is deprecated

Since we are now on C++20, we can remove the out-of-line definition of constexpr static data members. This diff does so.

 - If you approve of this diff, please use the "Accept & Ship" button :-)

Reviewed By: meyering

Differential Revision: D78635005

fbshipit-source-id: bd7cbfff0580b9579e78237ec4371615d3609536
2025-08-28 14:38:56 -07:00
anand76 4d1b6a2f9d Update HISTORY and version for 10.5.4 2025-08-21 16:48:56 -07:00
anand76 d6abf820d7 Fix multiscan crash when fill_cache=false (#13889)
Summary:
When fill_cache is ReadOptions is false, multi scan Prepare crashes with the following assertion failure. In this case, CreateAndPibBlockInCache needs to directly create a block with full ownership.

https://github.com/facebook/rocksdb/issues/9  0x00007f2fc003bc93 in __GI___assert_fail (assertion=0x7f2fc2147361 "pinned_data_blocks_guard[block_idx].GetValue()", file=0x7f2fc2146e08 "table/block_based/block_based_table_iterator.cc", line=1178, function=0x7f2fc2147262 "virtual void rocksdb::BlockBasedTableIterator::Prepare(const rocksdb::MultiScanArgs *)") at assert.c:101
101 in assert.c
https://github.com/facebook/rocksdb/issues/10 0x00007f2fc1d73088 in rocksdb::BlockBasedTableIterator::Prepare(rocksdb::MultiScanArgs const*) () from /data/users/anand76/rocksdb_anand76/librocksdb.so.10.6

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

Test Plan: Parameterize the DBMultiScanIteratorTest tests with fill_cache

Reviewed By: cbi42

Differential Revision: D80552069

Pulled By: anand1976

fbshipit-source-id: 1a0b64af1e14c63d826add1f994a832ebff12757
2025-08-21 16:47:37 -07:00
anand76 8265284538 Fix compilation failure in 1369c7b169 2025-08-14 15:50:40 -07:00
anand76 5b3897bff0 Update HISTORY and version for 10.5.3 patch 2025-08-14 10:24:36 -07:00
anand76 675846d46b Allow a user defined index to be configured from a string (#13880)
Summary:
Allow a user defined index to be configured from a string

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

Test Plan: Add a unit test in table_test.cc

Reviewed By: bikash-c

Differential Revision: D80237701

Pulled By: anand1976

fbshipit-source-id: 8b3d0bcdfbb4bb76803916ea1b1f940a4d985dfd
2025-08-14 10:14:20 -07:00
Hui Xiao 5265e31b32 Update history and version.h for 10.5.2 2025-08-12 15:12:24 -07:00
anand76 16ed52ebc4 Make UDI interface consistently use the user key (#13865)
Summary:
The original intention of the User Defined Index interface was to use the user key. However, the implementation mixed user and internal key usage. This PR makes it consistent. It also clarifies the UDI contract.

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

Test Plan: Update tests in table_test.cc

Reviewed By: pdillinger

Differential Revision: D80050344

Pulled By: anand1976

fbshipit-source-id: ace47737d21684ec19709640a09e198cee2d98bd
2025-08-12 15:08:02 -07:00
Karthik Krishnamurthy 0cd6abb91d Fix bug in the generation of index and meta blocks when constructing UDI (#13846)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13846

This diff addresses few issues that was identified during testing of the user defined index.

1. During the finishing of the index blocks, we run into an infinite loop because the user defined index wrapper returns
early on incomplete status. This happens because the wrapper blindly returns the status if it not OK. But, the status
could legitimately be `Incomplete()` for some indices like Partitioned Index (serving as the internal index for the UDI
wrapper). Fix is to exclude `Incomplete()` check from the status check early in the UDI wrapper's finish.

2. Once we fixed (1), we noticed that the meta blocks for the UDI-based index writer were not written out to the final
SST file. This is because the UDI's meta blocks are created after the internal index's meta blocks and the block-based
index builder didn't account for this. The fix is to finish the UDI wrapper first which will create the necessary meta blocks
and then finish the internal index. If the internal index is incomplete, the block-based index builder should still continue
to write out the meta blocks.

3. OnKeyAdded when delegating to the user-defined index should only pass the user key. The UDI builder doesn't
understand RocksDB's internal key format and while that poses interesting challenges when the UDI is used for non
last level SST files, our plan is to restrict the usage of the UDI to last level files only (for now).

Reviewed By: pdillinger

Differential Revision: D79781453

fbshipit-source-id: 2239c8fc016da55df5c24be6aacc8f6357cab029
2025-08-12 15:07:37 -07:00
Xingbo Wang c007dd1da6 Switch back to FSWritableFile in external sst file ingestion job (#13791)
Summary:
This patch reverted "NewRandomRWFile" back to "ReopenWritableFile" in external sst file ingestion job when file is linked instead of copied. The reason is that some of the file systems do not support "NewRandomRWFile". A long term fix is being worked in progress.

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

Test Plan: Unit test

Reviewed By: pdillinger

Differential Revision: D78697825

Pulled By: xingbowang

fbshipit-source-id: d3651223ab1f2369aac34b772bba8049c6c2c628
2025-08-05 10:45:52 -07:00
Jay Huh 0d79386197 Update version.h 2025-08-05 10:26:44 -07:00
Jay Huh 621dbd54fa Update HISTORY.md 2025-08-05 10:06:28 -07:00
Jay Huh 03cfaf3931 Make CompactionPicker::CompactFiles() take earliest_snapshot and snapshot_checker (#13816)
Summary:
One of the parameters for constructing a Compaction object is `earliest_snapshot`, which is required for Standalone Range Deletion Optimization (introduced in [https://github.com/facebook/rocksdb/pull/13078](https://github.com/facebook/rocksdb/pull/13078)). Remote Compaction has been using the `CompactionPicker::CompactFiles()` API to create the Compaction object, but this API never sets the `earliest_snapshot` parameter. To address this, update `CompactionPicker::CompactFiles()` to optionally accept `earliest_snapshot` and pass it during the call in `DBImplSecondary::CompactWithoutInstallation()`.

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

Test Plan:
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.StandaloneDeleteRangeTombstoneOptimization*"
```

\+ Tested in Meta's internal offload infra.

Reviewed By: hx235

Differential Revision: D79284769

Pulled By: jaykorean

fbshipit-source-id: 164834ef6972d5e0ddfc2970bb9234ef166d6e52
2025-08-05 10:05:02 -07:00
Jay Huh 1061f74b7d UnitTest for Remote Compaction Empty Result (#13812)
Summary:
Unit Test for a repro for the fix that was reported by https://github.com/facebook/rocksdb/pull/13743

There's potential dataloss when Remote Compaction entries are all removed due to various reasons (CompactionFilter, DeleteRange covering all keys of the SST file, etc)

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

Test Plan:
```
./compaction_service_test --gtest_filter="*CompactionServiceTest.EmptyResult*"
```

Failed before merging https://github.com/facebook/rocksdb/pull/13743, now passing

Reviewed By: cbi42

Differential Revision: D79192829

Pulled By: jaykorean

fbshipit-source-id: e200300c4a7993de21c63cd92bda65b692921b89
2025-08-05 10:04:30 -07:00
huangmengbin 6a83adcea8 prevent data loss when all entries are expired in Remote Compaction (#13743)
Summary:
**Issue**:
When running remote compaction, if all entries in the input files are expired, RocksDB incorrectly deletes an active file from the primary DB, leading to data loss and corruption.

**Root Cause**:
The current logic mistakenly mixed up the input and output file paths during the cleanup phase when no keys survive the compaction (all expired). This results in deleting the input files (which belong to the primary DB) instead of the output files (which belong to the SecondaryDB).

**Fix**:
Use `GetTableFileName` (virtual function) instead of `TableFileName`

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

Reviewed By: hx235

Differential Revision: D79108650

Pulled By: jaykorean

fbshipit-source-id: 1c9ba971a0e9a62c15ebc014436cb8fc961af95c
2025-08-05 10:04:20 -07:00
Jay Huh ab42881ce7 Update HISTORY.md 2025-07-21 15:12:48 -07:00
Jay Huh 27c544fb29 Expose GetTtl() API in TTL DB (#13790)
Summary:
As title

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

Test Plan:
```
./ttl_test --gtest_filter="*TtlTest.ChangeTtlOnOpenDb*"
```

Reviewed By: cbi42

Differential Revision: D78670347

Pulled By: jaykorean

fbshipit-source-id: 1b2538d6cd0f2a0fbf397a5d2f677852f97272c4
2025-07-21 15:11:49 -07:00
Peter Dillinger 11a61ca109 Support recompress-with-CompressionManager in sst_dump (#13783)
Summary:
So that we can use --command=recompress with a custom CompressionManager. (It's not required for reading files using a custom CompressionManager because those can already use ObjectLibrary for dependency injection.)

Suggested follow-up:
* These tests should not be using C arrays, snprintf, manual delete, etc. except for thin compatibility with argc/argv.

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

Test Plan: unit test added, some manual testing

Reviewed By: archang19

Differential Revision: D78574434

Pulled By: pdillinger

fbshipit-source-id: 609e6c6439090e6b7e9b63fbd4c2d3f04b104fcf
2025-07-21 08:37:50 -07:00
Andrew Chang 462388a434 Update HISTORY.md 2025-07-18 11:47:48 -07:00
37 changed files with 727 additions and 201 deletions

No files matched your search

+1 -1
View File
@@ -182,7 +182,7 @@ jobs:
- uses: actions/checkout@v4.1.0
- uses: "./.github/actions/pre-steps"
- run: ENCRYPTED_ENV=1 ROCKSDB_DISABLE_SNAPPY=1 ROCKSDB_DISABLE_ZLIB=1 ROCKSDB_DISABLE_BZIP=1 ROCKSDB_DISABLE_LZ4=1 ROCKSDB_DISABLE_ZSTD=1 make V=1 J=32 -j32 check
- run: "./sst_dump --help | grep -E -q 'Supported compression types: kNoCompression$' # Verify no compiled in compression\n"
- run: "./sst_dump --help | grep -E -q 'Supported built-in compression types: kNoCompression$' # Verify no compiled in compression\n"
- uses: "./.github/actions/post-steps"
# ======================== Linux No Test Runs ======================= #
build-linux-release:
+33
View File
@@ -1,6 +1,39 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.5.4 (08/21/2025)
### Bug Fixes
* Fix a crash in iterator Prepare() when fill_cache=false
## 10.5.3 (08/14/2025)
### Bug Fixes
* Allow a user defined index to be configured from a string.
## 10.5.2 (08/12/2025)
### Bug Fixes
* Make the User Defined Index interface consistently use the user key format, fixing the previous mixed usage of internal and user key.
## 10.5.1 (08/04/2025)
### Bug Fixes
* Fixed a bug in remote compaction that may mistakenly delete live SST file(s) during the cleanup phase when no keys survive the compaction (all expired)
## 10.5.0 (07/21/2025)
### Public API Changes
* DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
* GetTtl() API is now available in TTL DB
### Behavior Changes
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
* A new API GetFileSize is added to FSRandomAccessFile interface class. It uses fstat vs stat on the posix implementation which is more efficient. Caller could use it to get file size faster. This function might be required in the future for FileSystem implementation outside of the RocksDB code base.
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
### Bug Fixes
* Fix a bug in BackupEngine that can crash backup due to a null FSWritableFile passed to WritableFileWriter.
* Fix DB::NewMultiScan iterator to respect the scan upper bound specified in ScanOptions
### Performance Improvements
* Optimized MultiScan using BlockBasedTable to coalesce I/Os and prefetch all data blocks.
## 10.4.0 (06/20/2025)
### New Features
* Add a new CF option `memtable_avg_op_scan_flush_trigger` that supports triggering memtable flush when an iterator scans through an expensive range of keys, with the average number of skipped keys from the active memtable exceeding the threshold.
+5 -4
View File
@@ -1736,14 +1736,11 @@ Status CompactionJob::FinishCompactionOutputFile(
if (s.ok()) {
tp = outputs.GetTableProperties();
}
if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) {
// If there is nothing to output, no necessary to generate a sst file.
// This happens when the output level is bottom level, at the same time
// the sub_compact output nothing.
std::string fname =
TableFileName(sub_compact->compaction->immutable_options().cf_paths,
meta->fd.GetNumber(), meta->fd.GetPathId());
std::string fname = GetTableFileName(meta->fd.GetNumber());
// TODO(AR) it is not clear if there are any larger implications if
// DeleteFile fails here
@@ -1942,6 +1939,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
// no need to lock because VersionSet::next_file_number_ is atomic
uint64_t file_number = versions_->NewFileNumber();
#ifndef NDEBUG
TEST_SYNC_POINT_CALLBACK(
"CompactionJob::OpenCompactionOutputFile::NewFileNumber", &file_number);
#endif
std::string fname = GetTableFileName(file_number);
// Fire events.
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
+5 -3
View File
@@ -337,7 +337,9 @@ Compaction* CompactionPicker::CompactFiles(
const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files, int output_level,
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, uint32_t output_path_id) {
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
std::optional<SequenceNumber> earliest_snapshot,
const SnapshotChecker* snapshot_checker) {
#ifndef NDEBUG
assert(input_files.size());
// This compaction output should not overlap with a running compaction as
@@ -380,8 +382,8 @@ Compaction* CompactionPicker::CompactFiles(
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
mutable_cf_options.default_write_temperature,
compact_options.max_subcompactions,
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction);
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
CompactionReason::kManualCompaction);
RegisterCompaction(c);
return c;
}
+11 -6
View File
@@ -117,12 +117,17 @@ class CompactionPicker {
// Caller must provide a set of input files that has been passed through
// `SanitizeAndConvertCompactionInputFiles` earlier. The lock should not be
// released between that call and this one.
Compaction* CompactFiles(const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files,
int output_level, VersionStorageInfo* vstorage,
const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options,
uint32_t output_path_id);
//
// TODO - Remove default values for earliest_snapshot and snapshot_checker
// and require all callers to pass them in so that DB::CompactFiles() can
// also benefit from Standalone Range Tombstone Optimization
Compaction* CompactFiles(
const CompactionOptions& compact_options,
const std::vector<CompactionInputFiles>& input_files, int output_level,
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
std::optional<SequenceNumber> earliest_snapshot = std::nullopt,
const SnapshotChecker* snapshot_checker = nullptr);
// Converts a set of compaction input file numbers into
// a list of CompactionInputFiles.
+164
View File
@@ -461,6 +461,97 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
}
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
Options options = CurrentOptions();
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
ReopenWithCompactionService(&options);
size_t num_files_after_filtered = 0;
SyncPoint::GetInstance()->SetCallBack(
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
[&](void* arg) {
num_files_after_filtered = *static_cast<size_t*>(arg);
});
SyncPoint::GetInstance()->EnableProcessing();
std::vector<std::string> files;
{
// Writes first version of data in range partitioned files.
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file1 = dbname_ + "file1.sst";
ASSERT_OK(sst_file_writer.Open(file1));
ASSERT_OK(sst_file_writer.Put("a", "a1"));
ASSERT_OK(sst_file_writer.Put("b", "b1"));
ExternalSstFileInfo file1_info;
ASSERT_OK(sst_file_writer.Finish(&file1_info));
files.push_back(std::move(file1));
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.Put("x", "x1"));
ASSERT_OK(sst_file_writer.Put("y", "y1"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
}
IngestExternalFileOptions ifo;
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_EQ(Get("a"), "a1");
ASSERT_EQ(Get("b"), "b1");
ASSERT_EQ(Get("x"), "x1");
ASSERT_EQ(Get("y"), "y1");
ASSERT_EQ(2, NumTableFilesAtLevel(6));
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
{
// Atomically delete old version of data with one range delete file.
// And a new batch of range partitioned files with new version of data.
files.clear();
SstFileWriter sst_file_writer(EnvOptions(), options);
std::string file2 = dbname_ + "file2.sst";
ASSERT_OK(sst_file_writer.Open(file2));
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
ExternalSstFileInfo file2_info;
ASSERT_OK(sst_file_writer.Finish(&file2_info));
files.push_back(std::move(file2));
std::string file3 = dbname_ + "file3.sst";
ASSERT_OK(sst_file_writer.Open(file3));
ASSERT_OK(sst_file_writer.Put("a", "a2"));
ASSERT_OK(sst_file_writer.Put("b", "b2"));
ExternalSstFileInfo file3_info;
ASSERT_OK(sst_file_writer.Finish(&file3_info));
files.push_back(std::move(file3));
std::string file4 = dbname_ + "file4.sst";
ASSERT_OK(sst_file_writer.Open(file4));
ASSERT_OK(sst_file_writer.Put("x", "x2"));
ASSERT_OK(sst_file_writer.Put("y", "y2"));
ExternalSstFileInfo file4_info;
ASSERT_OK(sst_file_writer.Finish(&file4_info));
files.push_back(std::move(file4));
}
ASSERT_OK(db_->IngestExternalFile(files, ifo));
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
CompactionServiceResult result;
my_cs->GetResult(&result);
ASSERT_OK(result.status);
ASSERT_TRUE(result.stats.is_manual_compaction);
ASSERT_TRUE(result.stats.is_remote_compaction);
ASSERT_EQ(num_files_after_filtered, 1);
Close();
SyncPoint::GetInstance()->DisableProcessing();
}
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
@@ -793,6 +884,79 @@ TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, EmptyResult) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
ReopenWithCompactionService(&options);
GenerateTestData();
auto my_cs = GetCompactionService();
uint64_t comp_num = my_cs->GetCompactionNum();
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
// Delete range to cover entire range
ASSERT_OK(db_->DeleteRange(WriteOptions(), "key", "keyz"));
ASSERT_OK(Flush());
// In this unit test, both remote compaction and primary db instance are
// running in the same process, so NewFileNumber will never have a collision.
// In the real-world remote compactions, when the compaction is indeed running
// in another process, this is not going to be the case.
// To simulate the SST file with the same name created in the tmp directory,
// override the file number in remote compaction to re-use old SST file
// number.
bool need_to_override_file_number = false;
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
[&](void*) { need_to_override_file_number = true; });
SyncPoint::GetInstance()->SetCallBack(
"CompactionJob::OpenCompactionOutputFile::NewFileNumber",
[&](void* file_number) {
if (need_to_override_file_number) {
auto n = static_cast<uint64_t*>(file_number);
ColumnFamilyMetaData cf_meta;
db_->GetColumnFamilyMetaData(&cf_meta);
for (const auto& level : cf_meta.levels) {
for (const auto& file : level.files) {
// Use one of the existing file name
*n = test::GetFileNumber(file.name);
need_to_override_file_number = false;
return;
}
}
}
});
// Inject failure, so that the remote compaction fails after
// ProcessKeyValueCompaction()
SyncPoint::GetInstance()->SetCallBack(
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
// override job status
auto s = static_cast<Status*>(status);
*s = Status::Aborted("MyTestCompactionService failed to compact!");
});
SyncPoint::GetInstance()->EnableProcessing();
// Compaction should fail and SST files in the primary db should exist
{
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ColumnFamilyMetaData meta;
db_->GetColumnFamilyMetaData(&meta);
for (const auto& level : meta.levels) {
for (const auto& file : level.files) {
std::string fname = file.db_path + "/" + file.name;
ASSERT_OK(db_->GetEnv()->FileExists(fname));
}
}
}
Close();
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
TEST_F(CompactionServiceTest, CorruptedOutput) {
Options options = CurrentOptions();
options.disable_auto_compactions = true;
+16 -8
View File
@@ -848,7 +848,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
VersionStorageInfo* vstorage = version->storage_info();
// Use comp_options to reuse some CompactFiles functions
CompactionOptions comp_options;
comp_options.compression = kDisableCompressionOption;
comp_options.output_file_size_limit = MaxFileSizeForLevel(
@@ -867,13 +866,27 @@ Status DBImplSecondary::CompactWithoutInstallation(
return s;
}
const int job_id = next_job_id_.fetch_add(1);
JobContext job_context(job_id, true /*create_superversion*/);
std::vector<SequenceNumber> snapshots = input.snapshots;
// TODO - snapshot_checker support in Remote Compaction
job_context.InitSnapshotContext(/*checker=*/nullptr,
/*managed_snapshot=*/nullptr,
kMaxSequenceNumber, std::move(snapshots));
// TODO - consider serializing the entire Compaction object and using it as
// input instead of recreating it in the remote worker
std::unique_ptr<Compaction> c;
assert(cfd->compaction_picker());
c.reset(cfd->compaction_picker()->CompactFiles(
comp_options, input_files, input.output_level, vstorage,
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0));
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
/*earliest_snapshot=*/job_context.snapshot_seqs.empty()
? kMaxSequenceNumber
: job_context.snapshot_seqs.front(),
job_context.snapshot_checker));
assert(c != nullptr);
c->FinalizeInputInfo(version);
// Create output directory if it's not existed yet
@@ -886,11 +899,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
immutable_db_options_.info_log.get());
const int job_id = next_job_id_.fetch_add(1);
JobContext job_context(0, true /*create_superversion*/);
std::vector<SequenceNumber> snapshots = input.snapshots;
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
std::move(snapshots));
// use primary host's db_id for running the compaction, but db_session_id is
// using the local one, which is to make sure the unique id is unique from
// the remote compactors. Because the id is generated from db_id,
+9 -3
View File
@@ -4142,13 +4142,17 @@ TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
ASSERT_EQ(1, NumTableFilesAtLevel(0));
}
class DBMultiScanIteratorTest : public DBTestBase {
class DBMultiScanIteratorTest : public DBTestBase,
public ::testing::WithParamInterface<bool> {
public:
DBMultiScanIteratorTest()
: DBTestBase("db_multi_scan_iterator_test", /*env_do_fsync=*/true) {}
};
TEST_F(DBMultiScanIteratorTest, BasicTest) {
INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
::testing::Bool());
TEST_P(DBMultiScanIteratorTest, BasicTest) {
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4159,6 +4163,7 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
std::vector<std::string> key_ranges({"k03", "k10", "k25", "k50"});
ReadOptions ro;
ro.fill_cache = GetParam();
std::vector<ScanOptions> scan_options(
{ScanOptions(key_ranges[0], key_ranges[1]),
ScanOptions(key_ranges[2], key_ranges[3])});
@@ -4245,7 +4250,7 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
iter.reset();
}
TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
// Create a file
for (int i = 0; i < 100; ++i) {
std::stringstream ss;
@@ -4257,6 +4262,7 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
std::vector<std::string> key_ranges(
{"k03", "k10", "k25", "k50", "k75", "k90"});
ReadOptions ro;
ro.fill_cache = GetParam();
std::vector<ScanOptions> scan_options(
{ScanOptions(key_ranges[0], key_ranges[1]), ScanOptions(key_ranges[2]),
ScanOptions(key_ranges[4], key_ranges[5])});
-1
View File
@@ -2400,7 +2400,6 @@ class DataVisibilityTest : public DBBasicTestWithTimestampBase {
}
}
};
constexpr int DataVisibilityTest::kTestDataSize;
// Application specifies timestamp but not snapshot.
// reader writer
+3 -7
View File
@@ -156,10 +156,6 @@ Status ExternalSstFileIngestionJob::Prepare(
// It is unsafe to assume application had sync the file and file
// directory before ingest the file. For integrity of RocksDB we need
// to sync the file.
// Use FSRandomRWFile instead of FSWritableFile, as in encrypted file
// system the FSWritableFile will append a new prefix to the end of the
// file when the file exists, which causes file corruption. On the
// contrary, FSRandomRWFile handles an existing file correctly.
// TODO(xingbo), We should in general be moving away from production
// uses of ReuseWritableFile (except explicitly for WAL recycling),
@@ -168,9 +164,9 @@ Status ExternalSstFileIngestionJob::Prepare(
// re-open+sync+close combo but can (a) be reused easily, and (b) be
// overridden to do that more cleanly, e.g. in EncryptedEnv.
// https://github.com/facebook/rocksdb/issues/13741
std::unique_ptr<FSRandomRWFile> file_to_sync;
Status s = fs_->NewRandomRWFile(path_inside_db, env_options_,
&file_to_sync, nullptr);
std::unique_ptr<FSWritableFile> file_to_sync;
Status s = fs_->ReopenWritableFile(path_inside_db, env_options_,
&file_to_sync, nullptr);
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Prepare:Reopen",
&s);
// Some file systems (especially remote/distributed) don't support
+38 -3
View File
@@ -665,17 +665,52 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
std::unique_ptr<FSWritableFile>* result,
IODebugContext* dbg) override {
result->reset();
if (options.use_mmap_writes) {
if (options.use_mmap_reads || options.use_mmap_writes) {
return IOStatus::InvalidArgument();
}
size_t prefix_length = 0;
std::unique_ptr<BlockAccessCipherStream> stream;
// Open file using underlying Env implementation
std::unique_ptr<FSWritableFile> underlying;
IOStatus status =
auto status =
FileSystemWrapper::ReopenWritableFile(fname, options, &underlying, dbg);
if (!status.ok()) {
return status;
}
return CreateWritableEncryptedFile(fname, underlying, options, result, dbg);
if (underlying->GetFileSize(options.io_options, dbg) != 0) {
// read the cipher stream from file for non-empty file
std::unique_ptr<FSRandomAccessFile> underlying_file_reader;
status = FileSystemWrapper::NewRandomAccessFile(
fname, options, &underlying_file_reader, dbg);
if (!status.ok()) {
return status;
}
status = CreateRandomReadCipherStream(
fname, underlying_file_reader, options, &prefix_length, &stream, dbg);
if (!status.ok()) {
return status;
}
} else {
// create cipher stream for new or empty file
status = CreateWritableCipherStream(fname, underlying, options,
&prefix_length, &stream, dbg);
if (!status.ok()) {
return status;
}
}
if (stream) {
result->reset(new EncryptedWritableFile(
std::move(underlying), std::move(stream), prefix_length));
} else {
result->reset(underlying.release());
}
return status;
}
IOStatus ReuseWritableFile(const std::string& fname,
+26
View File
@@ -27,6 +27,11 @@ inline const std::string kUserDefinedIndexPrefix =
// It allows users to define their own index format and build custom
// indexes during table building. Currently, only a monolithic index
// block is supported (no partitioned index).
//
// This is currently supported only for a restricted set of use cases. The
// CF must be ingest only, and only files containing Puts generated by
// SstFileWriter are supported. The user_comparator used for the CF must
// be BytewiseComparator.
// The interface for building user-defined index.
class UserDefinedIndexBuilder {
@@ -51,6 +56,10 @@ class UserDefinedIndexBuilder {
// The previous index entry key and the new index entry key cover
// all the keys in the data block associated with the new index entry.
//
// The last_key_in_current_block and first_key_in_next_block will be user
// keys, i.e the user key string, and optionally the user timestamp if one
// is configured, without a sequence number suffix.
//
// Called before the OnKeyAdded() call for first_key_in_next_block.
// @last_key_in_current_block: The last key in the current data block
// @first_key_in_next_block: it will be nullptr if the entry being added is
@@ -72,6 +81,9 @@ class UserDefinedIndexBuilder {
// override OnKeyAdded() if they need to collect additional information.
// The type argument indicates whether the value is a full value or partial.
// At the moment, only full values are supported.
//
// The key will be a user key. RocksDB guarantees that there will only be
// one entry for each key in the file/index.
virtual void OnKeyAdded(const Slice& /*key*/, ValueType /*type*/,
const Slice& /*value*/) {}
@@ -100,6 +112,14 @@ class UserDefinedIndexIterator {
// termination criteria, kInbound if the data block is definitely fully
// within bounds, or kUnknown if the data block could be partially
// within bounds.
// The UDI implementation needs to be careful about returning kOutOfBound.
// If a limit key is specified in ScanOptions, an implementation that
// does not store the first key in the block for the corresponding index
// entry cannot reliably determine if the block is out of bounds. It must
// compare against the previous index key to determine if the current block
// is out of bounds w.r.t the limit. Other termination criteria (specified
// in property_bag) may cause the scan to terminate earlier, in which case
// kOutOfBound can be returned earlier.
virtual Status SeekAndGetResult(const Slice& target,
IterateResult* result) = 0;
@@ -130,6 +150,12 @@ class UserDefinedIndexFactory : public Customizable {
public:
virtual ~UserDefinedIndexFactory() = default;
static const char* Type() { return "UserDefinedIndexFactory"; }
static Status CreateFromString(
const ConfigOptions& config_options, const std::string& value,
std::shared_ptr<UserDefinedIndexFactory>* factory);
// Create a new builder for user-defined index.
virtual UserDefinedIndexBuilder* NewBuilder() const = 0;
+2
View File
@@ -63,6 +63,8 @@ class DBWithTTL : public StackableDB {
virtual void SetTtl(ColumnFamilyHandle* h, int32_t ttl) = 0;
virtual Status GetTtl(ColumnFamilyHandle* h, int32_t* ttl) = 0;
protected:
explicit DBWithTTL(DB* db) : StackableDB(db) {}
};
+1 -1
View File
@@ -13,7 +13,7 @@
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 10
#define ROCKSDB_MINOR 5
#define ROCKSDB_PATCH 0
#define ROCKSDB_PATCH 5
// Do not use these. We made the mistake of declaring macros starting with
// double underscore. Now we have to live with our choice. We'll deprecate these
+3
View File
@@ -72,6 +72,9 @@ std::unique_ptr<Configurable> CFOptionsAsConfigurable(
Status StringToMap(const std::string& opts_str,
std::unordered_map<std::string, std::string>* opts_map);
Status GetStringFromCompressionType(std::string* compression_str,
CompressionType compression_type);
struct OptionsHelper {
static const std::string kCFOptionsName /*= "ColumnFamilyOptions"*/;
static const std::string kDBOptionsName /*= "DBOptions" */;
+14 -7
View File
@@ -907,6 +907,12 @@ struct BlockBasedTableBuilder::Rep {
SetStatus(
Status::InvalidArgument("user_defined_index_factory not supported "
"with parallel compression"));
} else if (ioptions.user_comparator != BytewiseComparator()) {
// TODO: Pass the user_comparator to the UDI and let it validate. Do
// it in a major release.
SetStatus(
Status::InvalidArgument("user_defined_index_factory only supported "
"with bytewise comparator"));
} else {
std::unique_ptr<UserDefinedIndexBuilder> user_defined_index_builder(
table_options.user_defined_index_factory->NewBuilder());
@@ -1058,7 +1064,7 @@ struct BlockBasedTableBuilder::Rep {
char* ptr = compression_name.data() + pos;
// Populate the field contents
for (CompressionType t : compression_types_used) {
PutBaseChars<16>(&ptr, /*digits=*/2, static_cast<unsigned char>(t),
PutBaseChars<16>(&ptr, /*n=*/2, static_cast<unsigned char>(t),
/*uppercase=*/true);
}
assert(ptr == compression_name.data() + pos + ctype_count * 2);
@@ -1827,14 +1833,15 @@ void BlockBasedTableBuilder::WriteIndexBlock(
}
IndexBuilder::IndexBlocks index_blocks;
auto index_builder_status = rep_->index_builder->Finish(&index_blocks);
if (index_builder_status.IsIncomplete()) {
// We we have more than one index partition then meta_blocks are not
// supported for the index. Currently meta_blocks are used only by
// HashIndexBuilder which is not multi-partition.
assert(index_blocks.meta_blocks.empty());
} else if (ok() && !index_builder_status.ok()) {
if (ok() && !index_builder_status.ok() &&
!index_builder_status.IsIncomplete()) {
// If the index builder failed for non-Incomplete errors, we should
// mark the entire builder as having failed wit that status. However,
// If the index builder failed with an incomplete error, we should
// continue writing out any meta blocks that may have been generated.
rep_->SetStatus(index_builder_status);
}
if (ok()) {
for (const auto& item : index_blocks.meta_blocks) {
BlockHandle block_handle;
@@ -25,6 +25,8 @@
#include "rocksdb/flush_block_policy.h"
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/table.h"
#include "rocksdb/user_defined_index.h"
#include "rocksdb/utilities/customizable_util.h"
#include "rocksdb/utilities/options_type.h"
#include "table/block_based/block_based_table_builder.h"
#include "table/block_based/block_based_table_reader.h"
@@ -312,6 +314,11 @@ static struct BlockBasedTableTypeInfo {
OptionTypeInfo::AsCustomSharedPtr<const FilterPolicy>(
offsetof(struct BlockBasedTableOptions, filter_policy),
OptionVerificationType::kByNameAllowFromNull)},
{"user_defined_index_factory",
OptionTypeInfo::AsCustomSharedPtr<UserDefinedIndexFactory>(
offsetof(struct BlockBasedTableOptions,
user_defined_index_factory),
OptionVerificationType::kByNameAllowFromNull)},
{"whole_key_filtering",
{offsetof(struct BlockBasedTableOptions, whole_key_filtering),
OptionType::kBoolean, OptionVerificationType::kNormal}},
@@ -1011,6 +1018,13 @@ TableFactory* NewBlockBasedTableFactory(
return new BlockBasedTableFactory(_table_options);
}
Status UserDefinedIndexFactory::CreateFromString(
const ConfigOptions& config_options, const std::string& value,
std::shared_ptr<UserDefinedIndexFactory>* factory) {
return LoadSharedObject<UserDefinedIndexFactory>(config_options, value,
factory);
}
const std::string BlockBasedTablePropertyNames::kIndexType =
"rocksdb.block.based.table.index.type";
const std::string BlockBasedTablePropertyNames::kWholeKeyFiltering =
@@ -1162,7 +1162,8 @@ void BlockBasedTableIterator::Prepare(
#endif
assert(pinned_data_blocks_guard[block_idx].IsEmpty());
s = table_->CreateAndPinBlockInCache<Block_kData>(
read_options_, block, &tmp_contents,
read_options_, block, table_->get_rep()->decompressor.get(),
&tmp_contents,
&(pinned_data_blocks_guard[block_idx].As<Block_kData>()));
if (!s.ok()) {
// Abort: failed to create and pin block in cache
+50 -8
View File
@@ -109,8 +109,8 @@ CacheAllocationPtr CopyBufferToHeap(MemoryAllocator* allocator, Slice& buf) {
CachableEntry<T>* out_parsed_block) const; \
template Status BlockBasedTable::CreateAndPinBlockInCache<T>( \
const ReadOptions& ro, const BlockHandle& handle, \
BlockContents* block_contents, CachableEntry<T>* out_parsed_block) \
const;
UnownedPtr<Decompressor> decomp, BlockContents* block_contents, \
CachableEntry<T>* out_parsed_block) const;
INSTANTIATE_BLOCKLIKE_TEMPLATES(ParsedFullFilterBlock);
INSTANTIATE_BLOCKLIKE_TEMPLATES(DecompressorDict);
@@ -1741,13 +1741,55 @@ Status BlockBasedTable::LookupAndPinBlocksInCache(
template <typename TBlocklike>
Status BlockBasedTable::CreateAndPinBlockInCache(
const ReadOptions& ro, const BlockHandle& handle, BlockContents* contents,
const ReadOptions& ro, const BlockHandle& handle,
UnownedPtr<Decompressor> decomp, BlockContents* contents,
CachableEntry<TBlocklike>* out_parsed_block) const {
return MaybeReadBlockAndLoadToCache(
nullptr, ro, handle, rep_->decompressor.get(),
/*for_compaction=*/false, out_parsed_block, nullptr, nullptr, contents,
/*async_read=*/false,
/*use_block_cache_for_lookup=*/true);
CompressionType compression_type = GetBlockCompressionType(*contents);
// If we don't own the contents and we don't need to decompress, copy
// the block to heap in order to have ownership. If decompression is
// needed, then the decompressor will allocate a buffer.
if (!contents->own_bytes() && compression_type == kNoCompression) {
Slice src = Slice(contents->data.data(), BlockSizeWithTrailer(handle));
*contents = BlockContents(
CopyBufferToHeap(GetMemoryAllocator(rep_->table_options), src),
handle.size());
#ifndef NDEBUG
contents->has_trailer = true;
#endif
}
Status s;
if (ro.fill_cache) {
s = MaybeReadBlockAndLoadToCache(nullptr, ro, handle, decomp,
/*for_compaction=*/false, out_parsed_block,
nullptr, nullptr, contents,
/*async_read=*/false,
/*use_block_cache_for_lookup=*/true);
}
if (!s.ok()) {
return s;
}
// fill_cache could be false, or no block cache is configured. In that
// case, decompress if necessary and take ownership of the block
if (out_parsed_block->GetValue() == nullptr && contents != nullptr) {
BlockContents tmp_contents;
if (compression_type != kNoCompression) {
s = DecompressSerializedBlock(contents->data.data(), handle.size(),
compression_type, *decomp, &tmp_contents,
rep_->ioptions,
GetMemoryAllocator(rep_->table_options));
} else {
tmp_contents = std::move(*contents);
}
if (s.ok()) {
std::unique_ptr<TBlocklike> block_holder;
rep_->create_context.Create(&block_holder, std::move(tmp_contents));
out_parsed_block->SetOwnedValue(std::move(block_holder));
}
}
return s;
}
// If contents is nullptr, this function looks up the block caches for the
+1 -1
View File
@@ -311,7 +311,7 @@ class BlockBasedTable : public TableReader {
template <typename TBlocklike>
Status CreateAndPinBlockInCache(
const ReadOptions& ro, const BlockHandle& handle,
BlockContents* block_contents,
UnownedPtr<Decompressor> decomp, BlockContents* block_contents,
CachableEntry<TBlocklike>* out_parsed_block) const;
struct Rep;
@@ -37,8 +37,6 @@ DEFINE_SYNC_AND_ASYNC(void, BlockBasedTable::RetrieveMultipleBlocks)
RandomAccessFileReader* file = rep_->file.get();
const Footer& footer = rep_->footer;
const ImmutableOptions& ioptions = rep_->ioptions;
size_t read_amp_bytes_per_bit = rep_->table_options.read_amp_bytes_per_bit;
MemoryAllocator* memory_allocator = GetMemoryAllocator(rep_->table_options);
if (ioptions.allow_mmap_reads) {
size_t idx_in_batch = 0;
@@ -266,79 +264,8 @@ DEFINE_SYNC_AND_ASYNC(void, BlockBasedTable::RetrieveMultipleBlocks)
}
if (s.ok()) {
// When the blocks share the same underlying buffer (scratch or direct io
// buffer), we may need to manually copy the block into heap if the
// serialized block has to be inserted into a cache. That falls into the
// following cases -
// 1. serialized block is not compressed, it needs to be inserted into
// the uncompressed block cache if there is one
// 2. If the serialized block is compressed, it needs to be inserted
// into the compressed block cache if there is one
//
// In all other cases, the serialized block is either uncompressed into a
// heap buffer or there is no cache at all.
CompressionType compression_type =
GetBlockCompressionType(serialized_block);
if ((use_fs_scratch || use_shared_buffer) &&
compression_type == kNoCompression) {
Slice serialized =
Slice(req.result.data() + req_offset, BlockSizeWithTrailer(handle));
serialized_block = BlockContents(
CopyBufferToHeap(GetMemoryAllocator(rep_->table_options),
serialized),
handle.size());
#ifndef NDEBUG
serialized_block.has_trailer = true;
#endif
}
}
if (s.ok()) {
if (options.fill_cache) {
CachableEntry<Block_kData>* block_entry = &results[idx_in_batch];
// MaybeReadBlockAndLoadToCache will insert into the block caches if
// necessary. Since we're passing the serialized block contents, it
// will avoid looking up the block cache
s = MaybeReadBlockAndLoadToCache(
nullptr, options, handle, decomp,
/*for_compaction=*/false, block_entry, mget_iter->get_context,
/*lookup_context=*/nullptr, &serialized_block,
/*async_read=*/false, /*use_block_cache_for_lookup=*/true);
if (!s.ok()) {
statuses[idx_in_batch] = s;
continue;
}
// block_entry value could be null if no block cache is present, i.e
// BlockBasedTableOptions::no_block_cache is true and no compressed
// block cache is configured. In that case, fall
// through and set up the block explicitly
if (block_entry->GetValue() != nullptr) {
continue;
}
}
CompressionType compression_type =
GetBlockCompressionType(serialized_block);
BlockContents contents;
if (compression_type != kNoCompression) {
s = DecompressSerializedBlock(
req.result.data() + req_offset, handle.size(), compression_type,
*decomp, &contents, rep_->ioptions, memory_allocator);
} else {
// There are two cases here:
// 1) caller uses the shared buffer (scratch or direct io buffer);
// 2) we use the requst buffer.
// If scratch buffer or direct io buffer is used, we ensure that
// all serialized blocks are copyed to the heap as single blocks. If
// scratch buffer is not used, we also have no combined read, so the
// serialized block can be used directly.
contents = std::move(serialized_block);
}
if (s.ok()) {
results[idx_in_batch].SetOwnedValue(std::make_unique<Block_kData>(
std::move(contents), read_amp_bytes_per_bit, ioptions.stats));
}
s = CreateAndPinBlockInCache(options, handle, decomp, &serialized_block,
&results[idx_in_batch]);
}
statuses[idx_in_batch] = s;
}
+71 -26
View File
@@ -46,9 +46,24 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
handle.offset = block_handle.offset();
handle.size = block_handle.size();
// Forward the call to both index builders
user_defined_index_builder_->AddIndexEntry(last_key_in_current_block,
first_key_in_next_block, handle,
separator_scratch);
ParsedInternalKey pkey_last;
ParsedInternalKey pkey_first;
// There's no way to return an error here, so we remember the statsu and
// return it in Finish()
if (status_.ok()) {
status_ = ParseInternalKey(last_key_in_current_block, &pkey_last,
/*lof_err_key*/ false);
}
if (status_.ok() && first_key_in_next_block) {
status_ = ParseInternalKey(*first_key_in_next_block, &pkey_first,
/*lof_err_key*/ false);
}
if (status_.ok()) {
user_defined_index_builder_->AddIndexEntry(
pkey_last.user_key,
first_key_in_next_block ? &pkey_first.user_key : nullptr, handle,
separator_scratch);
}
return internal_index_builder_->AddIndexEntry(
last_key_in_current_block, first_key_in_next_block, block_handle,
separator_scratch);
@@ -56,13 +71,13 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
void OnKeyAdded(const Slice& key,
const std::optional<Slice>& value) override {
ParsedInternalKey pkey;
if (status_.ok()) {
if (!value.has_value()) {
status_ = Status::InvalidArgument(
"user_defined_index_factory not supported with parallel "
"compression");
} else {
ParsedInternalKey pkey;
status_ = ParseInternalKey(key, &pkey, /*lof_err_key*/ false);
if (status_.ok() && pkey.type != ValueType::kTypeValue) {
status_ = Status::InvalidArgument(
@@ -76,16 +91,38 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
// Forward the call to both index builders
internal_index_builder_->OnKeyAdded(key, value);
// Pass the user key to the UDI. We don't expect multiple entries with
// different sequence numbers for the same key in the file. RocksDB may
// enforce it in the future by allowing UDIs only for read only
// bulkloaded use cases, and only allow ingestion of files with
// sequence number 0.
user_defined_index_builder_->OnKeyAdded(
key, UserDefinedIndexBuilder::ValueType::kValue, value.value());
pkey.user_key, UserDefinedIndexBuilder::ValueType::kValue,
value.value());
}
Status Finish(IndexBlocks* index_blocks,
const BlockHandle& last_partition_block_handle) override {
if (!status_.ok()) {
if (!status_.ok() && !status_.IsIncomplete()) {
return status_;
}
if (!udi_finished_) {
// Finish the user defined index builder
Slice user_index_contents;
status_ = user_defined_index_builder_->Finish(&user_index_contents);
if (!status_.ok()) {
return status_;
}
// Add the user defined index to the meta blocks
std::string block_name = kUserDefinedIndexPrefix + name_;
index_blocks->meta_blocks.insert(
{block_name, {BlockType::kUserDefinedIndex, user_index_contents}});
udi_finished_ = true;
}
// Finish the internal index builder
status_ = internal_index_builder_->Finish(index_blocks,
last_partition_block_handle);
@@ -93,18 +130,6 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
return status_;
}
// Finish the user defined index builder
Slice user_index_contents;
status_ = user_defined_index_builder_->Finish(&user_index_contents);
if (!status_.ok()) {
return status_;
}
// Add the user defined index to the meta blocks
std::string block_name = kUserDefinedIndexPrefix + name_;
index_blocks->meta_blocks.insert(
{block_name, {BlockType::kUserDefinedIndex, user_index_contents}});
index_size_ = internal_index_builder_->IndexSize();
return status_;
}
@@ -120,6 +145,7 @@ class UserDefinedIndexBuilderWrapper : public IndexBuilder {
std::unique_ptr<IndexBuilder> internal_index_builder_;
std::unique_ptr<UserDefinedIndexBuilder> user_defined_index_builder_;
Status status_;
bool udi_finished_ = false;
};
class UserDefinedIndexIteratorWrapper
@@ -144,23 +170,41 @@ class UserDefinedIndexIteratorWrapper
status_ = ParseInternalKey(target, &pkey, /*log_err_key=*/false);
if (status_.ok()) {
status_ = udi_iter_->SeekAndGetResult(pkey.user_key, &result_);
valid_ = status_.ok() &&
result_.bound_check_result == IterBoundCheck::kInbound;
if (status_.ok()) {
valid_ = result_.bound_check_result == IterBoundCheck::kInbound;
if (valid_) {
ikey_.Set(result_.key, 0, ValueType::kTypeValue);
}
}
} else {
valid_ = false;
}
}
void Next() override {
status_ = udi_iter_->NextAndGetResult(&result_);
valid_ =
status_.ok() && result_.bound_check_result == IterBoundCheck::kInbound;
if (status_.ok()) {
valid_ = result_.bound_check_result == IterBoundCheck::kInbound;
if (valid_) {
ikey_.Set(result_.key, 0, ValueType::kTypeValue);
}
} else {
valid_ = false;
}
}
bool NextAndGetResult(IterateResult* result) override {
status_ = udi_iter_->NextAndGetResult(&result_);
valid_ =
status_.ok() && result_.bound_check_result == IterBoundCheck::kInbound;
if (status_.ok()) {
*result = result_;
valid_ = result_.bound_check_result == IterBoundCheck::kInbound;
if (valid_) {
ikey_.Set(result_.key, 0, ValueType::kTypeValue);
}
if (status_.ok()) {
*result = result_;
}
} else {
valid_ = false;
}
return valid_;
}
@@ -171,7 +215,7 @@ class UserDefinedIndexIteratorWrapper
void Prev() override { status_ = Status::NotSupported("Prev not supported"); }
Slice key() const override { return result_.key; }
Slice key() const override { return Slice(*ikey_.const_rep()); }
IndexValue value() const override {
auto handle = udi_iter_->value();
@@ -188,6 +232,7 @@ class UserDefinedIndexIteratorWrapper
private:
std::unique_ptr<UserDefinedIndexIterator> udi_iter_;
IterateResult result_;
InternalKey ikey_;
Status status_;
bool valid_;
};
+13 -8
View File
@@ -260,16 +260,21 @@ Status SstFileDumper::CalculateCompressedTableSize(
}
Status SstFileDumper::ShowAllCompressionSizes(
size_t block_size,
const std::vector<std::pair<CompressionType, const char*>>&
compression_types,
size_t block_size, const std::vector<CompressionType>& compression_types,
int32_t compress_level_from, int32_t compress_level_to,
uint32_t max_dict_bytes, uint32_t zstd_max_train_bytes,
uint64_t max_dict_buffer_bytes, bool use_zstd_dict_trainer) {
fprintf(stdout, "Block Size: %" ROCKSDB_PRIszt "\n", block_size);
for (auto& i : compression_types) {
if (CompressionTypeSupported(i.first)) {
fprintf(stdout, "Compression: %-24s\n", i.second);
for (CompressionType ctype : compression_types) {
std::string cname;
if (!GetStringFromCompressionType(&cname, ctype).ok()) {
// Can produce names like "Reserved4F" for unrecognized values
cname = CompressionTypeToString(ctype);
}
if (options_.compression_manager
? options_.compression_manager->SupportsCompressionType(ctype)
: CompressionTypeSupported(ctype)) {
fprintf(stdout, "Compression: %-24s\n", cname.c_str());
CompressionOptions compress_opt;
compress_opt.max_dict_bytes = max_dict_bytes;
compress_opt.zstd_max_train_bytes = zstd_max_train_bytes;
@@ -278,13 +283,13 @@ Status SstFileDumper::ShowAllCompressionSizes(
for (int32_t j = compress_level_from; j <= compress_level_to; j++) {
fprintf(stdout, "Compression level: %d", j);
compress_opt.level = j;
Status s = ShowCompressionSize(block_size, i.first, compress_opt);
Status s = ShowCompressionSize(block_size, ctype, compress_opt);
if (!s.ok()) {
return s;
}
}
} else {
fprintf(stdout, "Unsupported compression type: %s.\n", i.second);
fprintf(stdout, "Unsupported compression type: %s.\n", cname.c_str());
}
}
return Status::OK();
+1 -3
View File
@@ -43,9 +43,7 @@ class SstFileDumper {
Status getStatus() { return init_result_; }
Status ShowAllCompressionSizes(
size_t block_size,
const std::vector<std::pair<CompressionType, const char*>>&
compression_types,
size_t block_size, const std::vector<CompressionType>& compression_types,
int32_t compress_level_from, int32_t compress_level_to,
uint32_t max_dict_bytes, uint32_t zstd_max_train_bytes,
uint64_t max_dict_buffer_bytes, bool use_zstd_dict_trainer);
+110 -5
View File
@@ -53,6 +53,7 @@
#include "rocksdb/trace_record.h"
#include "rocksdb/unique_id.h"
#include "rocksdb/user_defined_index.h"
#include "rocksdb/utilities/object_registry.h"
#include "rocksdb/write_buffer_manager.h"
#include "table/block_based/block.h"
#include "table/block_based/block_based_table_builder.h"
@@ -2059,7 +2060,7 @@ TEST_P(BlockBasedTableTest, PrefetchTest) {
// Simple
PrefetchRange(&c, &opt, &table_options,
/*key_range=*/"k01", "k05",
/*key_begin=*/"k01", /*key_end=*/"k05",
/*keys_in_cache=*/{"k01", "k02", "k03", "k04", "k05"},
/*keys_not_in_cache=*/{"k06", "k07"});
PrefetchRange(&c, &opt, &table_options, "k01", "k01", {"k01", "k02", "k03"},
@@ -7457,8 +7458,11 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
const Slice* first_key_in_next_block,
const BlockHandle& block_handle,
std::string* separator_scratch) override {
EXPECT_EQ(last_key_in_current_block.size(), 5);
if (first_key_in_next_block) {
EXPECT_EQ(first_key_in_next_block->size(), 5);
}
// Unused parameters
(void)first_key_in_next_block;
(void)separator_scratch;
entries_added_++;
// Store the block handle for each key
@@ -7472,8 +7476,9 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
return last_key_in_current_block;
}
void OnKeyAdded(const Slice& /*key*/, ValueType /*value*/,
void OnKeyAdded(const Slice& key, ValueType /*value*/,
const Slice& /*value*/) override {
EXPECT_EQ(key.size(), 5);
// Track keys added to the index
keys_added_++;
}
@@ -7657,9 +7662,12 @@ class UserDefinedIndexTest : public BlockBasedTableTestBase {
index_data_;
};
};
protected:
void BasicTest(bool use_partitioned_index);
};
TEST_F(UserDefinedIndexTest, BasicTest) {
void UserDefinedIndexTest::BasicTest(bool use_partitioned_index) {
Options options;
BlockBasedTableOptions table_options;
std::string dbname = test::PerThreadDBPath("user_defined_index_test");
@@ -7669,7 +7677,11 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
auto user_defined_index_factory =
std::make_shared<TestUserDefinedIndexFactory>();
table_options.user_defined_index_factory = user_defined_index_factory;
if (use_partitioned_index) {
table_options.partition_filters = true;
table_options.decouple_partitioned_filters = true;
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
}
// Set up custom flush block policy that flushes every 3 keys
table_options.flush_block_policy_factory =
std::make_shared<CustomFlushBlockPolicyFactory>();
@@ -7809,6 +7821,14 @@ TEST_F(UserDefinedIndexTest, BasicTest) {
ASSERT_OK(iter->status());
}
TEST_F(UserDefinedIndexTest, BasicTestWithPartitionedIndex) {
BasicTest(/*use_partitioned_index=*/true);
}
TEST_F(UserDefinedIndexTest, BasicTestWithoutPartitionedIndex) {
BasicTest(/*use_partitioned_index=*/false);
}
TEST_F(UserDefinedIndexTest, InvalidArgumentTest1) {
Options options;
BlockBasedTableOptions table_options;
@@ -8024,6 +8044,91 @@ TEST_F(UserDefinedIndexTest, IngestFailTest) {
ASSERT_OK(db->Close());
ASSERT_OK(DestroyDB(dbname, options));
}
TEST_F(UserDefinedIndexTest, ConfigTest) {
Options options;
BlockBasedTableOptions table_options;
std::string dbname = test::PerThreadDBPath("user_defined_index_test");
std::string ingest_file = dbname + "test.sst";
// Set up the user-defined index factory
auto user_defined_index_factory =
std::make_shared<TestUserDefinedIndexFactory>();
table_options.user_defined_index_factory = user_defined_index_factory;
// Set up custom flush block policy that flushes every 3 keys
table_options.flush_block_policy_factory =
std::make_shared<CustomFlushBlockPolicyFactory>();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
std::unique_ptr<SstFileWriter> writer;
writer.reset(new SstFileWriter(EnvOptions(), options));
ASSERT_OK(writer->Open(ingest_file));
// Add 100 keys instead of just 5
for (int i = 0; i < 100; i++) {
std::stringstream ss;
ss << std::setw(2) << std::setfill('0') << i;
std::string key = "key" + ss.str();
std::string value = "value" + ss.str();
ASSERT_OK(writer->Put(key, value));
}
ASSERT_OK(writer->Finish());
writer.reset();
table_options.user_defined_index_factory.reset();
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
// Set up the user-defined index factory
ObjectLibrary::Default().get()->AddFactory<UserDefinedIndexFactory>(
"test_index", [](const std::string& /* uri */,
std::unique_ptr<UserDefinedIndexFactory>* guard,
std::string* /* errmsg */) {
auto factory = new TestUserDefinedIndexFactory();
guard->reset(factory);
return guard->get();
});
ASSERT_OK(GetColumnFamilyOptionsFromString(
ConfigOptions(), options,
"block_based_table_factory={user_defined_index_factory=test_index;}",
&options));
std::unique_ptr<DB> db;
options.create_if_missing = true;
Status s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
ColumnFamilyHandle* cfh = nullptr;
ASSERT_OK(db->CreateColumnFamily(options, "new_cf", &cfh));
IngestExternalFileOptions ifo;
s = db->IngestExternalFile(cfh, {ingest_file}, ifo);
ASSERT_OK(s);
ReadOptions ro;
ro.table_index_factory = user_defined_index_factory.get();
std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
ASSERT_NE(iter, nullptr);
std::vector<ScanOptions> scan_opts({ScanOptions("key20")});
scan_opts[0].property_bag.emplace().emplace("count", std::to_string(25));
iter->Prepare(scan_opts);
// Test that we can read all the keys
int key_count = 0;
for (iter->Seek(scan_opts[0].range.start.value()); iter->Valid();
iter->Next()) {
key_count++;
}
ASSERT_GE(key_count, 25);
// The index may undercount by 2 blocks
ASSERT_LE(key_count, 30);
ASSERT_OK(iter->status());
iter.reset();
ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
ASSERT_OK(db->Close());
ASSERT_OK(DestroyDB(dbname, options));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+71 -1
View File
@@ -15,6 +15,7 @@
#include "rocksdb/convenience.h"
#include "rocksdb/filter_policy.h"
#include "rocksdb/sst_dump_tool.h"
#include "rocksdb/utilities/object_registry.h"
#include "table/block_based/block_based_table_factory.h"
#include "table/sst_file_dumper.h"
#include "table/table_builder.h"
@@ -176,7 +177,6 @@ class SSTDumpToolTest : public testing::Test {
constexpr static int kNumKey = 1024;
};
constexpr int SSTDumpToolTest::kNumKey;
TEST_F(SSTDumpToolTest, HelpAndVersion) {
Options opts;
@@ -346,6 +346,76 @@ TEST_F(SSTDumpToolTest, CompressedSizes) {
}
}
namespace {
using Compressor8A = test::CompressorCustomAlg<kCustomCompression8A>;
class MyManager : public CompressionManager {
public:
static constexpr const char* kCompatibilityName = "SSTDumpToolTest:MyManager";
const char* Name() const override { return kCompatibilityName; }
const char* CompatibilityName() const override { return kCompatibilityName; }
bool SupportsCompressionType(CompressionType type) const override {
return type == kCustomCompression8A;
}
std::unique_ptr<Compressor> GetCompressor(const CompressionOptions& /*opts*/,
CompressionType type) override {
switch (static_cast<unsigned char>(type)) {
case kCustomCompression8A:
return std::make_unique<Compressor8A>();
default:
return nullptr;
}
}
std::shared_ptr<Decompressor> GetDecompressor() override {
return std::make_shared<test::DecompressorCustomAlg>();
}
};
} // namespace
TEST_F(SSTDumpToolTest, CompressionManager) {
if (!Compressor8A::Supported()) {
fprintf(stderr,
"Prerequisite compression library not supported. Skipping\n");
return;
}
// Registery in ObjectLibrary to check that sst_dump can use named
// CompressionManagers with dependency injection
auto& library = *ObjectLibrary::Default();
library.AddFactory<CompressionManager>(
MyManager::kCompatibilityName,
[](const std::string& /*uri*/, std::unique_ptr<CompressionManager>* guard,
std::string* /*errmsg*/) {
*guard = std::make_unique<MyManager>();
return guard->get();
});
Options opts;
opts.env = env();
BlockBasedTableOptions table_opts;
table_opts.filter_policy.reset(
ROCKSDB_NAMESPACE::NewBloomFilterPolicy(10, false));
opts.table_factory.reset(new BlockBasedTableFactory(table_opts));
std::string file_path = MakeFilePath("rocksdb_sst_test.sst");
createSST(opts, file_path, 10);
char* usage[5];
PopulateCommandArgs(file_path, "--command=recompress", usage);
snprintf(usage[3], kOptLength, "--compression_manager=%s",
MyManager::kCompatibilityName);
snprintf(usage[4], kOptLength, "--compression_types=kCustomCompression8A");
ROCKSDB_NAMESPACE::SSTDumpTool tool;
ASSERT_TRUE(!tool.Run(5, usage, opts));
cleanup(opts, file_path);
for (int i = 0; i < 5; i++) {
delete[] usage[i];
}
}
TEST_F(SSTDumpToolTest, MemEnv) {
std::unique_ptr<Env> mem_env(NewMemEnv(env()));
Options opts;
+37 -22
View File
@@ -17,17 +17,6 @@
namespace ROCKSDB_NAMESPACE {
static const std::vector<std::pair<CompressionType, const char*>>
kCompressions = {
{CompressionType::kNoCompression, "kNoCompression"},
{CompressionType::kSnappyCompression, "kSnappyCompression"},
{CompressionType::kZlibCompression, "kZlibCompression"},
{CompressionType::kBZip2Compression, "kBZip2Compression"},
{CompressionType::kLZ4Compression, "kLZ4Compression"},
{CompressionType::kLZ4HCCompression, "kLZ4HCCompression"},
{CompressionType::kXpressCompression, "kXpressCompression"},
{CompressionType::kZSTD, "kZSTD"}};
namespace {
void print_help(bool to_stderr) {
@@ -98,10 +87,15 @@ void print_help(bool to_stderr) {
be used when trying different compression algorithms
--compression_types=<comma-separated list of CompressionType members, e.g.,
kSnappyCompression>
kSnappyCompression or kCustomCompressionC4>
Can be combined with --command=recompress to run recompression for this
list of compression types
Supported compression types: %s
Supported built-in compression types: %s
--compression_manager=<compression manager string>
Used with --command=recompress to specify a compression manager to use
instead of the built-in compression manager, which may support a
different set of compression types.
--parse_internal_key=<0xKEY>
Convenience option to parse an internal key on the command line. Dumps the
@@ -178,7 +172,8 @@ int SSTDumpTool::Run(int argc, char const* const* argv, Options options) {
std::string compression_level_to_str;
size_t block_size = 0;
size_t readahead_size = 2 * 1024 * 1024;
std::vector<std::pair<CompressionType, const char*>> compression_types;
std::vector<CompressionType> compression_types;
std::shared_ptr<CompressionManager> compression_manager;
uint64_t total_num_files = 0;
uint64_t total_num_data_blocks = 0;
uint64_t total_data_block_size = 0;
@@ -244,19 +239,36 @@ int SSTDumpTool::Run(int argc, char const* const* argv, Options options) {
std::istringstream iss(compression_types_csv);
std::string compression_type;
has_specified_compression_types = true;
while (std::getline(iss, compression_type, ',')) {
auto iter = std::find_if(
kCompressions.begin(), kCompressions.end(),
[&compression_type](std::pair<CompressionType, const char*> curr) {
return curr.second == compression_type;
});
if (iter == kCompressions.end()) {
auto iter =
OptionsHelper::compression_type_string_map.find(compression_type);
if (iter == OptionsHelper::compression_type_string_map.end()) {
fprintf(stderr, "%s is not a valid CompressionType\n",
compression_type.c_str());
exit(1);
}
compression_types.emplace_back(*iter);
compression_types.emplace_back(iter->second);
}
} else if (strncmp(argv[i], "--compression_manager=", 22) == 0) {
std::string compression_manager_str = argv[i] + 22;
ConfigOptions config_options;
config_options.ignore_unsupported_options = false;
Status s = CompressionManager::CreateFromString(
config_options, compression_manager_str, &compression_manager);
if (!s.ok()) {
fprintf(stderr, "Failed to create compression manager: %s\n",
s.ToString().c_str());
exit(1);
}
if (compression_manager == nullptr) {
fprintf(stderr, "No compression manager created: %s\n",
compression_manager_str.c_str());
exit(1);
}
options.compression_manager = compression_manager;
printf("Using compression manager: %s\n",
compression_manager->GetId().c_str());
} else if (strncmp(argv[i], "--parse_internal_key=", 21) == 0) {
std::string in_key(argv[i] + 21);
try {
@@ -450,9 +462,12 @@ int SSTDumpTool::Run(int argc, char const* const* argv, Options options) {
}
if (command == "recompress") {
// TODO: consider getting supported compressions from the compression
// manager
st = dumper.ShowAllCompressionSizes(
set_block_size ? block_size : 16384,
compression_types.empty() ? kCompressions : compression_types,
compression_types.empty() ? GetSupportedCompressions()
: compression_types,
compress_level_from, compress_level_to, compression_max_dict_bytes,
compression_zstd_max_train_bytes, compression_max_dict_buffer_bytes,
!compression_use_zstd_finalize_dict);
@@ -1 +0,0 @@
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
@@ -1 +0,0 @@
A new API GetFileSize is added to FSRandomAccessFile interface class. It uses fstat vs stat on the posix implementation which is more efficient. Caller could use it to get file size faster. This function might be required in the future for FileSystem implementation outside of the RocksDB code base.
@@ -1 +0,0 @@
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
@@ -1 +0,0 @@
* Fix a bug in BackupEngine that can crash backup due to a null FSWritableFile passed to WritableFileWriter.
@@ -1 +0,0 @@
Fix DB::NewMultiScan iterator to respect the scan upper bound specified in ScanOptions
@@ -1 +0,0 @@
* Optimized MultiScan using BlockBasedTable to coalesce I/Os and prefetch all data blocks.
@@ -1 +0,0 @@
DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
+18
View File
@@ -635,4 +635,22 @@ void DBWithTTLImpl::SetTtl(ColumnFamilyHandle* h, int32_t ttl) {
filter->SetTtl(ttl);
}
Status DBWithTTLImpl::GetTtl(ColumnFamilyHandle* h, int32_t* ttl) {
if (h == nullptr || ttl == nullptr) {
return Status::InvalidArgument(
"column family handle or ttl cannot be null");
}
std::shared_ptr<TtlCompactionFilterFactory> filter;
Options opts;
opts = GetOptions(h);
filter = std::static_pointer_cast<TtlCompactionFilterFactory>(
opts.compaction_filter_factory);
if (!filter) {
return Status::InvalidArgument(
"TTLCompactionFilterFactory is not set for TTLDB");
}
*ttl = filter->GetTtl();
return Status::OK();
}
} // namespace ROCKSDB_NAMESPACE
+3
View File
@@ -100,6 +100,8 @@ class DBWithTTLImpl : public DBWithTTL {
void SetTtl(ColumnFamilyHandle* h, int32_t ttl) override;
Status GetTtl(ColumnFamilyHandle* h, int32_t* ttl) override;
private:
// remember whether the Close completes or not
bool closed_;
@@ -184,6 +186,7 @@ class TtlCompactionFilterFactory : public CompactionFilterFactory {
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
const CompactionFilter::Context& context) override;
void SetTtl(int32_t ttl) { ttl_ = ttl; }
int32_t GetTtl() { return ttl_; }
const char* Name() const override { return kClassName(); }
static const char* kClassName() { return "TtlCompactionFilterFactory"; }
+3
View File
@@ -720,6 +720,9 @@ TEST_F(TtlTest, ChangeTtlOnOpenDb) {
OpenTtl(1); // T=0:Open the db with ttl = 2
SetTtl(3);
int32_t ttl = 0;
ASSERT_OK(db_ttl_->GetTtl(db_ttl_->DefaultColumnFamily(), &ttl));
ASSERT_EQ(ttl, 3);
PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
SleepCompactCheck(2, 0, kSampleSize_, true); // T=2:Set1 should be there
CloseTtl();