Skip to content

Commit 4e7fe99

Browse files
author
Xingbo Wang
committed
Fix range delete file caused MultiScan issue (facebook#14028)
Summary: When there is an ingested SST file that only contains delete range operations, MultiScan may return error "Scan does not intersect with file". This is due to file selection during Prepare uses the file smallest and largest key without considering whether there is any key in the file. This is only a temporary fix. Pull Request resolved: facebook#14028 Test Plan: Unit test Reviewed By: anand1976 Differential Revision: D83986964 Pulled By: xingbowang fbshipit-source-id: e0961ca854e2062c2457be4324817ba073ae785d
1 parent 4b0558b commit 4e7fe99

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

db/version_set.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,11 @@ class LevelIterator final : public InternalIterator {
11871187
iend.Encode(), flevel_->files[i].smallest_key) < 0) {
11881188
continue;
11891189
}
1190+
auto const metadata = flevel_->files[i].file_metadata;
1191+
if (metadata->num_entries == metadata->num_range_deletions) {
1192+
// Skip range deletion only files.
1193+
continue;
1194+
}
11901195
auto& args = GetMultiScanArgForFile(i);
11911196
args.insert(start.value(), end.value(), opt.property_bag);
11921197
}

table/table_test.cc

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8650,6 +8650,121 @@ TEST_F(UserDefinedIndexTest, ReverseMultiScanTest) {
86508650
ASSERT_OK(DestroyDB(dbname, options));
86518651
}
86528652

8653+
TEST_F(UserDefinedIndexTest, RangeDelete) {
8654+
Options options;
8655+
BlockBasedTableOptions table_options;
8656+
options.num_levels = 50;
8657+
options.compaction_style = kCompactionStyleUniversal;
8658+
options.disable_auto_compactions = true;
8659+
std::string dbname = test::PerThreadDBPath("user_defined_index_test");
8660+
std::string ingest_file = dbname + "test.sst";
8661+
8662+
// Set up the user-defined index factory
8663+
auto user_defined_index_factory =
8664+
std::make_shared<TestUserDefinedIndexFactory>();
8665+
table_options.user_defined_index_factory = user_defined_index_factory;
8666+
8667+
// Set up custom flush block policy that flushes every 3 keys
8668+
table_options.flush_block_policy_factory =
8669+
std::make_shared<CustomFlushBlockPolicyFactory>();
8670+
8671+
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
8672+
8673+
auto create_ingestion_data_file = [&](const std::string& filename) {
8674+
std::unique_ptr<SstFileWriter> writer;
8675+
writer.reset(new SstFileWriter(EnvOptions(), options));
8676+
ASSERT_OK(writer->Open(filename));
8677+
// Add 100 keys
8678+
for (int i = 0; i < 100; i++) {
8679+
std::stringstream ss;
8680+
ss << std::setw(2) << std::setfill('0') << i;
8681+
std::string key = "key" + ss.str();
8682+
std::string value = "value" + ss.str();
8683+
ASSERT_OK(writer->Put(key, value));
8684+
}
8685+
ASSERT_OK(writer->Finish());
8686+
writer.reset();
8687+
};
8688+
8689+
// Create first ingestion file with data
8690+
create_ingestion_data_file(ingest_file + "_0");
8691+
8692+
// Create second ingestion file with range delete only that covers the first
8693+
// file to delete all of its keys.
8694+
{
8695+
std::unique_ptr<SstFileWriter> writer;
8696+
writer.reset(new SstFileWriter(EnvOptions(), options));
8697+
ASSERT_OK(writer->Open(ingest_file + "_1"));
8698+
ASSERT_OK(writer->DeleteRange("key", "keyz"));
8699+
ASSERT_OK(writer->Finish());
8700+
writer.reset();
8701+
}
8702+
8703+
// Create the second ingestion file with data
8704+
create_ingestion_data_file(ingest_file + "_2");
8705+
8706+
std::unique_ptr<DB> db;
8707+
options.create_if_missing = true;
8708+
Status s = DB::Open(options, dbname, &db);
8709+
ASSERT_OK(s);
8710+
ASSERT_TRUE(db != nullptr);
8711+
ColumnFamilyHandle* cfh = nullptr;
8712+
ASSERT_OK(db->CreateColumnFamily(options, "new_cf", &cfh));
8713+
8714+
IngestExternalFileOptions ifo;
8715+
// ingest first data file key00~key99
8716+
s = db->IngestExternalFile(cfh, {ingest_file + "_0"}, ifo);
8717+
ASSERT_OK(s);
8718+
// ingest delete range (key-keyz) and new data file (key00-key99) together
8719+
s = db->IngestExternalFile(cfh, {ingest_file + "_1", ingest_file + "_2"},
8720+
ifo);
8721+
ASSERT_OK(s);
8722+
8723+
ReadOptions ro;
8724+
std::unique_ptr<Iterator> iter(db->NewIterator(ro, cfh));
8725+
ASSERT_NE(iter, nullptr);
8726+
ASSERT_OK(iter->status());
8727+
8728+
std::vector<Slice> range = {
8729+
Slice("key10"),
8730+
Slice("key25"),
8731+
Slice("key80"),
8732+
Slice("key95"),
8733+
};
8734+
8735+
Slice ub("");
8736+
ro.iterate_upper_bound = &ub;
8737+
iter.reset(db->NewIterator(ro, cfh));
8738+
ASSERT_NE(iter, nullptr);
8739+
MultiScanArgs scan_opts(options.comparator);
8740+
std::unordered_map<std::string, std::string> property_bag;
8741+
property_bag["count"] = std::to_string(9);
8742+
8743+
std::vector<std::vector<char>> decoded_ranges;
8744+
for (size_t i = 0; i < range.size() / 2; i++) {
8745+
scan_opts.insert(range[i * 2], range[i * 2 + 1],
8746+
std::optional(property_bag));
8747+
}
8748+
iter->Prepare(scan_opts);
8749+
8750+
for (size_t i = 0; i < range.size() / 2; i++) {
8751+
// Update upper bound before each seek
8752+
ub = range[2 * i + 1];
8753+
auto key_count = 0;
8754+
for (iter->Seek(range[i * 2]); iter->Valid(); iter->Next()) {
8755+
key_count++;
8756+
}
8757+
ASSERT_OK(iter->status());
8758+
ASSERT_EQ(key_count, 15);
8759+
}
8760+
8761+
iter.reset();
8762+
8763+
ASSERT_OK(db->DestroyColumnFamilyHandle(cfh));
8764+
ASSERT_OK(db->Close());
8765+
ASSERT_OK(DestroyDB(dbname, options));
8766+
}
8767+
86538768
} // namespace ROCKSDB_NAMESPACE
86548769

86558770
int main(int argc, char** argv) {

0 commit comments

Comments
 (0)