mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 22:55:23 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 461d7f4464 | |||
| 04b53a2608 | |||
| a702d6c372 | |||
| 5e67d89702 | |||
| 746d3b4470 | |||
| 15a55329dd | |||
| 76c3385a8f |
@@ -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",
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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.
|
||||
Reference in New Issue
Block a user