mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4bf18a60b | ||
|
|
a7dc0e231e | ||
|
|
b0bd38001b | ||
|
|
4d1b6a2f9d | ||
|
|
d6abf820d7 | ||
|
|
8265284538 | ||
|
|
5b3897bff0 | ||
|
|
675846d46b | ||
|
|
5265e31b32 | ||
|
|
16ed52ebc4 | ||
|
|
0cd6abb91d | ||
|
|
c007dd1da6 | ||
|
|
0d79386197 | ||
|
|
621dbd54fa | ||
|
|
03cfaf3931 | ||
|
|
1061f74b7d | ||
|
|
6a83adcea8 | ||
|
|
ab42881ce7 | ||
|
|
27c544fb29 | ||
|
|
11a61ca109 | ||
|
|
462388a434 |
No files matched your search
@@ -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
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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])});
|
||||
|
||||
@@ -2400,7 +2400,6 @@ class DataVisibilityTest : public DBBasicTestWithTimestampBase {
|
||||
}
|
||||
}
|
||||
};
|
||||
constexpr int DataVisibilityTest::kTestDataSize;
|
||||
|
||||
// Application specifies timestamp but not snapshot.
|
||||
// reader writer
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+38
-3
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" */;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user