Compare commits

...

7 Commits

Author SHA1 Message Date
anand76 461d7f4464 Bump patch version to fix a mistake in release tagging 2025-03-29 14:42:01 -07:00
anand76 04b53a2608 Update version to 9.11.1 2025-02-19 20:03:01 -08:00
Sarang Masti a702d6c372 Bugfix: Ensure statuses are initialized with OK() in SSTFileReader::MultiGet (#13411)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13411

We should intialize statuses with OK rather than IOError to correctly handle cases
like NotFound due to bloom filter. In case of IOError status would be updated
appropriately by the reader

Reviewed By: anand1976

Differential Revision: D69886976

fbshipit-source-id: 92b130168f23633224ff4153bfe46a7d86482b90
2025-02-19 20:01:41 -08:00
anand76 5e67d89702 Initial version of an external table reader interface (#13401)
Summary:
This PR introduces an interface to plug in an external table file reader into RocksDB. The external table reader may support custom file formats that might work better for a specific use case compared to RocksDB native formats. This initial version allows the external table file to be loaded and queried using an `SstFileReader`. In the near future, we will allow it to be used with a limited RocksDB instance that allows bulkload but not live writes.

The model of a DB using an external table reader is a read only database allowing bulkload and atomic replace in the bottommost level only. Live writes, if supported in the future, are expected to use block based table files in higher levels. Tombstones, merge operands, and non-zero sequence numbers are expected to be present only in non-bottommost levels. External table files are assumed to have only Puts, and all keys implicitly have sequence number 0.

TODO (in future PRs) -
1. Add support for external file ingestion, with safety mechanisms to prevent accidental writes
2. Add support for atomic column family replace
3. Allow custom table file extensions
4. Add a TableBuilder interface for use with `SstFileWriter`

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

Reviewed By: pdillinger

Differential Revision: D69689351

Pulled By: anand1976

fbshipit-source-id: c5d5b92d56fd4d0fc43a77c4ceb0463d4f479bda
2025-02-19 20:01:22 -08:00
Sarang Masti 746d3b4470 MultiGet support in SstReader (#13403)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/13403

Add MultiGet support in SstReader. Today we only have iteration support and this change
also adds MultiGet support to SstFileReader if some application wants to use it.

Reviewed By: anand1976

Differential Revision: D69514499

fbshipit-source-id: 20e85a4bd13a3a9f45dacb223c1a4541fb87f561
2025-02-19 20:01:13 -08:00
anand76 15a55329dd Add a missing API deprecation to HISTORY.md 2025-01-17 18:06:52 -08:00
anand76 76c3385a8f Update HISTORY for 9.11 2025-01-17 15:59:08 -08:00
23 changed files with 752 additions and 11 deletions
+1
View File
@@ -214,6 +214,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
"table/cuckoo/cuckoo_table_builder.cc",
"table/cuckoo/cuckoo_table_factory.cc",
"table/cuckoo/cuckoo_table_reader.cc",
"table/external_table_reader.cc",
"table/format.cc",
"table/get_context.cc",
"table/iterator.cc",
+1
View File
@@ -835,6 +835,7 @@ set(SOURCES
table/cuckoo/cuckoo_table_builder.cc
table/cuckoo/cuckoo_table_factory.cc
table/cuckoo/cuckoo_table_reader.cc
table/external_table_reader.cc
table/format.cc
table/get_context.cc
table/iterator.cc
+24
View File
@@ -1,6 +1,30 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 9.11.1 (02/19/2025)
### New Features
* Added the ability to plug-in a custom table reader implementation. See include/rocksdb/external_table_reader.h for more details.
## 9.11.0 (01/17/2025)
### New Features
* Introduce CancelAwaitingJobs() in CompactionService interface which will allow users to implement cancellation of running remote compactions from the primary instance
* Experimental feature: RocksDB now supports defining secondary indices, which are automatically maintained by the storage engine. Secondary indices provide a new customization point: applications can provide their own by implementing the new `SecondaryIndex` interface. See the `SecondaryIndex` API comments for more details. Note: this feature is currently only available in conjunction with write-committed pessimistic transactions, and `Merge` is not yet supported.
* Provide a new option `track_and_verify_wals` to track and verify various information about WAL during WAL recovery. This is intended to be a better replacement to `track_and_verify_wals_in_manifest`.
### Public API Changes
* Add `io_buffer_size` to BackupEngineOptions to enable optimal configuration of IO size
* Clean up all the references to `random_access_max_buffer_size`, related rules and all the clients wrappers. This option has been officially deprecated in 5.4.0.
* Add `file_ingestion_nanos` and `file_ingestion_blocking_live_writes_nanos` in PerfContext to observe file ingestions
* Offer new DB::Open and variants that use `std::unique_ptr<DB>*` output parameters and deprecate the old versions that use `DB**` output parameters.
* The DB::DeleteFile API is officially deprecated.
### Behavior Changes
* For leveled compaction, manual compaction (CompactRange()) will be more strict about keeping compaction size under `max_compaction_bytes`. This prevents overly large compactions in some cases (#13306).
* Experimental tiering options `preclude_last_level_data_seconds` and `preserve_internal_time_seconds` are now mutable with `SetOptions()`. Some changes to handling of these features along with long-lived snapshots and range deletes made this possible.
### Bug Fixes
* Fix a longstanding major bug with SetOptions() in which setting changes can be quietly reverted.
## 9.10.0 (12/12/2024)
### New Features
* Introduce `TransactionOptions::commit_bypass_memtable` to enable transaction commit to bypass memtable insertions. This can be beneficial for transactions with many operations, as it reduces commit time that is mostly spent on memtable insertion.
+2
View File
@@ -498,6 +498,8 @@ class InternalKey {
// Intended only to be used together with ConvertFromUserKey().
std::string* rep() { return &rep_; }
const std::string* const_rep() const { return &rep_; }
// Assuming that *rep() contains a user key, this method makes internal key
// out of it in-place. This saves a memcpy compared to Set()/SetFrom().
void ConvertFromUserKey(SequenceNumber s, ValueType t) {
+124
View File
@@ -0,0 +1,124 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#pragma once
#include "rocksdb/customizable.h"
#include "rocksdb/iterator.h"
#include "rocksdb/options.h"
#include "rocksdb/status.h"
namespace ROCKSDB_NAMESPACE {
class ExternalTableFactory;
// EXPERIMENTAL
// The interface defined in this file is subject to change at any time without
// warning!!
// This file defines an interface for plugging in an external table reader
// into RocksDB. The external table reader will be used instead of the
// BlockBasedTable to load and query sst files. As of now, creating the
// external table files using RocksDB is not supported, but will be added in
// the near future. The external table files can be created outside and
// RocksDB and ingested into a RocksDB instance using the IngestExternalFIle()
// API.
//
// Initial support is for loading and querying the files using an
// SstFileReader. We will add support for ingestion of an external table
// into a limited RocksDB instance that only supports ingestion and not live
// writes in the near future. It'll be followed by support for replacing the
// column family by ingesting a new set of files. In all cases, the external
// table files will only be allowed in the bottommost level.
//
// The external table reader can support one or both of the following layouts -
// 1. Total order seek - All the keys in the files are in sorted order, and a
// user can seek to the first, last, or any key in between and iterate
// forwards or backwards till the end of the range. To support this mode,
// the implementation needs to use the comparator passed in
// ExternalTableOptions to enforce the key ordering. The prefix_extractor
// in ExternalTableOptions and the ExternalTableReader interfaces can be
// ignored.
// 2. Prefix seek - In this mode, the prefix_extractor is used to extract the
// prefix from a key. All the keys sharing the same prefix are ordered in
// ascending order according to the comparator. However, no specific
// ordering is required across prefixes. Users can scan keys by seeking
// to a specific key inside a prefix, and iterate forwards or backwards
// within the prefix. The prefix_same_as_start flag in ReadOptions will
// be true.
// 3. Both - If supporting both of the above, a user can seek inside a prefix
// and iterate beyond the prefix. The prefix_same_as_start in ReadOptions
// will be false. Additionally, the total_order_seek flag can be set to
// true to seek to the first non-empty prefix (as determined by the key
// order) if the seek prefix is empty.
//
// Many of the options in ReadOptions may not be relevant to the external
// table implementation.
// TODO: Specify which options are relevant
class ExternalTableReader {
public:
virtual ~ExternalTableReader() {}
// Return an Iterator that can be used to scan the table file.
// The read_options can optionally contain the upper bound
// key (exclusive) of the scan in iterate_upper_bound.
virtual Iterator* NewIterator(const ReadOptions& read_options,
const SliceTransform* prefix_extractor) = 0;
// Point lookup the given key and return its value
virtual Status Get(const ReadOptions& read_options, const Slice& key,
const SliceTransform* prefix_extractor,
std::string* value) = 0;
// Point lookup the given vector of keys and return the values, as well
// as status of each individual lookup in statuses.
virtual void MultiGet(const ReadOptions& read_options,
const std::vector<Slice>& keys,
const SliceTransform* prefix_extractor,
std::vector<std::string>* values,
std::vector<Status>* statuses) = 0;
// Return TableProperties for the file. At a minimum, the following
// properties need to be returned -
// comparator_name
// num_entries
// raw_key_size
// raw_value_size
virtual std::shared_ptr<const TableProperties> GetTableProperties() const = 0;
virtual Status VerifyChecksum(const ReadOptions& /*ro*/) {
return Status::NotSupported("VerifyChecksum() not supported");
}
};
struct ExternalTableOptions {
const std::shared_ptr<const SliceTransform>& prefix_extractor;
const Comparator* comparator;
ExternalTableOptions(
const std::shared_ptr<const SliceTransform>& _prefix_extractor,
const Comparator* _comparator)
: prefix_extractor(_prefix_extractor), comparator(_comparator) {}
};
class ExternalTableFactory : public Customizable {
public:
~ExternalTableFactory() override {}
const char* Name() const override { return "ExternalTableFactory"; }
virtual Status NewTableReader(
const ReadOptions& read_options, const std::string& file_path,
const ExternalTableOptions& table_options,
std::unique_ptr<ExternalTableReader>* table_reader) = 0;
};
// Allocate a TableFactory that wraps around an ExternalTableFactory. Use this
// to allocate and set in ColumnFamilyOptions::table_factory.
std::shared_ptr<TableFactory> NewExternalTableFactory(
std::shared_ptr<ExternalTableFactory> inner_factory);
} // namespace ROCKSDB_NAMESPACE
+10
View File
@@ -1953,6 +1953,16 @@ struct ReadOptions {
// EXPERIMENTAL
Env::IOActivity io_activity = Env::IOActivity::kUnknown;
// EXPERIMENTAL
// An optional weight of values to be returned by a scan. Once the
// weight is reached or exceeded the scan is terminated (i.e Next()
// invalidates the iterator). In the case of a DB with one of the built-in
// table formats, such as BlockBasedTable, the weight is simply the number
// of key-value pairs. In the case of an ExternalTableReader, the weight is
// passed through to the table reader and the interpretation is upto the
// reader implementation.
uint64_t weight = 0;
// *** END options for RocksDB internal use only ***
ReadOptions() {}
+5
View File
@@ -30,6 +30,11 @@ class SstFileReader {
// If "snapshot" is nullptr, the iterator returns only the latest keys.
Iterator* NewIterator(const ReadOptions& options);
// MultiGet to fetch a set of keys from the SST
std::vector<Status> MultiGet(const ReadOptions& options,
const std::vector<Slice>& keys,
std::vector<std::string>* values);
// Returns a new iterator over the table contents as a raw table iterator,
// a.k.a a `TableIterator`that iterates all point data entries in the table
// including logically invisible entries like delete entries.
+1 -1
View File
@@ -13,7 +13,7 @@
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 9
#define ROCKSDB_MINOR 11
#define ROCKSDB_PATCH 0
#define ROCKSDB_PATCH 2
// 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
+1
View File
@@ -205,6 +205,7 @@ LIB_SOURCES = \
table/cuckoo/cuckoo_table_builder.cc \
table/cuckoo/cuckoo_table_factory.cc \
table/cuckoo/cuckoo_table_reader.cc \
table/external_table_reader.cc \
table/format.cc \
table/get_context.cc \
table/iterator.cc \
+220
View File
@@ -0,0 +1,220 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
#include "rocksdb/external_table_reader.h"
#include "rocksdb/table.h"
#include "table/internal_iterator.h"
#include "table/table_builder.h"
#include "table/table_reader.h"
namespace ROCKSDB_NAMESPACE {
namespace {
class ExternalTableIterator : public InternalIterator {
public:
explicit ExternalTableIterator(Iterator* iterator) : iterator_(iterator) {}
// No copying allowed
ExternalTableIterator(const ExternalTableIterator&) = delete;
ExternalTableIterator& operator=(const ExternalTableIterator&) = delete;
~ExternalTableIterator() override {}
bool Valid() const override { return iterator_ && iterator_->Valid(); }
void SeekToFirst() override {
status_ = Status::OK();
if (iterator_) {
iterator_->SeekToFirst();
UpdateKey();
}
}
void SeekToLast() override {
status_ = Status::OK();
if (iterator_) {
iterator_->SeekToLast();
UpdateKey();
}
}
void Seek(const Slice& target) override {
status_ = Status::OK();
if (iterator_) {
ParsedInternalKey pkey;
status_ = ParseInternalKey(target, &pkey, /*log_err_key=*/false);
if (status_.ok()) {
iterator_->Seek(pkey.user_key);
UpdateKey();
}
}
}
void SeekForPrev(const Slice& target) override {
status_ = Status::OK();
if (iterator_) {
ParsedInternalKey pkey;
status_ = ParseInternalKey(target, &pkey, /*log_err_key=*/false);
if (status_.ok()) {
iterator_->SeekForPrev(pkey.user_key);
UpdateKey();
}
}
}
void Next() override {
if (iterator_) {
iterator_->Next();
UpdateKey();
}
}
void Prev() override {
if (iterator_) {
iterator_->Prev();
UpdateKey();
}
}
Slice key() const override {
if (iterator_) {
return Slice(*key_.const_rep());
}
return Slice();
}
Slice value() const override {
if (iterator_) {
return iterator_->value();
}
return Slice();
}
Status status() const override {
return !status_.ok() ? status_
: (iterator_ ? iterator_->status() : Status::OK());
}
private:
std::unique_ptr<Iterator> iterator_;
InternalKey key_;
Status status_;
void UpdateKey() { key_.Set(iterator_->key(), 0, ValueType::kTypeValue); }
};
class ExternalTableReaderAdapter : public TableReader {
public:
explicit ExternalTableReaderAdapter(
std::unique_ptr<ExternalTableReader> reader)
: reader_(std::move(reader)) {}
~ExternalTableReaderAdapter() override {}
// No copying allowed
ExternalTableReaderAdapter(const ExternalTableReaderAdapter&) = delete;
ExternalTableReaderAdapter& operator=(const ExternalTableReaderAdapter&) =
delete;
InternalIterator* NewIterator(
const ReadOptions& read_options, const SliceTransform* prefix_extractor,
Arena* arena, bool /* skip_filters */, TableReaderCaller /* caller */,
size_t /* compaction_readahead_size */ = 0,
bool /* allow_unprepared_value */ = false) override {
auto iterator = reader_->NewIterator(read_options, prefix_extractor);
if (arena == nullptr) {
return new ExternalTableIterator(iterator);
} else {
auto* mem = arena->AllocateAligned(sizeof(ExternalTableIterator));
return new (mem) ExternalTableIterator(iterator);
}
}
uint64_t ApproximateOffsetOf(const ReadOptions&, const Slice&,
TableReaderCaller) override {
return 0;
}
uint64_t ApproximateSize(const ReadOptions&, const Slice&, const Slice&,
TableReaderCaller) override {
return 0;
}
void SetupForCompaction() override {}
std::shared_ptr<const TableProperties> GetTableProperties() const override {
std::shared_ptr<TableProperties> props =
std::make_shared<TableProperties>(*reader_->GetTableProperties());
props->key_largest_seqno = 0;
return props;
}
size_t ApproximateMemoryUsage() const override { return 0; }
Status Get(const ReadOptions&, const Slice&, GetContext*,
const SliceTransform*, bool = false) override {
return Status::NotSupported(
"Get() not supported on external file iterator");
}
virtual Status VerifyChecksum(const ReadOptions& /*ro*/,
TableReaderCaller /*caller*/) override {
return Status::OK();
}
private:
std::unique_ptr<ExternalTableReader> reader_;
};
class ExternalTableFactoryAdapter : public TableFactory {
public:
explicit ExternalTableFactoryAdapter(
std::shared_ptr<ExternalTableFactory> inner)
: inner_(std::move(inner)) {}
const char* Name() const override { return inner_->Name(); }
using TableFactory::NewTableReader;
Status NewTableReader(
const ReadOptions& ro, const TableReaderOptions& topts,
std::unique_ptr<RandomAccessFileReader>&& file, uint64_t /* file_size */,
std::unique_ptr<TableReader>* table_reader,
bool /* prefetch_index_and_filter_in_cache */) const override {
std::unique_ptr<ExternalTableReader> reader;
ExternalTableOptions ext_topts(topts.prefix_extractor,
topts.ioptions.user_comparator);
auto status =
inner_->NewTableReader(ro, file->file_name(), ext_topts, &reader);
if (!status.ok()) {
return status;
}
table_reader->reset(new ExternalTableReaderAdapter(std::move(reader)));
file.reset();
return Status::OK();
}
TableBuilder* NewTableBuilder(const TableBuilderOptions&,
WritableFileWriter*) const override {
return nullptr;
}
std::unique_ptr<TableFactory> Clone() const override { return nullptr; }
private:
std::shared_ptr<ExternalTableFactory> inner_;
};
} // namespace
std::shared_ptr<TableFactory> NewExternalTableFactory(
std::shared_ptr<ExternalTableFactory> inner_factory) {
std::shared_ptr<TableFactory> res;
res.reset(new ExternalTableFactoryAdapter(std::move(inner_factory)));
return res;
}
} // namespace ROCKSDB_NAMESPACE
+79
View File
@@ -80,6 +80,85 @@ Status SstFileReader::Open(const std::string& file_path) {
return s;
}
std::vector<Status> SstFileReader::MultiGet(const ReadOptions& roptions,
const std::vector<Slice>& keys,
std::vector<std::string>* values) {
const auto num_keys = keys.size();
std::vector<Status> statuses(num_keys, Status::OK());
std::vector<PinnableSlice> pin_values(num_keys);
auto r = rep_.get();
const Comparator* user_comparator =
r->ioptions.internal_comparator.user_comparator();
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
autovector<GetContext, MultiGetContext::MAX_BATCH_SIZE> get_ctx;
autovector<MergeContext, MultiGetContext::MAX_BATCH_SIZE> merge_ctx;
sorted_keys.resize(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
PinnableSlice* val = &pin_values[i];
val->Reset();
merge_ctx.emplace_back();
key_context.emplace_back(nullptr, keys[i], val, nullptr,
nullptr /* timestamp */, &statuses[i]);
get_ctx.emplace_back(user_comparator, r->ioptions.merge_operator.get(),
nullptr, nullptr, GetContext::kNotFound,
*key_context[i].key, val, nullptr, nullptr, nullptr,
&merge_ctx[i], true,
&key_context[i].max_covering_tombstone_seq, nullptr);
key_context[i].get_context = &get_ctx[i];
}
for (size_t i = 0; i < num_keys; ++i) {
sorted_keys[i] = &key_context[i];
}
struct CompareKeyContext {
explicit CompareKeyContext(const Comparator* comp) : comparator(comp) {}
inline bool operator()(const KeyContext* lhs, const KeyContext* rhs) const {
return comparator->CompareWithoutTimestamp(*(lhs->key), false,
*(rhs->key), false) < 0;
}
const Comparator* comparator;
};
std::sort(sorted_keys.begin(), sorted_keys.end(),
CompareKeyContext(user_comparator));
const auto sequence = roptions.snapshot != nullptr
? roptions.snapshot->GetSequenceNumber()
: kMaxSequenceNumber;
MultiGetContext ctx(&sorted_keys, 0, num_keys, sequence, roptions,
r->ioptions.fs.get(), nullptr);
MultiGetRange range = ctx.GetMultiGetRange();
r->table_reader->MultiGet(roptions, &range,
r->moptions.prefix_extractor.get(),
false /* skip filters */);
values->resize(num_keys);
for (size_t i = 0; i < num_keys; ++i) {
if (statuses[i].ok()) {
switch (get_ctx[i].State()) {
case GetContext::kFound:
(*values)[i].assign(pin_values[i].data(), pin_values[i].size());
break;
case GetContext::kNotFound:
case GetContext::kDeleted:
statuses[i] = Status::NotFound();
break;
case GetContext::kMerge:
statuses[i] = Status::MergeInProgress();
break;
case GetContext::kCorrupt:
case GetContext::kUnexpectedBlobIndex:
case GetContext::kMergeOperatorFailed:
statuses[i] = Status::Corruption();
break;
};
}
}
return statuses;
}
Iterator* SstFileReader::NewIterator(const ReadOptions& roptions) {
assert(roptions.io_activity == Env::IOActivity::kUnknown);
auto r = rep_.get();
+88
View File
@@ -768,6 +768,94 @@ TEST_F(SstFileReaderTableIteratorTest, UserDefinedTimestampsEnabled) {
Close();
}
class SstFileReaderTableMultiGetTest : public DBTestBase {
public:
SstFileReaderTableMultiGetTest()
: DBTestBase("sst_file_reader_table_multi_get_test",
/*env_do_fsync=*/false) {}
void VerifyTableEntry(Iterator* iter, const std::string& user_key,
ValueType value_type,
std::optional<std::string> expected_value,
bool backward_iteration = false) {
ASSERT_TRUE(iter->Valid());
ASSERT_TRUE(iter->status().ok());
ParsedInternalKey pikey;
ASSERT_OK(ParseInternalKey(iter->key(), &pikey, /*log_err_key=*/false));
ASSERT_EQ(pikey.user_key, user_key);
ASSERT_EQ(pikey.type, value_type);
if (expected_value.has_value()) {
ASSERT_EQ(iter->value(), expected_value.value());
}
if (!backward_iteration) {
iter->Next();
} else {
iter->Prev();
}
}
};
TEST_F(SstFileReaderTableMultiGetTest, Basic) {
Options options = CurrentOptions();
const Comparator* ucmp = BytewiseComparator();
options.comparator = ucmp;
options.disable_auto_compactions = true;
options.merge_operator = MergeOperators::CreateStringAppendOperator();
BlockBasedTableOptions bbto;
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "val1"));
const Snapshot* snapshot1 = dbfull()->GetSnapshot();
ASSERT_OK(Delete("foo"));
ASSERT_OK(Delete("bar"));
const Snapshot* snapshot2 = dbfull()->GetSnapshot();
ASSERT_OK(Put("bar", "val2"));
ASSERT_OK(Put("baz", "val3"));
ASSERT_OK(Put("aaa", "val4"));
const Snapshot* snapshot3 = dbfull()->GetSnapshot();
ASSERT_OK(Merge("aaa", "val5"));
ASSERT_OK(Flush());
std::vector<LiveFileMetaData> files;
dbfull()->GetLiveFilesMetaData(&files);
ASSERT_TRUE(files.size() == 1);
ASSERT_TRUE(files[0].level == 0);
std::string file_name = files[0].directory + "/" + files[0].relative_filename;
SstFileReader reader(options);
ASSERT_OK(reader.Open(file_name));
ASSERT_OK(reader.VerifyChecksum());
std::vector<Slice> keys;
std::vector<std::string> values;
keys.emplace_back("fo1");
keys.emplace_back("foo");
keys.emplace_back("baz");
keys.emplace_back("bar");
keys.emplace_back("aaa");
auto statuses = reader.MultiGet(ReadOptions(), keys, &values);
ASSERT_TRUE(statuses[0].IsNotFound())
<< "Failed: status=" << statuses[0].ToString() << " val=" << values[0];
ASSERT_TRUE(statuses[1].IsNotFound())
<< "Failed: status=" << statuses[0].ToString() << " val=" << values[1];
ASSERT_TRUE(statuses[2].ok());
ASSERT_TRUE(statuses[3].ok());
ASSERT_EQ("val3", values[2]);
ASSERT_EQ("val2", values[3]);
ASSERT_TRUE(statuses[4].ok());
ASSERT_EQ("val4,val5", values[4]);
dbfull()->ReleaseSnapshot(snapshot1);
dbfull()->ReleaseSnapshot(snapshot2);
dbfull()->ReleaseSnapshot(snapshot3);
Close();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+196
View File
@@ -36,6 +36,7 @@
#include "rocksdb/convenience.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
#include "rocksdb/external_table_reader.h"
#include "rocksdb/file_checksum.h"
#include "rocksdb/file_system.h"
#include "rocksdb/filter_policy.h"
@@ -44,6 +45,7 @@
#include "rocksdb/options.h"
#include "rocksdb/perf_context.h"
#include "rocksdb/slice_transform.h"
#include "rocksdb/sst_file_reader.h"
#include "rocksdb/statistics.h"
#include "rocksdb/table_properties.h"
#include "rocksdb/trace_record.h"
@@ -6522,6 +6524,200 @@ TEST_F(CacheUsageOptionsOverridesTest, SanitizeAndValidateOptions) {
std::string::npos);
Destroy(options);
}
class ExternalTableReaderTest : public DBTestBase {
public:
ExternalTableReaderTest()
: DBTestBase("external_table_reader_test", /*env_do_fsync=*/false) {}
protected:
class DummyExternalTableIterator : public Iterator {
public:
explicit DummyExternalTableIterator(bool empty) : empty_(empty) {}
bool Valid() const override { return empty_ ? !empty_ : valid_; }
void SeekToFirst() override {
valid_ = true;
status_ = Status::OK();
}
void SeekToLast() override {
valid_ = true;
status_ = Status::OK();
}
void Seek(const Slice& target) override {
if (target.compare(key_str) <= 0) {
valid_ = true;
} else {
valid_ = false;
}
status_ = Status::OK();
}
void SeekForPrev(const Slice& /*target*/) override {
valid_ = false;
status_ = Status::NotSupported();
}
void Next() override {
valid_ = false;
// status_ is still ok. valid_ indicates end of scan
}
void Prev() override {
valid_ = false;
status_ = Status::NotSupported();
}
Slice key() const override {
// If valid_ is false or status_ is non-ok, behavior is indeterminate
return Slice(key_str);
}
Status status() const override {
// status_ gets overwritten by next Seek
return status_;
}
Slice value() const override {
// If valid_ is false or status_ is non-ok, behavior is indeterminate
return Slice(value_str);
}
private:
static const std::string key_str;
static const std::string value_str;
bool valid_ = false;
bool empty_;
Status status_ = Status::OK();
};
class DummyExternalTableReader : public ExternalTableReader {
public:
Iterator* NewIterator(const ReadOptions& read_options,
const SliceTransform* /*prefix_extractor*/) override {
return new DummyExternalTableIterator((read_options.weight == 0) ? true
: false);
}
Status Get(const ReadOptions& /*read_options*/, const Slice& key,
const SliceTransform* /*prefix_extractor*/,
std::string* value) override {
if (!key.compare("foo")) {
value->assign("bar");
return Status::OK();
}
return Status::NotFound();
}
void MultiGet(const ReadOptions& read_options,
const std::vector<Slice>& keys,
const SliceTransform* prefix_extractor,
std::vector<std::string>* values,
std::vector<Status>* statuses) override {
values->resize(keys.size());
statuses->resize(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
statuses->at(i) =
Get(read_options, keys[i], prefix_extractor, &values->at(i));
}
}
std::shared_ptr<const TableProperties> GetTableProperties() const override {
std::shared_ptr<TableProperties> props =
std::make_shared<TableProperties>();
props->comparator_name.assign(BytewiseComparator()->Name());
props->num_entries = 1;
props->raw_key_size = 3;
props->raw_value_size = 3;
return props;
}
};
class DummyExternalTableFactory : public ExternalTableFactory {
public:
const char* Name() const override { return "DummyExternalTableFactory"; }
Status NewTableReader(
const ReadOptions& /*read_options*/, const std::string& /*file_path*/,
const ExternalTableOptions& /*topts*/,
std::unique_ptr<ExternalTableReader>* table_reader) override {
table_reader->reset(new DummyExternalTableReader());
return Status::OK();
}
};
};
const std::string ExternalTableReaderTest::DummyExternalTableIterator::key_str =
"foo";
const std::string
ExternalTableReaderTest::DummyExternalTableIterator::value_str = "bar";
TEST_F(ExternalTableReaderTest, BasicTest) {
std::shared_ptr<ExternalTableFactory> factory =
std::make_shared<DummyExternalTableFactory>();
std::unique_ptr<ExternalTableReader> reader;
std::shared_ptr<SliceTransform> prefix_extractor;
ASSERT_OK(factory->NewTableReader(
{}, "", ExternalTableOptions(prefix_extractor, nullptr), &reader));
ReadOptions ro;
ro.weight = 1;
std::unique_ptr<Iterator> iter(reader->NewIterator(ro, nullptr));
ASSERT_NE(iter, nullptr);
iter->Seek("foo");
ASSERT_TRUE(iter->Valid() && iter->status().ok());
ASSERT_EQ(iter->value(), "bar");
iter->Next();
ASSERT_FALSE(iter->Valid());
std::string val;
ASSERT_OK(reader->Get({}, "foo", nullptr, &val));
ASSERT_EQ(val, "bar");
std::vector<std::string> vals;
std::vector<Status> statuses;
reader->MultiGet({}, {"foo", "bar"}, nullptr, &vals, &statuses);
ASSERT_EQ(vals.size(), 2);
ASSERT_EQ(statuses.size(), 2);
ASSERT_EQ(vals[0], "bar");
ASSERT_EQ(statuses[0], Status::OK());
ASSERT_EQ(statuses[1], Status::NotFound());
}
TEST_F(ExternalTableReaderTest, SstReaderTest) {
Options options = GetDefaultOptions();
std::string dbname = test::PerThreadDBPath("external_table_reader_test");
std::string ingest_file = dbname + "test.immutabledb";
dbname += "_db";
std::shared_ptr<ExternalTableFactory> factory =
std::make_shared<DummyExternalTableFactory>();
options.table_factory = NewExternalTableFactory(factory);
// Create a file
ASSERT_OK(WriteStringToFile(options.env, "Hello World", ingest_file,
/*should_sync=*/true));
std::unique_ptr<SstFileReader> reader(new SstFileReader(options));
ASSERT_OK(reader->Open(ingest_file));
ReadOptions ro;
ro.weight = 1;
std::unique_ptr<Iterator> iter(reader->NewIterator(ro));
ASSERT_NE(iter, nullptr);
iter->Seek("foo");
ASSERT_TRUE(iter->Valid() && iter->status().ok());
ASSERT_EQ(iter->value(), "bar");
iter->Next();
ASSERT_FALSE(iter->Valid());
ASSERT_TRUE(iter->status().ok());
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
@@ -1 +0,0 @@
* For leveled compaction, manual compaction (CompactRange()) will be more strict about keeping compaction size under `max_compaction_bytes`. This prevents overly large compactions in some cases (#13306).
@@ -1 +0,0 @@
* Experimental tiering options `preclude_last_level_data_seconds` and `preserve_internal_time_seconds` are now mutable with `SetOptions()`. Some changes to handling of these features along with long-lived snapshots and range deletes made this possible.
@@ -1 +0,0 @@
* Fix a longstanding major bug with SetOptions() in which setting changes can be quietly reverted.
@@ -1 +0,0 @@
Introduce CancelAwaitingJobs() in CompactionService interface which will allow users to implement cancellation of running remote compactions from the primary instance
@@ -1 +0,0 @@
Experimental feature: RocksDB now supports defining secondary indices, which are automatically maintained by the storage engine. Secondary indices provide a new customization point: applications can provide their own by implementing the new `SecondaryIndex` interface. See the `SecondaryIndex` API comments for more details. Note: this feature is currently only available in conjunction with write-committed pessimistic transactions, and `Merge` is not yet supported.
@@ -1 +0,0 @@
Provide a new option `track_and_verify_wals` to track and verify various information about WAL during WAL recovery. This is intended to be a better replacement to `track_and_verify_wals_in_manifest`.
@@ -1 +0,0 @@
Add `io_buffer_size` to BackupEngineOptions to enable optimal configuration of IO size
@@ -1 +0,0 @@
Clean up all the references to `random_access_max_buffer_size`, related rules and all the clients wrappers. This option has been officially deprecated in 5.4.0.
@@ -1 +0,0 @@
Add `file_ingestion_nanos` and `file_ingestion_blocking_live_writes_nanos` in PerfContext to observe file ingestions
@@ -1 +0,0 @@
* Offer new DB::Open and variants that use `std::unique_ptr<DB>*` output parameters and deprecate the old versions that use `DB**` output parameters.