Summary: Contains the following changes: - Implementation of cuckoo_table_factory - Adding cuckoo table into AdaptiveTableFactory - Adding cuckoo_table_db_test, similar to lines of plain_table_db_test - Minor fixes to Reader: When a key is found in the table, return the key found instead of the search key. - Minor fixes to Builder: Add table properties that are required by Version::UpdateTemporaryStats() during Get operation. Don't define curr_node as a reference variable as the memory locations may get reassigned during tree.push_back operation, leading to invalid memory access. Test Plan: cuckoo_table_reader_test --enable_perf cuckoo_table_builder_test cuckoo_table_db_test make check all make valgrind_check make asan_check Reviewers: sdong, igor, yhchiang, ljin Reviewed By: ljin Subscribers: leveldb Differential Revision: https://reviews.facebook.net/D21219main
parent
37c6740c38
commit
9674c11d01
@ -0,0 +1,291 @@ |
|||||||
|
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include "db/db_impl.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "table/meta_blocks.h" |
||||||
|
#include "table/cuckoo_table_factory.h" |
||||||
|
#include "table/cuckoo_table_reader.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class CuckooTableDBTest { |
||||||
|
private: |
||||||
|
std::string dbname_; |
||||||
|
Env* env_; |
||||||
|
DB* db_; |
||||||
|
|
||||||
|
public: |
||||||
|
CuckooTableDBTest() : env_(Env::Default()) { |
||||||
|
dbname_ = test::TmpDir() + "/cuckoo_table_db_test"; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, Options())); |
||||||
|
db_ = nullptr; |
||||||
|
Reopen(); |
||||||
|
} |
||||||
|
|
||||||
|
~CuckooTableDBTest() { |
||||||
|
delete db_; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, Options())); |
||||||
|
} |
||||||
|
|
||||||
|
Options CurrentOptions() { |
||||||
|
Options options; |
||||||
|
options.table_factory.reset(NewCuckooTableFactory()); |
||||||
|
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true)); |
||||||
|
options.allow_mmap_reads = true; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.max_mem_compaction_level = 0; |
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
DBImpl* dbfull() { |
||||||
|
return reinterpret_cast<DBImpl*>(db_); |
||||||
|
} |
||||||
|
|
||||||
|
// The following util methods are copied from plain_table_db_test.
|
||||||
|
void Reopen(Options* options = nullptr) { |
||||||
|
delete db_; |
||||||
|
db_ = nullptr; |
||||||
|
Options opts; |
||||||
|
if (options != nullptr) { |
||||||
|
opts = *options; |
||||||
|
} else { |
||||||
|
opts = CurrentOptions(); |
||||||
|
opts.create_if_missing = true; |
||||||
|
} |
||||||
|
ASSERT_OK(DB::Open(opts, dbname_, &db_)); |
||||||
|
} |
||||||
|
|
||||||
|
Status Put(const Slice& k, const Slice& v) { |
||||||
|
return db_->Put(WriteOptions(), k, v); |
||||||
|
} |
||||||
|
|
||||||
|
Status Delete(const std::string& k) { |
||||||
|
return db_->Delete(WriteOptions(), k); |
||||||
|
} |
||||||
|
|
||||||
|
std::string Get(const std::string& k) { |
||||||
|
ReadOptions options; |
||||||
|
std::string result; |
||||||
|
Status s = db_->Get(options, k, &result); |
||||||
|
if (s.IsNotFound()) { |
||||||
|
result = "NOT_FOUND"; |
||||||
|
} else if (!s.ok()) { |
||||||
|
result = s.ToString(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
int NumTableFilesAtLevel(int level) { |
||||||
|
std::string property; |
||||||
|
ASSERT_TRUE( |
||||||
|
db_->GetProperty("rocksdb.num-files-at-level" + NumberToString(level), |
||||||
|
&property)); |
||||||
|
return atoi(property.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// Return spread of files per level
|
||||||
|
std::string FilesPerLevel() { |
||||||
|
std::string result; |
||||||
|
int last_non_zero_offset = 0; |
||||||
|
for (int level = 0; level < db_->NumberLevels(); level++) { |
||||||
|
int f = NumTableFilesAtLevel(level); |
||||||
|
char buf[100]; |
||||||
|
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); |
||||||
|
result += buf; |
||||||
|
if (f > 0) { |
||||||
|
last_non_zero_offset = result.size(); |
||||||
|
} |
||||||
|
} |
||||||
|
result.resize(last_non_zero_offset); |
||||||
|
return result; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(CuckooTableDBTest, Flush) { |
||||||
|
// Try with empty DB first.
|
||||||
|
ASSERT_TRUE(dbfull() != nullptr); |
||||||
|
ASSERT_EQ("NOT_FOUND", Get("key2")); |
||||||
|
|
||||||
|
// Add some values to db.
|
||||||
|
Options options = CurrentOptions(); |
||||||
|
Reopen(&options); |
||||||
|
|
||||||
|
ASSERT_OK(Put("key1", "v1")); |
||||||
|
ASSERT_OK(Put("key2", "v2")); |
||||||
|
ASSERT_OK(Put("key3", "v3")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
TablePropertiesCollection ptc; |
||||||
|
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc); |
||||||
|
ASSERT_EQ(1U, ptc.size()); |
||||||
|
ASSERT_EQ(3, ptc.begin()->second->num_entries); |
||||||
|
ASSERT_EQ("1", FilesPerLevel()); |
||||||
|
|
||||||
|
ASSERT_EQ("v1", Get("key1")); |
||||||
|
ASSERT_EQ("v2", Get("key2")); |
||||||
|
ASSERT_EQ("v3", Get("key3")); |
||||||
|
ASSERT_EQ("NOT_FOUND", Get("key4")); |
||||||
|
ASSERT_EQ("Invalid argument: Length of key is invalid.", Get("somelongkey")); |
||||||
|
ASSERT_EQ("Invalid argument: Length of key is invalid.", Get("s")); |
||||||
|
|
||||||
|
// Now add more keys and flush.
|
||||||
|
ASSERT_OK(Put("key4", "v4")); |
||||||
|
ASSERT_OK(Put("key5", "v5")); |
||||||
|
ASSERT_OK(Put("key6", "v6")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc); |
||||||
|
ASSERT_EQ(2U, ptc.size()); |
||||||
|
auto row = ptc.begin(); |
||||||
|
ASSERT_EQ(3, row->second->num_entries); |
||||||
|
ASSERT_EQ(3, (++row)->second->num_entries); |
||||||
|
ASSERT_EQ("2", FilesPerLevel()); |
||||||
|
ASSERT_EQ("v1", Get("key1")); |
||||||
|
ASSERT_EQ("v2", Get("key2")); |
||||||
|
ASSERT_EQ("v3", Get("key3")); |
||||||
|
ASSERT_EQ("v4", Get("key4")); |
||||||
|
ASSERT_EQ("v5", Get("key5")); |
||||||
|
ASSERT_EQ("v6", Get("key6")); |
||||||
|
|
||||||
|
ASSERT_OK(Delete("key6")); |
||||||
|
ASSERT_OK(Delete("key5")); |
||||||
|
ASSERT_OK(Delete("key4")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc); |
||||||
|
ASSERT_EQ(3U, ptc.size()); |
||||||
|
row = ptc.begin(); |
||||||
|
ASSERT_EQ(3, row->second->num_entries); |
||||||
|
ASSERT_EQ(3, (++row)->second->num_entries); |
||||||
|
ASSERT_EQ(3, (++row)->second->num_entries); |
||||||
|
ASSERT_EQ("3", FilesPerLevel()); |
||||||
|
ASSERT_EQ("v1", Get("key1")); |
||||||
|
ASSERT_EQ("v2", Get("key2")); |
||||||
|
ASSERT_EQ("v3", Get("key3")); |
||||||
|
ASSERT_EQ("NOT_FOUND", Get("key4")); |
||||||
|
ASSERT_EQ("NOT_FOUND", Get("key5")); |
||||||
|
ASSERT_EQ("NOT_FOUND", Get("key6")); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CuckooTableDBTest, FlushWithDuplicateKeys) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
Reopen(&options); |
||||||
|
ASSERT_OK(Put("key1", "v1")); |
||||||
|
ASSERT_OK(Put("key2", "v2")); |
||||||
|
ASSERT_OK(Put("key1", "v3")); // Duplicate
|
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
TablePropertiesCollection ptc; |
||||||
|
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc); |
||||||
|
ASSERT_EQ(1U, ptc.size()); |
||||||
|
ASSERT_EQ(2, ptc.begin()->second->num_entries); |
||||||
|
ASSERT_EQ("1", FilesPerLevel()); |
||||||
|
ASSERT_EQ("v3", Get("key1")); |
||||||
|
ASSERT_EQ("v2", Get("key2")); |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
static std::string Key(int i) { |
||||||
|
char buf[100]; |
||||||
|
snprintf(buf, sizeof(buf), "key_______%06d", i); |
||||||
|
return std::string(buf); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CuckooTableDBTest, CompactionTrigger) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.write_buffer_size = 100 << 10; // 100KB
|
||||||
|
options.level0_file_num_compaction_trigger = 2; |
||||||
|
Reopen(&options); |
||||||
|
|
||||||
|
// Write 11 values, each 10016 B
|
||||||
|
for (int idx = 0; idx < 11; ++idx) { |
||||||
|
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx))); |
||||||
|
} |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
ASSERT_EQ("1", FilesPerLevel()); |
||||||
|
|
||||||
|
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||||
|
for (int idx = 11; idx < 22; ++idx) { |
||||||
|
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx))); |
||||||
|
} |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr); |
||||||
|
|
||||||
|
ASSERT_EQ("0,2", FilesPerLevel()); |
||||||
|
for (int idx = 0; idx < 22; ++idx) { |
||||||
|
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) { |
||||||
|
// Insert same key twice so that they go to different SST files. Then wait for
|
||||||
|
// compaction and check if the latest value is stored and old value removed.
|
||||||
|
Options options = CurrentOptions(); |
||||||
|
options.write_buffer_size = 100 << 10; // 100KB
|
||||||
|
options.level0_file_num_compaction_trigger = 2; |
||||||
|
Reopen(&options); |
||||||
|
|
||||||
|
// Write 11 values, each 10016 B
|
||||||
|
for (int idx = 0; idx < 11; ++idx) { |
||||||
|
ASSERT_OK(Put(Key(idx), std::string(10000, 'a'))); |
||||||
|
} |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
ASSERT_EQ("1", FilesPerLevel()); |
||||||
|
|
||||||
|
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||||
|
for (int idx = 0; idx < 11; ++idx) { |
||||||
|
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx))); |
||||||
|
} |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
dbfull()->TEST_CompactRange(0, nullptr, nullptr); |
||||||
|
|
||||||
|
ASSERT_EQ("0,1", FilesPerLevel()); |
||||||
|
for (int idx = 0; idx < 11; ++idx) { |
||||||
|
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CuckooTableDBTest, AdaptiveTable) { |
||||||
|
Options options = CurrentOptions(); |
||||||
|
|
||||||
|
// Write some keys using cuckoo table.
|
||||||
|
options.table_factory.reset(NewCuckooTableFactory()); |
||||||
|
Reopen(&options); |
||||||
|
|
||||||
|
ASSERT_OK(Put("key1", "v1")); |
||||||
|
ASSERT_OK(Put("key2", "v2")); |
||||||
|
ASSERT_OK(Put("key3", "v3")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
// Write some keys using plain table.
|
||||||
|
options.create_if_missing = false; |
||||||
|
options.table_factory.reset(NewPlainTableFactory()); |
||||||
|
Reopen(&options); |
||||||
|
ASSERT_OK(Put("key4", "v4")); |
||||||
|
ASSERT_OK(Put("key1", "v5")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
// Write some keys using block based table.
|
||||||
|
std::shared_ptr<TableFactory> block_based_factory( |
||||||
|
NewBlockBasedTableFactory()); |
||||||
|
options.table_factory.reset(NewAdaptiveTableFactory(block_based_factory)); |
||||||
|
Reopen(&options); |
||||||
|
ASSERT_OK(Put("key5", "v6")); |
||||||
|
ASSERT_OK(Put("key2", "v7")); |
||||||
|
dbfull()->TEST_FlushMemTable(); |
||||||
|
|
||||||
|
ASSERT_EQ("v5", Get("key1")); |
||||||
|
ASSERT_EQ("v7", Get("key2")); |
||||||
|
ASSERT_EQ("v3", Get("key3")); |
||||||
|
ASSERT_EQ("v4", Get("key4")); |
||||||
|
ASSERT_EQ("v6", Get("key5")); |
||||||
|
} |
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
@ -0,0 +1,60 @@ |
|||||||
|
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
#include "table/cuckoo_table_factory.h" |
||||||
|
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "table/cuckoo_table_builder.h" |
||||||
|
#include "table/cuckoo_table_reader.h" |
||||||
|
#include "util/murmurhash.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
extern const uint32_t kMaxNumHashTable = 64; |
||||||
|
|
||||||
|
extern uint64_t GetSliceMurmurHash(const Slice& s, uint32_t index, |
||||||
|
uint64_t max_num_buckets) { |
||||||
|
static constexpr uint32_t seeds[kMaxNumHashTable] = { |
||||||
|
816922183, 506425713, 949485004, 22513986, 421427259, 500437285, |
||||||
|
888981693, 847587269, 511007211, 722295391, 934013645, 566947683, |
||||||
|
193618736, 428277388, 770956674, 819994962, 755946528, 40807421, |
||||||
|
263144466, 241420041, 444294464, 731606396, 304158902, 563235655, |
||||||
|
968740453, 336996831, 462831574, 407970157, 985877240, 637708754, |
||||||
|
736932700, 205026023, 755371467, 729648411, 807744117, 46482135, |
||||||
|
847092855, 620960699, 102476362, 314094354, 625838942, 550889395, |
||||||
|
639071379, 834567510, 397667304, 151945969, 443634243, 196618243, |
||||||
|
421986347, 407218337, 964502417, 327741231, 493359459, 452453139, |
||||||
|
692216398, 108161624, 816246924, 234779764, 618949448, 496133787, |
||||||
|
156374056, 316589799, 982915425, 553105889 }; |
||||||
|
return MurmurHash(s.data(), s.size(), seeds[index]) % max_num_buckets; |
||||||
|
} |
||||||
|
|
||||||
|
Status CuckooTableFactory::NewTableReader(const Options& options, |
||||||
|
const EnvOptions& soptions, const InternalKeyComparator& icomp, |
||||||
|
std::unique_ptr<RandomAccessFile>&& file, uint64_t file_size, |
||||||
|
std::unique_ptr<TableReader>* table) const { |
||||||
|
std::unique_ptr<CuckooTableReader> new_reader(new CuckooTableReader(options, |
||||||
|
std::move(file), file_size, GetSliceMurmurHash)); |
||||||
|
Status s = new_reader->status(); |
||||||
|
if (s.ok()) { |
||||||
|
*table = std::move(new_reader); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
TableBuilder* CuckooTableFactory::NewTableBuilder( |
||||||
|
const Options& options, const InternalKeyComparator& internal_comparator, |
||||||
|
WritableFile* file, CompressionType compression_type) const { |
||||||
|
return new CuckooTableBuilder(file, hash_table_ratio_, kMaxNumHashTable, |
||||||
|
max_search_depth_, GetSliceMurmurHash); |
||||||
|
} |
||||||
|
|
||||||
|
TableFactory* NewCuckooTableFactory(double hash_table_ratio, |
||||||
|
uint32_t max_search_depth) { |
||||||
|
return new CuckooTableFactory(hash_table_ratio, max_search_depth); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
#endif // ROCKSDB_LITE
|
Loading…
Reference in new issue