Add functionality to pre-fetch blocks specified by a key range to BlockBasedTable implementation.
Summary: Pre-fetching is a common operation performed by data stores for disk/flash based systems as part of database startup. This is part of task 5197184. Test Plan: Run the newly added unit test Reviewers: rven, igor, sdong Reviewed By: sdong Subscribers: dhruba, leveldb Differential Revision: https://reviews.facebook.net/D33933
This commit is contained in:
parent
c4bd03a97e
commit
f29b33c73b
@ -1240,6 +1240,52 @@ Status BlockBasedTable::Get(
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status BlockBasedTable::Prefetch(const Slice* const begin,
|
||||||
|
const Slice* const end) {
|
||||||
|
auto& comparator = rep_->internal_comparator;
|
||||||
|
// pre-condition
|
||||||
|
if (begin && end && comparator.Compare(*begin, *end) > 0) {
|
||||||
|
return Status::InvalidArgument(*begin, *end);
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockIter iiter;
|
||||||
|
NewIndexIterator(ReadOptions(), &iiter);
|
||||||
|
|
||||||
|
if (!iiter.status().ok()) {
|
||||||
|
// error opening index iterator
|
||||||
|
return iiter.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
// indicates if we are on the last page that need to be pre-fetched
|
||||||
|
bool prefetching_boundary_page = false;
|
||||||
|
|
||||||
|
for (begin ? iiter.Seek(*begin) : iiter.SeekToFirst(); iiter.Valid();
|
||||||
|
iiter.Next()) {
|
||||||
|
Slice block_handle = iiter.value();
|
||||||
|
|
||||||
|
if (end && comparator.Compare(iiter.key(), *end) >= 0) {
|
||||||
|
if (prefetching_boundary_page) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The index entry represents the last key in the data block.
|
||||||
|
// We should load this page into memory as well, but no more
|
||||||
|
prefetching_boundary_page = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the block specified by the block_handle into the block cache
|
||||||
|
BlockIter biter;
|
||||||
|
NewDataBlockIterator(rep_, ReadOptions(), block_handle, &biter);
|
||||||
|
|
||||||
|
if (!biter.status().ok()) {
|
||||||
|
// there was an unexpected error while pre-fetching
|
||||||
|
return biter.status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
|
bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
|
||||||
const Slice& key) {
|
const Slice& key) {
|
||||||
std::unique_ptr<Iterator> iiter(NewIndexIterator(options));
|
std::unique_ptr<Iterator> iiter(NewIndexIterator(options));
|
||||||
|
@ -83,6 +83,11 @@ class BlockBasedTable : public TableReader {
|
|||||||
Status Get(const ReadOptions& readOptions, const Slice& key,
|
Status Get(const ReadOptions& readOptions, const Slice& key,
|
||||||
GetContext* get_context) override;
|
GetContext* get_context) override;
|
||||||
|
|
||||||
|
// Pre-fetch the disk blocks that correspond to the key range specified by
|
||||||
|
// (kbegin, kend). The call will return return error status in the event of
|
||||||
|
// IO or iteration error.
|
||||||
|
Status Prefetch(const Slice* begin, const Slice* end) override;
|
||||||
|
|
||||||
// Given a key, return an approximate byte offset in the file where
|
// Given a key, return an approximate byte offset in the file where
|
||||||
// the data for that key begins (or would begin if the key were
|
// the data for that key begins (or would begin if the key were
|
||||||
// present in the file). The returned value is in terms of file
|
// present in the file). The returned value is in terms of file
|
||||||
|
@ -68,6 +68,18 @@ class TableReader {
|
|||||||
virtual Status Get(const ReadOptions& readOptions, const Slice& key,
|
virtual Status Get(const ReadOptions& readOptions, const Slice& key,
|
||||||
GetContext* get_context) = 0;
|
GetContext* get_context) = 0;
|
||||||
|
|
||||||
|
// Prefetch data corresponding to a give range of keys
|
||||||
|
// Typically this functionality is required for table implementations that
|
||||||
|
// persists the data on a non volatile storage medium like disk/SSD
|
||||||
|
virtual Status Prefetch(const Slice* begin = nullptr,
|
||||||
|
const Slice* end = nullptr) {
|
||||||
|
(void) begin;
|
||||||
|
(void) end;
|
||||||
|
// Default implementation is NOOP.
|
||||||
|
// The child class should implement functionality when applicable
|
||||||
|
return Status::OK();
|
||||||
|
}
|
||||||
|
|
||||||
// convert db file to a human readable form
|
// convert db file to a human readable form
|
||||||
virtual Status DumpTable(WritableFile* out_file) {
|
virtual Status DumpTable(WritableFile* out_file) {
|
||||||
return Status::NotSupported("DumpTable() not supported");
|
return Status::NotSupported("DumpTable() not supported");
|
||||||
|
@ -47,6 +47,9 @@
|
|||||||
#include "util/testutil.h"
|
#include "util/testutil.h"
|
||||||
#include "util/scoped_arena_iterator.h"
|
#include "util/scoped_arena_iterator.h"
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
namespace rocksdb {
|
namespace rocksdb {
|
||||||
|
|
||||||
extern const uint64_t kLegacyBlockBasedTableMagicNumber;
|
extern const uint64_t kLegacyBlockBasedTableMagicNumber;
|
||||||
@ -1125,6 +1128,131 @@ TEST(BlockBasedTableTest, FilterPolicyNameProperties) {
|
|||||||
ASSERT_EQ("rocksdb.BuiltinBloomFilter", props.filter_policy_name);
|
ASSERT_EQ("rocksdb.BuiltinBloomFilter", props.filter_policy_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// BlockBasedTableTest::PrefetchTest
|
||||||
|
//
|
||||||
|
void AssertKeysInCache(BlockBasedTable* table_reader,
|
||||||
|
const vector<string>& keys_in_cache,
|
||||||
|
const vector<string>& keys_not_in_cache) {
|
||||||
|
for (auto key : keys_in_cache) {
|
||||||
|
ASSERT_TRUE(table_reader->TEST_KeyInCache(ReadOptions(), key));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto key : keys_not_in_cache) {
|
||||||
|
ASSERT_TRUE(!table_reader->TEST_KeyInCache(ReadOptions(), key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrefetchRange(TableConstructor* c, Options* opt,
|
||||||
|
BlockBasedTableOptions* table_options,
|
||||||
|
const vector<std::string>& keys,
|
||||||
|
const char* key_begin, const char* key_end,
|
||||||
|
const vector<string>& keys_in_cache,
|
||||||
|
const vector<string>& keys_not_in_cache,
|
||||||
|
const Status expected_status = Status::OK()) {
|
||||||
|
// reset the cache and reopen the table
|
||||||
|
table_options->block_cache = NewLRUCache(16 * 1024 * 1024);
|
||||||
|
opt->table_factory.reset(NewBlockBasedTableFactory(*table_options));
|
||||||
|
const ImmutableCFOptions ioptions2(*opt);
|
||||||
|
ASSERT_OK(c->Reopen(ioptions2));
|
||||||
|
|
||||||
|
// prefetch
|
||||||
|
auto* table_reader = dynamic_cast<BlockBasedTable*>(c->GetTableReader());
|
||||||
|
// empty string replacement is a trick so we don't crash the test
|
||||||
|
Slice begin(key_begin ? key_begin : "");
|
||||||
|
Slice end(key_end ? key_end : "");
|
||||||
|
Status s = table_reader->Prefetch(key_begin ? &begin : nullptr,
|
||||||
|
key_end ? &end : nullptr);
|
||||||
|
ASSERT_TRUE(s.code() == expected_status.code());
|
||||||
|
|
||||||
|
// assert our expectation in cache warmup
|
||||||
|
AssertKeysInCache(table_reader, keys_in_cache, keys_not_in_cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST(BlockBasedTableTest, PrefetchTest) {
|
||||||
|
// The purpose of this test is to test the prefetching operation built into
|
||||||
|
// BlockBasedTable.
|
||||||
|
Options opt;
|
||||||
|
unique_ptr<InternalKeyComparator> ikc;
|
||||||
|
ikc.reset(new test::PlainInternalKeyComparator(opt.comparator));
|
||||||
|
opt.compression = kNoCompression;
|
||||||
|
BlockBasedTableOptions table_options;
|
||||||
|
table_options.block_size = 1024;
|
||||||
|
// big enough so we don't ever lose cached values.
|
||||||
|
table_options.block_cache = NewLRUCache(16 * 1024 * 1024);
|
||||||
|
opt.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||||
|
|
||||||
|
TableConstructor c(BytewiseComparator());
|
||||||
|
c.Add("k01", "hello");
|
||||||
|
c.Add("k02", "hello2");
|
||||||
|
c.Add("k03", std::string(10000, 'x'));
|
||||||
|
c.Add("k04", std::string(200000, 'x'));
|
||||||
|
c.Add("k05", std::string(300000, 'x'));
|
||||||
|
c.Add("k06", "hello3");
|
||||||
|
c.Add("k07", std::string(100000, 'x'));
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
KVMap kvmap;
|
||||||
|
const ImmutableCFOptions ioptions(opt);
|
||||||
|
c.Finish(opt, ioptions, table_options, *ikc, &keys, &kvmap);
|
||||||
|
|
||||||
|
// We get the following data spread :
|
||||||
|
//
|
||||||
|
// Data block Index
|
||||||
|
// ========================
|
||||||
|
// [ k01 k02 k03 ] k03
|
||||||
|
// [ k04 ] k04
|
||||||
|
// [ k05 ] k05
|
||||||
|
// [ k06 k07 ] k07
|
||||||
|
|
||||||
|
|
||||||
|
// Simple
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
/*key_range=*/ "k01", "k05",
|
||||||
|
/*keys_in_cache=*/ {"k01", "k02", "k03", "k04", "k05"},
|
||||||
|
/*keys_not_in_cache=*/ {"k06", "k07"});
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k01", "k01",
|
||||||
|
{"k01", "k02", "k03"},
|
||||||
|
{"k04", "k05", "k06", "k07"});
|
||||||
|
// odd
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"a", "z",
|
||||||
|
{"k01", "k02", "k03", "k04", "k05", "k06", "k07"},
|
||||||
|
{});
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k00", "k00",
|
||||||
|
{"k01", "k02", "k03"},
|
||||||
|
{"k04", "k05", "k06", "k07"});
|
||||||
|
// Edge cases
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k00", "k06",
|
||||||
|
{"k01", "k02", "k03", "k04", "k05", "k06", "k07"},
|
||||||
|
{});
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k00", "zzz",
|
||||||
|
{"k01", "k02", "k03", "k04", "k05", "k06", "k07"},
|
||||||
|
{});
|
||||||
|
// null keys
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
nullptr, nullptr,
|
||||||
|
{"k01", "k02", "k03", "k04", "k05", "k06", "k07"},
|
||||||
|
{});
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k04", nullptr,
|
||||||
|
{"k04", "k05", "k06", "k07"},
|
||||||
|
{"k01", "k02", "k03"});
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
nullptr, "k05",
|
||||||
|
{"k01", "k02", "k03", "k04", "k05"},
|
||||||
|
{"k06", "k07"});
|
||||||
|
// invalid
|
||||||
|
PrefetchRange(&c, &opt, &table_options, keys,
|
||||||
|
"k06", "k00", {}, {},
|
||||||
|
Status::InvalidArgument(Slice("k06 "), Slice("k07")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST(BlockBasedTableTest, TotalOrderSeekOnHashIndex) {
|
TEST(BlockBasedTableTest, TotalOrderSeekOnHashIndex) {
|
||||||
BlockBasedTableOptions table_options;
|
BlockBasedTableOptions table_options;
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
Loading…
Reference in New Issue
Block a user