mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
678d3a1633 | ||
|
|
68a4cdf3f7 | ||
|
|
e37eb21e92 | ||
|
|
c2d7826ced | ||
|
|
73f9518b21 | ||
|
|
8e792e5896 | ||
|
|
1d7b4765c3 | ||
|
|
1036537c94 | ||
|
|
cacd812fb2 | ||
|
|
7c9093ab53 | ||
|
|
c42485f67c | ||
|
|
59d0b02f8b | ||
|
|
9700677a2b | ||
|
|
0f0a24e298 | ||
|
|
542cc10b19 | ||
|
|
6db52b525a | ||
|
|
abc90b067c | ||
|
|
18afff2e63 | ||
|
|
d7ba5bce37 | ||
|
|
52d7ecfc78 | ||
|
|
6fbe4e981a | ||
|
|
f3baeecd44 | ||
|
|
bf66c10b13 | ||
|
|
d364eea1fc | ||
|
|
9ee68871dc | ||
|
|
e9b675bd94 | ||
|
|
2a986919d6 | ||
|
|
8a5341ec7d | ||
|
|
821889e207 | ||
|
|
a8d5f8dde2 | ||
|
|
9ba82786ce | ||
|
|
92ca816a60 | ||
|
|
d56523c49c | ||
|
|
71e0f695c1 | ||
|
|
34ef873290 | ||
|
|
6894a50aa7 |
No files matched your search
@@ -63,7 +63,8 @@ TESTS = \
|
||||
filelock_test \
|
||||
merge_test \
|
||||
redis_test \
|
||||
manual_compaction_test
|
||||
manual_compaction_test \
|
||||
stringappend_test
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@@ -74,7 +75,7 @@ TOOLS = \
|
||||
PROGRAMS = db_bench signal_test $(TESTS) $(TOOLS)
|
||||
BENCHMARKS = db_bench_sqlite3 db_bench_tree_db
|
||||
|
||||
LIBRARY = libleveldb.a
|
||||
LIBRARY = librocksdb.a
|
||||
MEMENVLIBRARY = libmemenv.a
|
||||
|
||||
default: all
|
||||
@@ -83,15 +84,15 @@ default: all
|
||||
ifneq ($(PLATFORM_SHARED_EXT),)
|
||||
|
||||
ifneq ($(PLATFORM_SHARED_VERSIONED),true)
|
||||
SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED1 = librocksdb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1)
|
||||
SHARED3 = $(SHARED1)
|
||||
SHARED = $(SHARED1)
|
||||
else
|
||||
# Update db.h if you change these.
|
||||
SHARED_MAJOR = 1
|
||||
SHARED_MINOR = 5
|
||||
SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED_MAJOR = 2
|
||||
SHARED_MINOR = 0
|
||||
SHARED1 = librocksdb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1).$(SHARED_MAJOR)
|
||||
SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
SHARED = $(SHARED1) $(SHARED2) $(SHARED3)
|
||||
@@ -140,8 +141,9 @@ valgrind_check: all $(PROGRAMS) $(TESTS)
|
||||
done
|
||||
|
||||
clean:
|
||||
-rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) $(THRIFTSERVER) */*.o */*/*.o ios-x86/*/*.o ios-arm/*/*.o build_config.mk */*.d */*/*.d
|
||||
-rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) $(THRIFTSERVER) build_config.mk
|
||||
-rm -rf ios-x86/* ios-arm/*
|
||||
-for x in `find . -name "*.[od]"`; do rm $$x; done
|
||||
|
||||
$(LIBRARY): $(LIBOBJECTS)
|
||||
rm -f $@
|
||||
@@ -238,8 +240,8 @@ reduce_levels_test: tools/reduce_levels_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
write_batch_test: db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
merge_test: db/merge_test.o $(LIBOBJECTS)
|
||||
$(CXX) db/merge_test.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
$(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
rm -f $@
|
||||
@@ -251,7 +253,7 @@ memenv_test : helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHAR
|
||||
manual_compaction_test: util/manual_compaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/manual_compaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
leveldb_shell: tools/shell/ShellContext.o tools/shell/ShellState.o tools/shell/LeveldbShell.o tools/shell/DBClientProxy.o tools/shell/ShellContext.h tools/shell/ShellState.h tools/shell/DBClientProxy.h $(LIBOBJECTS)
|
||||
rocksdb_shell: tools/shell/ShellContext.o tools/shell/ShellState.o tools/shell/LeveldbShell.o tools/shell/DBClientProxy.o tools/shell/ShellContext.h tools/shell/ShellState.h tools/shell/DBClientProxy.h $(LIBOBJECTS)
|
||||
$(CXX) tools/shell/ShellContext.o tools/shell/ShellState.o tools/shell/LeveldbShell.o tools/shell/DBClientProxy.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
DBClientProxy_test: tools/shell/test/DBClientProxyTest.o tools/shell/DBClientProxy.o $(LIBRARY)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
rocksdb: A persistent key-value store for flash storage
|
||||
Authors: The Facebook Database Engineering Team
|
||||
Authors: * The Facebook Database Engineering Team
|
||||
* Build on earlier work on leveldb by Sanjay Ghemawat
|
||||
(sanjay@google.com) and Jeff Dean (jeff@google.com)
|
||||
|
||||
This code is a library that forms the core building block for a fast
|
||||
key value server, especially suited for storing data on flash drives.
|
||||
@@ -56,6 +58,25 @@ include/env.h
|
||||
Abstraction of the OS environment. A posix implementation of
|
||||
this interface is in util/env_posix.cc
|
||||
|
||||
include/table.h
|
||||
include/table_builder.h
|
||||
Lower-level modules that most clients probably won't use directly
|
||||
|
||||
include/cache.h
|
||||
An API for the block cache.
|
||||
|
||||
include/compaction_filter.h
|
||||
An API for a application filter invoked on every compaction.
|
||||
|
||||
include/filter_policy.h
|
||||
An API for configuring a bloom filter.
|
||||
|
||||
include/memtablerep.h
|
||||
An API for implementing a memtable.
|
||||
|
||||
include/statistics.h
|
||||
An API to retrieve various database statistics.
|
||||
|
||||
include/transaction_log_iterator.h
|
||||
An API to retrieve transaction logs from a database.
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
* Detailed instructions on how to compile using fbcode and jemalloc
|
||||
|
||||
* Latest release is 1.5.9.fb
|
||||
* Latest release is 2.1.fb
|
||||
+56
-45
@@ -48,81 +48,92 @@ Status BuildTable(const std::string& dbname,
|
||||
TableBuilder* builder = new TableBuilder(options, file.get(), 0);
|
||||
|
||||
// the first key is the smallest key
|
||||
Slice key = iter->key();
|
||||
meta->smallest.DecodeFrom(key);
|
||||
meta->smallest.DecodeFrom(iter->key());
|
||||
|
||||
MergeHelper merge(user_comparator, options.merge_operator,
|
||||
options.info_log.get(),
|
||||
true /* internal key corruption is not ok */);
|
||||
|
||||
if (purge) {
|
||||
ParsedInternalKey ikey;
|
||||
// Ugly walkaround to avoid compiler error for release build
|
||||
// TODO: find a clean way to treat in memory key corruption
|
||||
ikey.type = kTypeValue;
|
||||
bool ok __attribute__((unused)) = true;
|
||||
|
||||
// Will write to builder if current key != prev key
|
||||
ParsedInternalKey prev_ikey;
|
||||
std::string prev_value;
|
||||
std::string prev_key;
|
||||
|
||||
// Ugly walkaround to avoid compiler error for release build
|
||||
// TODO: find a clean way to treat in memory key corruption
|
||||
auto ok __attribute__((unused)) = ParseInternalKey(key, &ikey);
|
||||
// in-memory key corruption is not ok;
|
||||
assert(ok);
|
||||
|
||||
if (ikey.type == kTypeMerge) {
|
||||
// merge values if the first entry is of merge type
|
||||
merge.MergeUntil(iter, 0 /* don't worry about snapshot */);
|
||||
prev_key.assign(merge.key().data(), merge.key().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
prev_value.assign(merge.value().data(), merge.value().size());
|
||||
} else {
|
||||
// store first key-value
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
assert(prev_ikey.sequence >= earliest_seqno_in_memtable);
|
||||
iter->Next();
|
||||
}
|
||||
bool is_first_key = true; // Also write if this is the very first key
|
||||
|
||||
while (iter->Valid()) {
|
||||
bool iterator_at_next = false;
|
||||
|
||||
// Get current key
|
||||
ParsedInternalKey this_ikey;
|
||||
Slice key = iter->key();
|
||||
Slice value = iter->value();
|
||||
|
||||
// In-memory key corruption is not ok;
|
||||
// TODO: find a clean way to treat in memory key corruption
|
||||
ok = ParseInternalKey(key, &this_ikey);
|
||||
assert(ok);
|
||||
assert(this_ikey.sequence >= earliest_seqno_in_memtable);
|
||||
|
||||
if (user_comparator->Compare(prev_ikey.user_key, this_ikey.user_key)) {
|
||||
// This key is different from previous key.
|
||||
// Output prev key and remember current key
|
||||
builder->Add(Slice(prev_key), Slice(prev_value));
|
||||
// If the key is the same as the previous key (and it is not the
|
||||
// first key), then we skip it, since it is an older version.
|
||||
// Otherwise we output the key and mark it as the "new" previous key.
|
||||
if (!is_first_key && !user_comparator->Compare(prev_ikey.user_key,
|
||||
this_ikey.user_key)) {
|
||||
// seqno within the same key are in decreasing order
|
||||
assert(this_ikey.sequence < prev_ikey.sequence);
|
||||
} else {
|
||||
is_first_key = false;
|
||||
|
||||
if (this_ikey.type == kTypeMerge) {
|
||||
// Handle merge-type keys using the MergeHelper
|
||||
merge.MergeUntil(iter, 0 /* don't worry about snapshot */);
|
||||
iterator_at_next = true;
|
||||
prev_key.assign(merge.key().data(), merge.key().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
prev_value.assign(merge.value().data(), merge.value().size());
|
||||
if (merge.IsSuccess()) {
|
||||
// Merge completed correctly.
|
||||
// Add the resulting merge key/value and continue to next
|
||||
builder->Add(merge.key(), merge.value());
|
||||
prev_key.assign(merge.key().data(), merge.key().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
} else {
|
||||
// Merge did not find a Put/Delete.
|
||||
// Can not compact these merges into a kValueType.
|
||||
// Write them out one-by-one. (Proceed back() to front())
|
||||
const std::deque<std::string>& keys = merge.keys();
|
||||
const std::deque<std::string>& values = merge.values();
|
||||
assert(keys.size() == values.size() && keys.size() >= 1);
|
||||
std::deque<std::string>::const_reverse_iterator key_iter;
|
||||
std::deque<std::string>::const_reverse_iterator value_iter;
|
||||
for (key_iter=keys.rbegin(), value_iter = values.rbegin();
|
||||
key_iter != keys.rend() && value_iter != values.rend();
|
||||
++key_iter, ++value_iter) {
|
||||
|
||||
builder->Add(Slice(*key_iter), Slice(*value_iter));
|
||||
}
|
||||
|
||||
// Sanity check. Both iterators should end at the same time
|
||||
assert(key_iter == keys.rend() && value_iter == values.rend());
|
||||
|
||||
prev_key.assign(keys.front());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
}
|
||||
} else {
|
||||
// Handle Put/Delete-type keys by simply writing them
|
||||
builder->Add(key, value);
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
}
|
||||
} else {
|
||||
// seqno within the same key are in decreasing order
|
||||
assert(this_ikey.sequence < prev_ikey.sequence);
|
||||
// This key is an earlier version of the same key in prev_key.
|
||||
// Skip current key.
|
||||
}
|
||||
|
||||
if (!iterator_at_next) iter->Next();
|
||||
}
|
||||
// output last key
|
||||
builder->Add(Slice(prev_key), Slice(prev_value));
|
||||
|
||||
// The last key is the largest key
|
||||
meta->largest.DecodeFrom(Slice(prev_key));
|
||||
|
||||
} else {
|
||||
|
||||
@@ -57,6 +57,7 @@ class CorruptionTest {
|
||||
opt.env = &env_;
|
||||
opt.block_cache = tiny_cache_;
|
||||
opt.block_size_deviation = 0;
|
||||
opt.arena_block_size = 4096;
|
||||
return DB::Open(opt, dbname_, &db_);
|
||||
}
|
||||
|
||||
|
||||
+27
-24
@@ -264,13 +264,16 @@ static int FLAGS_stats_interval = 0;
|
||||
// than 0.
|
||||
static int FLAGS_stats_per_interval = 0;
|
||||
|
||||
static double FLAGS_soft_rate_limit = 0;
|
||||
|
||||
// When not equal to 0 this make threads sleep at each stats
|
||||
// reporting interval until the compaction score for all levels is
|
||||
// less than or equal to this value.
|
||||
static double FLAGS_rate_limit = 0;
|
||||
static double FLAGS_hard_rate_limit = 0;
|
||||
|
||||
// When FLAGS_rate_limit is set then this is the max time a put will be stalled.
|
||||
static int FLAGS_rate_limit_delay_milliseconds = 1000;
|
||||
// When FLAGS_hard_rate_limit is set then this is the max time a put will be
|
||||
// stalled.
|
||||
static int FLAGS_rate_limit_delay_max_milliseconds = 1000;
|
||||
|
||||
// Control maximum bytes of overlaps in grandparent (i.e., level+2) before we
|
||||
// stop building a single file in a level->level+1 compaction.
|
||||
@@ -292,18 +295,12 @@ static uint64_t FLAGS_WAL_ttl_seconds = 0;
|
||||
// Allow buffered io using OS buffers
|
||||
static bool FLAGS_use_os_buffer;
|
||||
|
||||
// Allow filesystem to do read-aheads
|
||||
static bool FLAGS_use_fsreadahead;
|
||||
|
||||
// Allow reads to occur via mmap-ing files
|
||||
static bool FLAGS_use_mmap_reads;
|
||||
|
||||
// Allow writes to occur via mmap-ing files
|
||||
static bool FLAGS_use_mmap_writes;
|
||||
|
||||
// Allow readaheads to occur for compactions
|
||||
static bool FLAGS_use_readahead_compactions;
|
||||
|
||||
// Advise random access on table file open
|
||||
static bool FLAGS_advise_random_on_open =
|
||||
leveldb::Options().advise_random_on_open;
|
||||
@@ -333,6 +330,9 @@ static auto FLAGS_use_adaptive_mutex =
|
||||
static auto FLAGS_bytes_per_sync =
|
||||
leveldb::Options().bytes_per_sync;
|
||||
|
||||
// On true, deletes use bloom-filter and drop the delete if key not present
|
||||
static bool FLAGS_filter_deletes = false;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Helper for quickly generating random data.
|
||||
@@ -1117,6 +1117,7 @@ unique_ptr<char []> GenerateKeyFromInt(int v, const char* suffix = "")
|
||||
options.max_bytes_for_level_base = FLAGS_max_bytes_for_level_base;
|
||||
options.max_bytes_for_level_multiplier =
|
||||
FLAGS_max_bytes_for_level_multiplier;
|
||||
options.filter_deletes = FLAGS_filter_deletes;
|
||||
if (FLAGS_max_bytes_for_level_multiplier_additional.size() > 0) {
|
||||
if (FLAGS_max_bytes_for_level_multiplier_additional.size() !=
|
||||
(unsigned int)FLAGS_num_levels) {
|
||||
@@ -1148,8 +1149,10 @@ unique_ptr<char []> GenerateKeyFromInt(int v, const char* suffix = "")
|
||||
options.disable_seek_compaction = FLAGS_disable_seek_compaction;
|
||||
options.delete_obsolete_files_period_micros =
|
||||
FLAGS_delete_obsolete_files_period_micros;
|
||||
options.rate_limit = FLAGS_rate_limit;
|
||||
options.rate_limit_delay_milliseconds = FLAGS_rate_limit_delay_milliseconds;
|
||||
options.soft_rate_limit = FLAGS_soft_rate_limit;
|
||||
options.hard_rate_limit = FLAGS_hard_rate_limit;
|
||||
options.rate_limit_delay_max_milliseconds =
|
||||
FLAGS_rate_limit_delay_max_milliseconds;
|
||||
options.table_cache_numshardbits = FLAGS_table_cache_numshardbits;
|
||||
options.max_grandparent_overlap_factor =
|
||||
FLAGS_max_grandparent_overlap_factor;
|
||||
@@ -1158,7 +1161,6 @@ unique_ptr<char []> GenerateKeyFromInt(int v, const char* suffix = "")
|
||||
|
||||
// fill storage options
|
||||
options.allow_os_buffer = FLAGS_use_os_buffer;
|
||||
options.allow_readahead = FLAGS_use_fsreadahead;
|
||||
options.allow_mmap_reads = FLAGS_use_mmap_reads;
|
||||
options.allow_mmap_writes = FLAGS_use_mmap_writes;
|
||||
options.advise_random_on_open = FLAGS_advise_random_on_open;
|
||||
@@ -2042,7 +2044,8 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--min_write_buffer_number_to_merge=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_min_write_buffer_number_to_merge = n;
|
||||
} else if (sscanf(argv[i], "--max_background_compactions=%d%c", &n, &junk) == 1) {
|
||||
} else if (sscanf(argv[i], "--max_background_compactions=%d%c", &n, &junk)
|
||||
== 1) {
|
||||
FLAGS_max_background_compactions = n;
|
||||
} else if (sscanf(argv[i], "--cache_size=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_cache_size = l;
|
||||
@@ -2080,12 +2083,6 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--mmap_write=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_use_mmap_writes = n;
|
||||
} else if (sscanf(argv[i], "--readahead=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_use_fsreadahead = n;
|
||||
} else if (sscanf(argv[i], "--readahead_compactions=%d%c", &n, &junk) == 1&&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_use_readahead_compactions = n;
|
||||
} else if (sscanf(argv[i], "--statistics=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
if (n == 1) {
|
||||
@@ -2182,13 +2179,16 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--stats_per_interval=%d%c", &n, &junk) == 1
|
||||
&& (n == 0 || n == 1)) {
|
||||
FLAGS_stats_per_interval = n;
|
||||
} else if (sscanf(argv[i], "--rate_limit=%lf%c", &d, &junk) == 1 &&
|
||||
} else if (sscanf(argv[i], "--soft_rate_limit=%lf%c", &d, &junk) == 1 &&
|
||||
d > 0.0) {
|
||||
FLAGS_soft_rate_limit = d;
|
||||
} else if (sscanf(argv[i], "--hard_rate_limit=%lf%c", &d, &junk) == 1 &&
|
||||
d > 1.0) {
|
||||
FLAGS_rate_limit = d;
|
||||
FLAGS_hard_rate_limit = d;
|
||||
} else if (sscanf(argv[i],
|
||||
"--rate_limit_delay_milliseconds=%d%c", &n, &junk) == 1
|
||||
&& n > 0) {
|
||||
FLAGS_rate_limit_delay_milliseconds = n;
|
||||
"--rate_limit_delay_max_milliseconds=%d%c", &n, &junk) == 1
|
||||
&& n >= 0) {
|
||||
FLAGS_rate_limit_delay_max_milliseconds = n;
|
||||
} else if (sscanf(argv[i], "--readonly=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n ==1 )) {
|
||||
FLAGS_read_only = n;
|
||||
@@ -2229,6 +2229,9 @@ int main(int argc, char** argv) {
|
||||
FLAGS_keys_per_multiget = n;
|
||||
} else if (sscanf(argv[i], "--bytes_per_sync=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_bytes_per_sync = l;
|
||||
} else if (sscanf(argv[i], "--filter_deletes=%d%c", &n, &junk)
|
||||
== 1 && (n == 0 || n ==1 )) {
|
||||
FLAGS_filter_deletes = n;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
|
||||
+363
-92
@@ -10,6 +10,7 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -127,6 +128,12 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
((size_t)64)<<30);
|
||||
ClipToRange(&result.block_size, 1<<10, 4<<20);
|
||||
|
||||
// if user sets arena_block_size, we trust user to use this value. Otherwise,
|
||||
// calculate a proper value from writer_buffer_size;
|
||||
if (result.arena_block_size <= 0) {
|
||||
result.arena_block_size = result.write_buffer_size / 10;
|
||||
}
|
||||
|
||||
result.min_write_buffer_number_to_merge = std::min(
|
||||
result.min_write_buffer_number_to_merge, result.max_write_buffer_number-1);
|
||||
if (result.info_log == nullptr) {
|
||||
@@ -147,6 +154,9 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
if (result.max_mem_compaction_level >= result.num_levels) {
|
||||
result.max_mem_compaction_level = result.num_levels - 1;
|
||||
}
|
||||
if (result.soft_rate_limit > result.hard_rate_limit) {
|
||||
result.soft_rate_limit = result.hard_rate_limit;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -162,7 +172,9 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
mutex_(options.use_adaptive_mutex),
|
||||
shutting_down_(nullptr),
|
||||
bg_cv_(&mutex_),
|
||||
mem_(new MemTable(internal_comparator_, NumberLevels())),
|
||||
mem_rep_factory_(options_.memtable_factory),
|
||||
mem_(new MemTable(internal_comparator_, mem_rep_factory_,
|
||||
NumberLevels(), options_)),
|
||||
logfile_number_(0),
|
||||
tmp_batch_(),
|
||||
bg_compaction_scheduled_(0),
|
||||
@@ -176,20 +188,28 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
stall_level0_slowdown_(0),
|
||||
stall_memtable_compaction_(0),
|
||||
stall_level0_num_files_(0),
|
||||
stall_level0_slowdown_count_(0),
|
||||
stall_memtable_compaction_count_(0),
|
||||
stall_level0_num_files_count_(0),
|
||||
started_at_(options.env->NowMicros()),
|
||||
flush_on_destroy_(false),
|
||||
stats_(options.num_levels),
|
||||
delayed_writes_(0),
|
||||
last_flushed_sequence_(0),
|
||||
storage_options_(options) {
|
||||
storage_options_(options),
|
||||
bg_work_gate_closed_(false),
|
||||
refitting_level_(false) {
|
||||
|
||||
mem_->Ref();
|
||||
|
||||
env_->GetAbsolutePath(dbname, &db_absolute_path_);
|
||||
|
||||
stall_leveln_slowdown_.resize(options.num_levels);
|
||||
for (int i = 0; i < options.num_levels; ++i)
|
||||
stall_leveln_slowdown_count_.resize(options.num_levels);
|
||||
for (int i = 0; i < options.num_levels; ++i) {
|
||||
stall_leveln_slowdown_[i] = 0;
|
||||
stall_leveln_slowdown_count_[i] = 0;
|
||||
}
|
||||
|
||||
// Reserve ten files or so for other uses and give the rest to TableCache.
|
||||
const int table_cache_size = options_.max_open_files - 10;
|
||||
@@ -208,7 +228,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
|
||||
char name[100];
|
||||
Status st = env_->GetHostName(name, 100L);
|
||||
if(st.ok()) {
|
||||
if (st.ok()) {
|
||||
host_name_ = name;
|
||||
} else {
|
||||
Log(options_.info_log, "Can't get hostname, use localhost as host name.");
|
||||
@@ -449,7 +469,7 @@ void DBImpl::PurgeObsoleteFiles(DeletionState& state) {
|
||||
}
|
||||
} else {
|
||||
Status st = env_->DeleteFile(dbname_ + "/" + state.allfiles[i]);
|
||||
if(!st.ok()) {
|
||||
if (!st.ok()) {
|
||||
Log(options_.info_log, "Delete type=%d #%lld FAILED\n",
|
||||
int(type),
|
||||
static_cast<unsigned long long>(number));
|
||||
@@ -685,10 +705,11 @@ Status DBImpl::RecoverLogFile(uint64_t log_number,
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
|
||||
if (mem == nullptr) {
|
||||
mem = new MemTable(internal_comparator_, NumberLevels());
|
||||
mem = new MemTable(internal_comparator_, mem_rep_factory_,
|
||||
NumberLevels(), options_);
|
||||
mem->Ref();
|
||||
}
|
||||
status = WriteBatchInternal::InsertInto(&batch, mem);
|
||||
status = WriteBatchInternal::InsertInto(&batch, mem, &options_);
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
@@ -899,7 +920,8 @@ Status DBImpl::CompactMemTable(bool* madeProgress) {
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::CompactRange(const Slice* begin, const Slice* end) {
|
||||
void DBImpl::CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level) {
|
||||
int max_level_with_files = 1;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
@@ -914,6 +936,78 @@ void DBImpl::CompactRange(const Slice* begin, const Slice* end) {
|
||||
for (int level = 0; level < max_level_with_files; level++) {
|
||||
TEST_CompactRange(level, begin, end);
|
||||
}
|
||||
|
||||
if (reduce_level) {
|
||||
ReFitLevel(max_level_with_files);
|
||||
}
|
||||
}
|
||||
|
||||
// return the same level if it cannot be moved
|
||||
int DBImpl::FindMinimumEmptyLevelFitting(int level) {
|
||||
mutex_.AssertHeld();
|
||||
int minimum_level = level;
|
||||
for (int i = level - 1; i > 0; --i) {
|
||||
// stop if level i is not empty
|
||||
if (versions_->NumLevelFiles(i) > 0) break;
|
||||
|
||||
// stop if level i is too small (cannot fit the level files)
|
||||
if (versions_->MaxBytesForLevel(i) < versions_->NumLevelBytes(level)) break;
|
||||
|
||||
minimum_level = i;
|
||||
}
|
||||
return minimum_level;
|
||||
}
|
||||
|
||||
void DBImpl::ReFitLevel(int level) {
|
||||
assert(level < NumberLevels());
|
||||
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// only allow one thread refitting
|
||||
if (refitting_level_) {
|
||||
Log(options_.info_log, "ReFitLevel: another thread is refitting");
|
||||
return;
|
||||
}
|
||||
refitting_level_ = true;
|
||||
|
||||
// wait for all background threads to stop
|
||||
bg_work_gate_closed_ = true;
|
||||
while (bg_compaction_scheduled_ > 0) {
|
||||
Log(options_.info_log,
|
||||
"RefitLevel: waiting for background threads to stop: %d",
|
||||
bg_compaction_scheduled_);
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
// move to a smaller level
|
||||
int to_level = FindMinimumEmptyLevelFitting(level);
|
||||
|
||||
assert(to_level <= level);
|
||||
|
||||
if (to_level < level) {
|
||||
Log(options_.info_log, "Before refitting:\n%s",
|
||||
versions_->current()->DebugString().data());
|
||||
|
||||
VersionEdit edit(NumberLevels());
|
||||
for (const auto& f : versions_->current()->files_[level]) {
|
||||
edit.DeleteFile(level, f->number);
|
||||
edit.AddFile(to_level, f->number, f->file_size, f->smallest, f->largest);
|
||||
}
|
||||
Log(options_.info_log, "Apply version edit:\n%s",
|
||||
edit.DebugString().data());
|
||||
|
||||
auto status = versions_->LogAndApply(&edit, &mutex_);
|
||||
|
||||
Log(options_.info_log, "LogAndApply: %s\n", status.ToString().data());
|
||||
|
||||
if (status.ok()) {
|
||||
Log(options_.info_log, "After refitting:\n%s",
|
||||
versions_->current()->DebugString().data());
|
||||
}
|
||||
}
|
||||
|
||||
refitting_level_ = false;
|
||||
bg_work_gate_closed_ = false;
|
||||
}
|
||||
|
||||
int DBImpl::NumberLevels() {
|
||||
@@ -1016,7 +1110,7 @@ Status DBImpl::FindProbableWALFiles(std::vector<LogFile>* const allLogs,
|
||||
}
|
||||
}
|
||||
size_t startIndex = std::max(0l, end); // end could be -ve.
|
||||
for( size_t i = startIndex; i < allLogs->size(); ++i) {
|
||||
for (size_t i = startIndex; i < allLogs->size(); ++i) {
|
||||
result->push_back(allLogs->at(i));
|
||||
}
|
||||
if (result->empty()) {
|
||||
@@ -1237,7 +1331,9 @@ Status DBImpl::TEST_WaitForCompact() {
|
||||
|
||||
void DBImpl::MaybeScheduleCompaction() {
|
||||
mutex_.AssertHeld();
|
||||
if (bg_compaction_scheduled_ >= options_.max_background_compactions) {
|
||||
if (bg_work_gate_closed_) {
|
||||
// gate closed for backgrond work
|
||||
} else if (bg_compaction_scheduled_ >= options_.max_background_compactions) {
|
||||
// Already scheduled
|
||||
} else if (shutting_down_.Acquire_Load()) {
|
||||
// DB is being deleted; no more background compactions
|
||||
@@ -1607,7 +1703,6 @@ inline SequenceNumber DBImpl::findEarliestVisibleSnapshot(
|
||||
|
||||
Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
int64_t imm_micros = 0; // Micros spent doing imm_ compactions
|
||||
|
||||
Log(options_.info_log,
|
||||
"Compacting %d@%d + %d@%d files, score %.2f slots available %d",
|
||||
compact->compaction->num_input_files(0),
|
||||
@@ -1697,7 +1792,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
|
||||
// Handle key/value, add to state, etc.
|
||||
bool drop = false;
|
||||
bool current_entry_is_merged = false;
|
||||
bool current_entry_is_merging = false;
|
||||
if (!ParseInternalKey(key, &ikey)) {
|
||||
// Do not hide error keys
|
||||
// TODO: error key stays in db forever? Figure out the intention/rationale
|
||||
@@ -1791,11 +1886,24 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
// logic could also be nicely re-used for memtable flush purge
|
||||
// optimization in BuildTable.
|
||||
merge.MergeUntil(input.get(), prev_snapshot, bottommost_level);
|
||||
current_entry_is_merged = true;
|
||||
// get the merge result
|
||||
key = merge.key();
|
||||
ParseInternalKey(key, &ikey);
|
||||
value = merge.value();
|
||||
current_entry_is_merging = true;
|
||||
if (merge.IsSuccess()) {
|
||||
// Successfully found Put/Delete/(end-of-key-range) while merging
|
||||
// Get the merge result
|
||||
key = merge.key();
|
||||
ParseInternalKey(key, &ikey);
|
||||
value = merge.value();
|
||||
} else {
|
||||
// Did not find a Put/Delete/(end-of-key-range) while merging
|
||||
// We now have some stack of merge operands to write out.
|
||||
// NOTE: key,value, and ikey are now referring to old entries.
|
||||
// These will be correctly set below.
|
||||
assert(!merge.keys().empty());
|
||||
assert(merge.keys().size() == merge.values().size());
|
||||
|
||||
// Hack to make sure last_sequence_for_key is correct
|
||||
ParseInternalKey(merge.keys().front(), &ikey);
|
||||
}
|
||||
}
|
||||
|
||||
last_sequence_for_key = ikey.sequence;
|
||||
@@ -1813,52 +1921,97 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
#endif
|
||||
|
||||
if (!drop) {
|
||||
// We may write a single key (e.g.: for Put/Delete or successful merge).
|
||||
// Or we may instead have to write a sequence/list of keys.
|
||||
// We have to write a sequence iff we have an unsuccessful merge
|
||||
bool has_merge_list = current_entry_is_merging && !merge.IsSuccess();
|
||||
const std::deque<std::string>* keys = nullptr;
|
||||
const std::deque<std::string>* values = nullptr;
|
||||
std::deque<std::string>::const_reverse_iterator key_iter;
|
||||
std::deque<std::string>::const_reverse_iterator value_iter;
|
||||
if (has_merge_list) {
|
||||
keys = &merge.keys();
|
||||
values = &merge.values();
|
||||
key_iter = keys->rbegin(); // The back (*rbegin()) is the first key
|
||||
value_iter = values->rbegin();
|
||||
|
||||
char* kptr = (char*)key.data();
|
||||
std::string kstr;
|
||||
|
||||
// Zeroing out the sequence number leads to better compression.
|
||||
// If this is the bottommost level (no files in lower levels)
|
||||
// and the earliest snapshot is larger than this seqno
|
||||
// then we can squash the seqno to zero.
|
||||
if (bottommost_level && ikey.sequence < earliest_snapshot &&
|
||||
ikey.type != kTypeMerge) {
|
||||
assert(ikey.type != kTypeDeletion);
|
||||
// make a copy because updating in place would cause problems
|
||||
// with the priority queue that is managing the input key iterator
|
||||
kstr.assign(key.data(), key.size());
|
||||
kptr = (char *)kstr.c_str();
|
||||
UpdateInternalKey(kptr, key.size(), (uint64_t)0, ikey.type);
|
||||
key = Slice(*key_iter);
|
||||
value = Slice(*value_iter);
|
||||
}
|
||||
|
||||
Slice newkey(kptr, key.size());
|
||||
assert((key.clear(), 1)); // we do not need 'key' anymore
|
||||
// If we have a list of keys to write, traverse the list.
|
||||
// If we have a single key to write, simply write that key.
|
||||
while (true) {
|
||||
// Invariant: key,value,ikey will always be the next entry to write
|
||||
char* kptr = (char*)key.data();
|
||||
std::string kstr;
|
||||
|
||||
// Open output file if necessary
|
||||
if (compact->builder == nullptr) {
|
||||
status = OpenCompactionOutputFile(compact);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
// Zeroing out the sequence number leads to better compression.
|
||||
// If this is the bottommost level (no files in lower levels)
|
||||
// and the earliest snapshot is larger than this seqno
|
||||
// then we can squash the seqno to zero.
|
||||
if (bottommost_level && ikey.sequence < earliest_snapshot &&
|
||||
ikey.type != kTypeMerge) {
|
||||
assert(ikey.type != kTypeDeletion);
|
||||
// make a copy because updating in place would cause problems
|
||||
// with the priority queue that is managing the input key iterator
|
||||
kstr.assign(key.data(), key.size());
|
||||
kptr = (char *)kstr.c_str();
|
||||
UpdateInternalKey(kptr, key.size(), (uint64_t)0, ikey.type);
|
||||
}
|
||||
}
|
||||
if (compact->builder->NumEntries() == 0) {
|
||||
compact->current_output()->smallest.DecodeFrom(newkey);
|
||||
}
|
||||
compact->current_output()->largest.DecodeFrom(newkey);
|
||||
compact->builder->Add(newkey, value);
|
||||
|
||||
// Close output file if it is big enough
|
||||
if (compact->builder->FileSize() >=
|
||||
compact->compaction->MaxOutputFileSize()) {
|
||||
status = FinishCompactionOutputFile(compact, input.get());
|
||||
if (!status.ok()) {
|
||||
Slice newkey(kptr, key.size());
|
||||
assert((key.clear(), 1)); // we do not need 'key' anymore
|
||||
|
||||
// Open output file if necessary
|
||||
if (compact->builder == nullptr) {
|
||||
status = OpenCompactionOutputFile(compact);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (compact->builder->NumEntries() == 0) {
|
||||
compact->current_output()->smallest.DecodeFrom(newkey);
|
||||
}
|
||||
compact->current_output()->largest.DecodeFrom(newkey);
|
||||
compact->builder->Add(newkey, value);
|
||||
|
||||
// Close output file if it is big enough
|
||||
if (compact->builder->FileSize() >=
|
||||
compact->compaction->MaxOutputFileSize()) {
|
||||
status = FinishCompactionOutputFile(compact, input.get());
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a list of entries, move to next element
|
||||
// If we only had one entry, then break the loop.
|
||||
if (has_merge_list) {
|
||||
++key_iter;
|
||||
++value_iter;
|
||||
|
||||
// If at end of list
|
||||
if (key_iter == keys->rend() || value_iter == values->rend()) {
|
||||
// Sanity Check: if one ends, then both end
|
||||
assert(key_iter == keys->rend() && value_iter == values->rend());
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise not at end of list. Update key, value, and ikey.
|
||||
key = Slice(*key_iter);
|
||||
value = Slice(*value_iter);
|
||||
ParseInternalKey(key, &ikey);
|
||||
|
||||
} else{
|
||||
// Only had one item to begin with (Put/Delete)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MergeUntil has moved input to the next entry
|
||||
if (!current_entry_is_merged) {
|
||||
if (!current_entry_is_merging) {
|
||||
input->Next();
|
||||
}
|
||||
}
|
||||
@@ -1997,6 +2150,14 @@ int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes() {
|
||||
Status DBImpl::Get(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value) {
|
||||
return GetImpl(options, key, value);
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
const bool no_io,
|
||||
bool* value_found) {
|
||||
Status s;
|
||||
|
||||
StopWatch sw(env_, options_.statistics, DB_GET);
|
||||
@@ -2016,21 +2177,25 @@ Status DBImpl::Get(const ReadOptions& options,
|
||||
current->Ref();
|
||||
|
||||
// Unlock while reading from files and memtables
|
||||
|
||||
mutex_.Unlock();
|
||||
bool have_stat_update = false;
|
||||
Version::GetStats stats;
|
||||
|
||||
|
||||
// Prepare to store a list of merge operations if merge occurs.
|
||||
std::deque<std::string> merge_operands;
|
||||
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
// s is both in/out. When in, s could either be OK or MergeInProgress.
|
||||
// value will contain the current merge operand in the latter case.
|
||||
// merge_operands will contain the sequence of merges in the latter case.
|
||||
LookupKey lkey(key, snapshot);
|
||||
if (mem->Get(lkey, value, &s, options_)) {
|
||||
if (mem->Get(lkey, value, &s, &merge_operands, options_)) {
|
||||
// Done
|
||||
} else if (imm.Get(lkey, value, &s, options_)) {
|
||||
} else if (imm.Get(lkey, value, &s, &merge_operands, options_)) {
|
||||
// Done
|
||||
} else {
|
||||
current->Get(options, lkey, value, &s, &stats, options_);
|
||||
current->Get(options, lkey, value, &s, &merge_operands, &stats,
|
||||
options_, value_found);
|
||||
have_stat_update = true;
|
||||
}
|
||||
mutex_.Lock();
|
||||
@@ -2073,6 +2238,9 @@ std::vector<Status> DBImpl::MultiGet(const ReadOptions& options,
|
||||
bool have_stat_update = false;
|
||||
Version::GetStats stats;
|
||||
|
||||
// Prepare to store a list of merge operations if merge occurs.
|
||||
std::deque<std::string> merge_operands;
|
||||
|
||||
// Note: this always resizes the values array
|
||||
int numKeys = keys.size();
|
||||
std::vector<Status> statList(numKeys);
|
||||
@@ -2084,18 +2252,19 @@ std::vector<Status> DBImpl::MultiGet(const ReadOptions& options,
|
||||
// For each of the given keys, apply the entire "get" process as follows:
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
// s is both in/out. When in, s could either be OK or MergeInProgress.
|
||||
// value will contain the current merge operand in the latter case.
|
||||
for(int i=0; i<numKeys; ++i) {
|
||||
// merge_operands will contain the sequence of merges in the latter case.
|
||||
for (int i=0; i<numKeys; ++i) {
|
||||
merge_operands.clear();
|
||||
Status& s = statList[i];
|
||||
std::string* value = &(*values)[i];
|
||||
|
||||
LookupKey lkey(keys[i], snapshot);
|
||||
if (mem->Get(lkey, value, &s, options_)) {
|
||||
if (mem->Get(lkey, value, &s, &merge_operands, options_)) {
|
||||
// Done
|
||||
} else if (imm.Get(lkey, value, &s, options_)) {
|
||||
} else if (imm.Get(lkey, value, &s, &merge_operands, options_)) {
|
||||
// Done
|
||||
} else {
|
||||
current->Get(options, lkey, value, &s, &stats, options_);
|
||||
current->Get(options, lkey, value, &s, &merge_operands, &stats, options_);
|
||||
have_stat_update = true;
|
||||
}
|
||||
|
||||
@@ -2120,6 +2289,16 @@ std::vector<Status> DBImpl::MultiGet(const ReadOptions& options,
|
||||
return statList;
|
||||
}
|
||||
|
||||
bool DBImpl::KeyMayExist(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found) {
|
||||
if (value_found != nullptr) {
|
||||
*value_found = true; // falsify later if key-may-exist but can't fetch value
|
||||
}
|
||||
return GetImpl(options, key, value, true, value_found).ok();
|
||||
}
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& options) {
|
||||
SequenceNumber latest_snapshot;
|
||||
Iterator* internal_iter = NewInternalIterator(options, &latest_snapshot);
|
||||
@@ -2213,14 +2392,21 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = WriteBatchInternal::InsertInto(updates, mem_);
|
||||
status = WriteBatchInternal::InsertInto(updates, mem_, &options_, this,
|
||||
options_.filter_deletes);
|
||||
if (!status.ok()) {
|
||||
// Panic for in-memory corruptions
|
||||
// Note that existing logic was not sound. Any partial failure writing
|
||||
// into the memtable would result in a state that some write ops might
|
||||
// have succeeded in memtable but Status reports error for all writes.
|
||||
throw std::runtime_error("In memory WriteBatch corruption!");
|
||||
}
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
last_flushed_sequence_ = current_sequence;
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
last_flushed_sequence_ = current_sequence;
|
||||
if (updates == &tmp_batch_) tmp_batch_.Clear();
|
||||
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
@@ -2296,13 +2482,46 @@ WriteBatch* DBImpl::BuildBatchGroup(Writer** last_writer) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// This function computes the amount of time in microseconds by which a write
|
||||
// should be delayed based on the number of level-0 files according to the
|
||||
// following formula:
|
||||
// if n < bottom, return 0;
|
||||
// if n >= top, return 1000;
|
||||
// otherwise, let r = (n - bottom) /
|
||||
// (top - bottom)
|
||||
// and return r^2 * 1000.
|
||||
// The goal of this formula is to gradually increase the rate at which writes
|
||||
// are slowed. We also tried linear delay (r * 1000), but it seemed to do
|
||||
// slightly worse. There is no other particular reason for choosing quadratic.
|
||||
uint64_t DBImpl::SlowdownAmount(int n, int top, int bottom) {
|
||||
uint64_t delay;
|
||||
if (n >= top) {
|
||||
delay = 1000;
|
||||
}
|
||||
else if (n < bottom) {
|
||||
delay = 0;
|
||||
}
|
||||
else {
|
||||
// If we are here, we know that:
|
||||
// level0_start_slowdown <= n < level0_slowdown
|
||||
// since the previous two conditions are false.
|
||||
float how_much =
|
||||
(float) (n - bottom) /
|
||||
(top - bottom);
|
||||
delay = how_much * how_much * 1000;
|
||||
}
|
||||
assert(delay <= 1000);
|
||||
return delay;
|
||||
}
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
mutex_.AssertHeld();
|
||||
assert(!writers_.empty());
|
||||
bool allow_delay = !force;
|
||||
bool allow_rate_limit_delay = !force;
|
||||
bool allow_hard_rate_limit_delay = !force;
|
||||
bool allow_soft_rate_limit_delay = !force;
|
||||
uint64_t rate_limit_delay_millis = 0;
|
||||
Status s;
|
||||
double score;
|
||||
@@ -2319,15 +2538,23 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
// We are getting close to hitting a hard limit on the number of
|
||||
// L0 files. Rather than delaying a single write by several
|
||||
// seconds when we hit the hard limit, start delaying each
|
||||
// individual write by 1ms to reduce latency variance. Also,
|
||||
// individual write by 0-1ms to reduce latency variance. Also,
|
||||
// this delay hands over some CPU to the compaction thread in
|
||||
// case it is sharing the same core as the writer.
|
||||
mutex_.Unlock();
|
||||
uint64_t t1 = env_->NowMicros();
|
||||
env_->SleepForMicroseconds(1000);
|
||||
uint64_t delayed = env_->NowMicros() - t1;
|
||||
uint64_t delayed;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics, STALL_L0_SLOWDOWN_COUNT);
|
||||
env_->SleepForMicroseconds(
|
||||
SlowdownAmount(versions_->NumLevelFiles(0),
|
||||
options_.level0_slowdown_writes_trigger,
|
||||
options_.level0_stop_writes_trigger)
|
||||
);
|
||||
delayed = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics, STALL_L0_SLOWDOWN_MICROS, delayed);
|
||||
stall_level0_slowdown_ += delayed;
|
||||
stall_level0_slowdown_count_++;
|
||||
allow_delay = false; // Do not delay a single write more than once
|
||||
//Log(options_.info_log,
|
||||
// "delaying write %llu usecs for level0_slowdown_writes_trigger\n",
|
||||
@@ -2346,44 +2573,76 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
// ones are still being compacted, so we wait.
|
||||
DelayLoggingAndReset();
|
||||
Log(options_.info_log, "wait for memtable compaction...\n");
|
||||
uint64_t t1 = env_->NowMicros();
|
||||
bg_cv_.Wait();
|
||||
const uint64_t stall = env_->NowMicros() -t1;
|
||||
uint64_t stall;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics,
|
||||
STALL_MEMTABLE_COMPACTION_COUNT);
|
||||
bg_cv_.Wait();
|
||||
stall = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics, STALL_MEMTABLE_COMPACTION_MICROS, stall);
|
||||
stall_memtable_compaction_ += stall;
|
||||
stall_memtable_compaction_count_++;
|
||||
} else if (versions_->NumLevelFiles(0) >=
|
||||
options_.level0_stop_writes_trigger) {
|
||||
// There are too many level-0 files.
|
||||
DelayLoggingAndReset();
|
||||
uint64_t t1 = env_->NowMicros();
|
||||
Log(options_.info_log, "wait for fewer level0 files...\n");
|
||||
bg_cv_.Wait();
|
||||
const uint64_t stall = env_->NowMicros() - t1;
|
||||
uint64_t stall;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics, STALL_L0_NUM_FILES_COUNT);
|
||||
bg_cv_.Wait();
|
||||
stall = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics, STALL_L0_NUM_FILES_MICROS, stall);
|
||||
stall_level0_num_files_ += stall;
|
||||
stall_level0_num_files_count_++;
|
||||
} else if (
|
||||
allow_rate_limit_delay &&
|
||||
options_.rate_limit > 1.0 &&
|
||||
(score = versions_->MaxCompactionScore()) > options_.rate_limit) {
|
||||
allow_hard_rate_limit_delay &&
|
||||
options_.hard_rate_limit > 1.0 &&
|
||||
(score = versions_->MaxCompactionScore()) > options_.hard_rate_limit) {
|
||||
// Delay a write when the compaction score for any level is too large.
|
||||
int max_level = versions_->MaxCompactionScoreLevel();
|
||||
mutex_.Unlock();
|
||||
uint64_t t1 = env_->NowMicros();
|
||||
env_->SleepForMicroseconds(1000);
|
||||
uint64_t delayed = env_->NowMicros() - t1;
|
||||
uint64_t delayed;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics, HARD_RATE_LIMIT_DELAY_COUNT);
|
||||
env_->SleepForMicroseconds(1000);
|
||||
delayed = sw.ElapsedMicros();
|
||||
}
|
||||
stall_leveln_slowdown_[max_level] += delayed;
|
||||
stall_leveln_slowdown_count_[max_level]++;
|
||||
// Make sure the following value doesn't round to zero.
|
||||
uint64_t rate_limit = std::max((delayed / 1000), (uint64_t) 1);
|
||||
rate_limit_delay_millis += rate_limit;
|
||||
RecordTick(options_.statistics, RATE_LIMIT_DELAY_MILLIS, rate_limit);
|
||||
if (rate_limit_delay_millis >=
|
||||
(unsigned)options_.rate_limit_delay_milliseconds) {
|
||||
allow_rate_limit_delay = false;
|
||||
if (options_.rate_limit_delay_max_milliseconds > 0 &&
|
||||
rate_limit_delay_millis >=
|
||||
(unsigned)options_.rate_limit_delay_max_milliseconds) {
|
||||
allow_hard_rate_limit_delay = false;
|
||||
}
|
||||
// Log(options_.info_log,
|
||||
// "delaying write %llu usecs for rate limits with max score %.2f\n",
|
||||
// (long long unsigned int)delayed, score);
|
||||
mutex_.Lock();
|
||||
} else if (
|
||||
allow_soft_rate_limit_delay &&
|
||||
options_.soft_rate_limit > 0.0 &&
|
||||
(score = versions_->MaxCompactionScore()) > options_.soft_rate_limit) {
|
||||
// Delay a write when the compaction score for any level is too large.
|
||||
// TODO: add statistics
|
||||
mutex_.Unlock();
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics, SOFT_RATE_LIMIT_DELAY_COUNT);
|
||||
env_->SleepForMicroseconds(SlowdownAmount(
|
||||
score,
|
||||
options_.soft_rate_limit,
|
||||
options_.hard_rate_limit)
|
||||
);
|
||||
rate_limit_delay_millis += sw.ElapsedMicros();
|
||||
}
|
||||
allow_soft_rate_limit_delay = false;
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
// Attempt to switch to a new memtable and trigger compaction of old
|
||||
DelayLoggingAndReset();
|
||||
@@ -2409,7 +2668,8 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
log_.reset(new log::Writer(std::move(lfile)));
|
||||
mem_->SetLogNumber(logfile_number_);
|
||||
imm_.Add(mem_);
|
||||
mem_ = new MemTable(internal_comparator_, NumberLevels());
|
||||
mem_ = new MemTable(internal_comparator_, mem_rep_factory_,
|
||||
NumberLevels(), options_);
|
||||
mem_->Ref();
|
||||
force = false; // Do not force another compaction if have room
|
||||
MaybeScheduleCompaction();
|
||||
@@ -2465,6 +2725,7 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) {
|
||||
// Add "+1" to make sure seconds_up is > 0 and avoid NaN later
|
||||
double seconds_up = (micros_up + 1) / 1000000.0;
|
||||
uint64_t total_slowdown = 0;
|
||||
uint64_t total_slowdown_count = 0;
|
||||
uint64_t interval_bytes_written = 0;
|
||||
uint64_t interval_bytes_read = 0;
|
||||
uint64_t interval_bytes_new = 0;
|
||||
@@ -2473,8 +2734,8 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) {
|
||||
// Pardon the long line but I think it is easier to read this way.
|
||||
snprintf(buf, sizeof(buf),
|
||||
" Compactions\n"
|
||||
"Level Files Size(MB) Score Time(sec) Read(MB) Write(MB) Rn(MB) Rnp1(MB) Wnew(MB) Amplify Read(MB/s) Write(MB/s) Rn Rnp1 Wnp1 NewW Count Ln-stall\n"
|
||||
"----------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
|
||||
"Level Files Size(MB) Score Time(sec) Read(MB) Write(MB) Rn(MB) Rnp1(MB) Wnew(MB) Amplify Read(MB/s) Write(MB/s) Rn Rnp1 Wnp1 NewW Count Ln-stall Stall-cnt\n"
|
||||
"--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n"
|
||||
);
|
||||
value->append(buf);
|
||||
for (int level = 0; level < NumberLevels(); level++) {
|
||||
@@ -2494,7 +2755,7 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) {
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"%3d %8d %8.0f %5.1f %9.0f %9.0f %9.0f %9.0f %9.0f %9.0f %7.1f %9.1f %11.1f %8d %8d %8d %8d %8d %9.1f\n",
|
||||
"%3d %8d %8.0f %5.1f %9.0f %9.0f %9.0f %9.0f %9.0f %9.0f %7.1f %9.1f %11.1f %8d %8d %8d %8d %8d %9.1f %9lu\n",
|
||||
level,
|
||||
files,
|
||||
versions_->NumLevelBytes(level) / 1048576.0,
|
||||
@@ -2516,8 +2777,10 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) {
|
||||
stats_[level].files_out_levelnp1,
|
||||
stats_[level].files_out_levelnp1 - stats_[level].files_in_levelnp1,
|
||||
stats_[level].count,
|
||||
stall_leveln_slowdown_[level] / 1000000.0);
|
||||
stall_leveln_slowdown_[level] / 1000000.0,
|
||||
(unsigned long) stall_leveln_slowdown_count_[level]);
|
||||
total_slowdown += stall_leveln_slowdown_[level];
|
||||
total_slowdown_count += stall_leveln_slowdown_count_[level];
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
@@ -2593,6 +2856,15 @@ bool DBImpl::GetProperty(const Slice& property, std::string* value) {
|
||||
total_slowdown / 1000000.0);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Stalls(count): %lu level0_slowdown, %lu level0_numfiles, "
|
||||
"%lu memtable_compaction, %lu leveln_slowdown\n",
|
||||
(unsigned long) stall_level0_slowdown_count_,
|
||||
(unsigned long) stall_level0_num_files_count_,
|
||||
(unsigned long) stall_memtable_compaction_count_,
|
||||
(unsigned long) total_slowdown_count);
|
||||
value->append(buf);
|
||||
|
||||
last_stats_.bytes_read_ = total_bytes_read;
|
||||
last_stats_.bytes_written_ = total_bytes_written;
|
||||
last_stats_.bytes_new_ = stats_[0].bytes_written;
|
||||
@@ -2663,8 +2935,7 @@ Status DB::Merge(const WriteOptions& opt, const Slice& key,
|
||||
|
||||
DB::~DB() { }
|
||||
|
||||
Status DB::Open(const Options& options, const std::string& dbname,
|
||||
DB** dbptr) {
|
||||
Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
|
||||
*dbptr = nullptr;
|
||||
EnvOptions soptions;
|
||||
|
||||
|
||||
+40
-1
@@ -18,6 +18,7 @@
|
||||
#include "port/port.h"
|
||||
#include "util/stats_logger.h"
|
||||
#include "memtablelist.h"
|
||||
#include "leveldb/memtablerep.h"
|
||||
|
||||
#ifdef USE_SCRIBE
|
||||
#include "scribe/scribe_logger.h"
|
||||
@@ -48,12 +49,22 @@ class DBImpl : public DB {
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values);
|
||||
|
||||
// Returns false if key doesn't exist in the database and true if it may.
|
||||
// If value_found is not passed in as null, then return the value if found in
|
||||
// memory. On return, if value was found, then value_found will be set to true
|
||||
// , otherwise false.
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr);
|
||||
virtual Iterator* NewIterator(const ReadOptions&);
|
||||
virtual const Snapshot* GetSnapshot();
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot);
|
||||
virtual bool GetProperty(const Slice& property, std::string* value);
|
||||
virtual void GetApproximateSizes(const Range* range, int n, uint64_t* sizes);
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end);
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false);
|
||||
virtual int NumberLevels();
|
||||
virtual int MaxMemCompactionLevel();
|
||||
virtual int Level0StopWriteTrigger();
|
||||
@@ -155,6 +166,7 @@ class DBImpl : public DB {
|
||||
Status WriteLevel0Table(std::vector<MemTable*> &mems, VersionEdit* edit,
|
||||
uint64_t* filenumber);
|
||||
|
||||
uint64_t SlowdownAmount(int n, int top, int bottom);
|
||||
Status MakeRoomForWrite(bool force /* compact even if there is room? */);
|
||||
WriteBatch* BuildBatchGroup(Writer** last_writer);
|
||||
|
||||
@@ -217,6 +229,14 @@ class DBImpl : public DB {
|
||||
// dump leveldb.stats to LOG
|
||||
void MaybeDumpStats();
|
||||
|
||||
// Return the minimum empty level that could hold the total data in the
|
||||
// input level. Return the input level, if such level could not be found.
|
||||
int FindMinimumEmptyLevelFitting(int level);
|
||||
|
||||
// Move the files in the input level to the minimum level that could hold
|
||||
// the data set.
|
||||
void ReFitLevel(int level);
|
||||
|
||||
// Constant after construction
|
||||
const InternalFilterPolicy internal_filter_policy_;
|
||||
bool owns_info_log_;
|
||||
@@ -231,6 +251,7 @@ class DBImpl : public DB {
|
||||
port::Mutex mutex_;
|
||||
port::AtomicPointer shutting_down_;
|
||||
port::CondVar bg_cv_; // Signalled when background work finishes
|
||||
std::shared_ptr<MemTableRepFactory> mem_rep_factory_;
|
||||
MemTable* mem_;
|
||||
MemTableList imm_; // Memtable that are not changing
|
||||
uint64_t logfile_number_;
|
||||
@@ -289,6 +310,10 @@ class DBImpl : public DB {
|
||||
uint64_t stall_memtable_compaction_;
|
||||
uint64_t stall_level0_num_files_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_;
|
||||
uint64_t stall_level0_slowdown_count_;
|
||||
uint64_t stall_memtable_compaction_count_;
|
||||
uint64_t stall_level0_num_files_count_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_;
|
||||
|
||||
// Time at which this instance was started.
|
||||
const uint64_t started_at_;
|
||||
@@ -366,6 +391,12 @@ class DBImpl : public DB {
|
||||
// The options to access storage files
|
||||
const EnvOptions storage_options_;
|
||||
|
||||
// A value of true temporarily disables scheduling of background work
|
||||
bool bg_work_gate_closed_;
|
||||
|
||||
// Guard against multiple concurrent refitting
|
||||
bool refitting_level_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
@@ -379,6 +410,14 @@ class DBImpl : public DB {
|
||||
SequenceNumber in,
|
||||
std::vector<SequenceNumber>& snapshots,
|
||||
SequenceNumber* prev_snapshot);
|
||||
|
||||
// Function that Get and KeyMayExist call with no_io true or false
|
||||
// Note: 'value_found' from KeyMayExist propagates here
|
||||
Status GetImpl(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
const bool no_io = false,
|
||||
bool* value_found = nullptr);
|
||||
};
|
||||
|
||||
// Sanitize db options. The caller should delete result.info_log if
|
||||
|
||||
@@ -53,11 +53,12 @@ Status DBImplReadOnly::Get(const ReadOptions& options,
|
||||
MemTable* mem = GetMemTable();
|
||||
Version* current = versions_->current();
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
std::deque<std::string> merge_operands;
|
||||
LookupKey lkey(key, snapshot);
|
||||
if (mem->Get(lkey, value, &s, options_)) {
|
||||
if (mem->Get(lkey, value, &s, &merge_operands, options_)) {
|
||||
} else {
|
||||
Version::GetStats stats;
|
||||
current->Get(options, lkey, value, &s, &stats, options_);
|
||||
current->Get(options, lkey, value, &s, &merge_operands, &stats, options_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ public:
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value);
|
||||
|
||||
// TODO: Implement ReadOnly MultiGet?
|
||||
|
||||
virtual Iterator* NewIterator(const ReadOptions&);
|
||||
|
||||
virtual Status Put(const WriteOptions&, const Slice& key, const Slice& value) {
|
||||
@@ -47,7 +50,8 @@ public:
|
||||
virtual Status Write(const WriteOptions& options, WriteBatch* updates) {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end) {
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false) {
|
||||
}
|
||||
virtual Status DisableFileDeletions() {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
|
||||
+33
-16
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -231,10 +232,13 @@ void DBIter::FindNextUserEntry(bool skipping) {
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
void DBIter::MergeValuesNewToOld() {
|
||||
// TODO: Is there a way to unite with MergeHelper or other similar code?
|
||||
|
||||
const Slice value = iter_->value();
|
||||
std::string operand(value.data(), value.size());
|
||||
// Start the merge process by pushing the first operand
|
||||
std::deque<std::string> operands;
|
||||
operands.push_front(iter_->value().ToString());
|
||||
|
||||
std::string merge_result; // Temporary string to hold merge result later
|
||||
ParsedInternalKey ikey;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
if (!ParseKey(&ikey)) {
|
||||
@@ -255,10 +259,11 @@ void DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
|
||||
if (kTypeValue == ikey.type) {
|
||||
// hit a put, merge the put value with operand and store it in the
|
||||
// final result saved_value_. We are done!
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
// ignore corruption if there is any.
|
||||
const Slice value = iter_->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, Slice(operand),
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, operands,
|
||||
&saved_value_, logger_.get());
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
@@ -266,29 +271,41 @@ void DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
|
||||
if (kTypeMerge == ikey.type) {
|
||||
// hit a merge, merge the value with operand and continue.
|
||||
// saved_value_ is used as a scratch area. The result is put
|
||||
// back in operand
|
||||
const Slice value = iter_->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, operand,
|
||||
&saved_value_, logger_.get());
|
||||
swap(saved_value_, operand);
|
||||
// hit a merge, add the value as an operand and run associative merge.
|
||||
// when complete, add result to operands and continue.
|
||||
const Slice& value = iter_->value();
|
||||
operands.push_front(value.ToString());
|
||||
while(operands.size() >= 2) {
|
||||
// Call user associative-merge until it returns false
|
||||
if (user_merge_operator_->PartialMerge(ikey.user_key,
|
||||
Slice(operands[0]),
|
||||
Slice(operands[1]),
|
||||
&merge_result,
|
||||
logger_.get())) {
|
||||
operands.pop_front();
|
||||
swap(operands.front(), merge_result);
|
||||
} else {
|
||||
// Associative merge returns false ==> stack the operands
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge opexrator, such that
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
user_merge_operator_->Merge(ikey.user_key, nullptr, operand,
|
||||
user_merge_operator_->Merge(ikey.user_key, nullptr, operands,
|
||||
&saved_value_, logger_.get());
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
|
||||
// TODO: support backward iteration
|
||||
// Throw an exception now if merge_operator is provided
|
||||
// TODO: support backward iteration
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "Prev not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::Prev backward iteration not supported"
|
||||
@@ -387,8 +404,8 @@ void DBIter::SeekToFirst() {
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
// Throw an exception for now if merge_operator is provided
|
||||
// TODO: support backward iteration
|
||||
// throw an exception for now if merge_operator is provided
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "SeekToLast not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::SeekToLast: backward iteration not"
|
||||
|
||||
+99
-4
@@ -218,6 +218,7 @@ class DBTest {
|
||||
kManifestFileSize,
|
||||
kCompactOnFlush,
|
||||
kPerfOptions,
|
||||
kDeletesFilterFirst,
|
||||
kEnd
|
||||
};
|
||||
int option_config_;
|
||||
@@ -285,10 +286,13 @@ class DBTest {
|
||||
options.purge_redundant_kvs_while_flush = !options.purge_redundant_kvs_while_flush;
|
||||
break;
|
||||
case kPerfOptions:
|
||||
options.rate_limit = 2.0;
|
||||
options.rate_limit_delay_milliseconds = 2;
|
||||
options.hard_rate_limit = 2.0;
|
||||
options.rate_limit_delay_max_milliseconds = 2;
|
||||
// TODO -- test more options
|
||||
break;
|
||||
case kDeletesFilterFirst:
|
||||
options.filter_deletes = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -768,6 +772,84 @@ TEST(DBTest, GetEncountersEmptyLevel) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
// KeyMayExist can lead to a few false positives, but not false negatives.
|
||||
// To make test deterministic, use a much larger number of bits per key-20 than
|
||||
// bits in the key, so that false positives are eliminated
|
||||
TEST(DBTest, KeyMayExist) {
|
||||
do {
|
||||
ReadOptions ropts;
|
||||
std::string value;
|
||||
Options options = CurrentOptions();
|
||||
options.filter_policy = NewBloomFilterPolicy(20);
|
||||
Reopen(&options);
|
||||
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, "a", &value));
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "a", "b"));
|
||||
bool value_found = false;
|
||||
ASSERT_TRUE(db_->KeyMayExist(ropts, "a", &value, &value_found));
|
||||
ASSERT_TRUE(value_found);
|
||||
ASSERT_EQ("b", value);
|
||||
|
||||
dbfull()->Flush(FlushOptions());
|
||||
value.clear();
|
||||
value_found = false;
|
||||
ASSERT_TRUE(db_->KeyMayExist(ropts, "a", &value, &value_found));
|
||||
ASSERT_TRUE(value_found);
|
||||
ASSERT_EQ("b", value);
|
||||
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), "a"));
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, "a", &value));
|
||||
|
||||
dbfull()->Flush(FlushOptions());
|
||||
dbfull()->CompactRange(nullptr, nullptr);
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, "a", &value));
|
||||
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), "c"));
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, "c", &value));
|
||||
|
||||
delete options.filter_policy;
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
// A delete is skipped for key if KeyMayExist(key) returns False
|
||||
// Tests Writebatch consistency and proper delete behaviour
|
||||
TEST(DBTest, FilterDeletes) {
|
||||
Options options = CurrentOptions();
|
||||
options.filter_policy = NewBloomFilterPolicy(20);
|
||||
options.filter_deletes = true;
|
||||
Reopen(&options);
|
||||
WriteBatch batch;
|
||||
|
||||
batch.Delete("a");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(AllEntriesFor("a"), "[ ]"); // Delete skipped
|
||||
batch.Clear();
|
||||
|
||||
batch.Put("a", "b");
|
||||
batch.Delete("a");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(Get("a"), "NOT_FOUND");
|
||||
ASSERT_EQ(AllEntriesFor("a"), "[ DEL, b ]"); // Delete issued
|
||||
batch.Clear();
|
||||
|
||||
batch.Delete("c");
|
||||
batch.Put("c", "d");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(Get("c"), "d");
|
||||
ASSERT_EQ(AllEntriesFor("c"), "[ d ]"); // Delete skipped
|
||||
batch.Clear();
|
||||
|
||||
dbfull()->Flush(FlushOptions()); // A stray Flush
|
||||
|
||||
batch.Delete("c");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(AllEntriesFor("c"), "[ DEL, d ]"); // Delete issued
|
||||
batch.Clear();
|
||||
|
||||
delete options.filter_policy;
|
||||
}
|
||||
|
||||
TEST(DBTest, IterEmpty) {
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
|
||||
@@ -1403,7 +1485,7 @@ class DeleteFilter : public CompactionFilter {
|
||||
|
||||
class ChangeFilter : public CompactionFilter {
|
||||
public:
|
||||
ChangeFilter(int argv) : argv_(argv) {}
|
||||
explicit ChangeFilter(int argv) : argv_(argv) {}
|
||||
|
||||
virtual bool Filter(int level, const Slice& key,
|
||||
const Slice& value, std::string* new_value,
|
||||
@@ -2970,6 +3052,15 @@ class ModelDB: public DB {
|
||||
Status::NotSupported("Not implemented."));
|
||||
return s;
|
||||
}
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) {
|
||||
if (value_found != nullptr) {
|
||||
*value_found = false;
|
||||
}
|
||||
return true; // Not Supported directly
|
||||
}
|
||||
virtual Iterator* NewIterator(const ReadOptions& options) {
|
||||
if (options.snapshot == nullptr) {
|
||||
KVMap* saved = new KVMap;
|
||||
@@ -3018,7 +3109,8 @@ class ModelDB: public DB {
|
||||
sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
virtual void CompactRange(const Slice* start, const Slice* end) {
|
||||
virtual void CompactRange(const Slice* start, const Slice* end,
|
||||
bool reduce_level ) {
|
||||
}
|
||||
|
||||
virtual int NumberLevels()
|
||||
@@ -3151,6 +3243,9 @@ static bool CompareIterators(int step,
|
||||
TEST(DBTest, Randomized) {
|
||||
Random rnd(test::RandomSeed());
|
||||
do {
|
||||
if (CurrentOptions().filter_deletes) {
|
||||
ChangeOptions(); // DBTest.Randomized not suited for filter_deletes
|
||||
}
|
||||
ModelDB model(CurrentOptions());
|
||||
const int N = 10000;
|
||||
const Snapshot* model_snap = nullptr;
|
||||
|
||||
+73
-48
@@ -3,6 +3,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/memtable.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/env.h"
|
||||
@@ -19,23 +22,28 @@ static Slice GetLengthPrefixedSlice(const char* data) {
|
||||
return Slice(p, len);
|
||||
}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp, int numlevel)
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
std::shared_ptr<MemTableRepFactory> table_factory,
|
||||
int numlevel,
|
||||
const Options& options)
|
||||
: comparator_(cmp),
|
||||
refs_(0),
|
||||
table_(comparator_, &arena_),
|
||||
arena_impl_(options.arena_block_size),
|
||||
table_(table_factory->CreateMemTableRep(comparator_, &arena_impl_)),
|
||||
flush_in_progress_(false),
|
||||
flush_completed_(false),
|
||||
file_number_(0),
|
||||
edit_(numlevel),
|
||||
first_seqno_(0),
|
||||
mem_logfile_number_(0) {
|
||||
}
|
||||
mem_logfile_number_(0) { }
|
||||
|
||||
MemTable::~MemTable() {
|
||||
assert(refs_ == 0);
|
||||
}
|
||||
|
||||
size_t MemTable::ApproximateMemoryUsage() { return arena_.MemoryUsage(); }
|
||||
size_t MemTable::ApproximateMemoryUsage() {
|
||||
return arena_impl_.ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
int MemTable::KeyComparator::operator()(const char* aptr, const char* bptr)
|
||||
const {
|
||||
@@ -57,24 +65,27 @@ static const char* EncodeKey(std::string* scratch, const Slice& target) {
|
||||
|
||||
class MemTableIterator: public Iterator {
|
||||
public:
|
||||
explicit MemTableIterator(MemTable::Table* table) : iter_(table) { }
|
||||
explicit MemTableIterator(MemTableRep* table)
|
||||
: iter_(table->GetIterator()) { }
|
||||
|
||||
virtual bool Valid() const { return iter_.Valid(); }
|
||||
virtual void Seek(const Slice& k) { iter_.Seek(EncodeKey(&tmp_, k)); }
|
||||
virtual void SeekToFirst() { iter_.SeekToFirst(); }
|
||||
virtual void SeekToLast() { iter_.SeekToLast(); }
|
||||
virtual void Next() { iter_.Next(); }
|
||||
virtual void Prev() { iter_.Prev(); }
|
||||
virtual Slice key() const { return GetLengthPrefixedSlice(iter_.key()); }
|
||||
virtual bool Valid() const { return iter_->Valid(); }
|
||||
virtual void Seek(const Slice& k) { iter_->Seek(EncodeKey(&tmp_, k)); }
|
||||
virtual void SeekToFirst() { iter_->SeekToFirst(); }
|
||||
virtual void SeekToLast() { iter_->SeekToLast(); }
|
||||
virtual void Next() { iter_->Next(); }
|
||||
virtual void Prev() { iter_->Prev(); }
|
||||
virtual Slice key() const {
|
||||
return GetLengthPrefixedSlice(iter_->key());
|
||||
}
|
||||
virtual Slice value() const {
|
||||
Slice key_slice = GetLengthPrefixedSlice(iter_.key());
|
||||
Slice key_slice = GetLengthPrefixedSlice(iter_->key());
|
||||
return GetLengthPrefixedSlice(key_slice.data() + key_slice.size());
|
||||
}
|
||||
|
||||
virtual Status status() const { return Status::OK(); }
|
||||
|
||||
private:
|
||||
MemTable::Table::Iterator iter_;
|
||||
std::shared_ptr<MemTableRep::Iterator> iter_;
|
||||
std::string tmp_; // For passing to EncodeKey
|
||||
|
||||
// No copying allowed
|
||||
@@ -83,7 +94,7 @@ class MemTableIterator: public Iterator {
|
||||
};
|
||||
|
||||
Iterator* MemTable::NewIterator() {
|
||||
return new MemTableIterator(&table_);
|
||||
return new MemTableIterator(table_.get());
|
||||
}
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
@@ -100,7 +111,7 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
const size_t encoded_len =
|
||||
VarintLength(internal_key_size) + internal_key_size +
|
||||
VarintLength(val_size) + val_size;
|
||||
char* buf = arena_.Allocate(encoded_len);
|
||||
char* buf = arena_impl_.Allocate(encoded_len);
|
||||
char* p = EncodeVarint32(buf, internal_key_size);
|
||||
memcpy(p, key.data(), key_size);
|
||||
p += key_size;
|
||||
@@ -109,7 +120,7 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
p = EncodeVarint32(p, val_size);
|
||||
memcpy(p, value.data(), val_size);
|
||||
assert((p + val_size) - buf == (unsigned)encoded_len);
|
||||
table_.Insert(buf);
|
||||
table_->Insert(buf);
|
||||
|
||||
// The first sequence number inserted into the memtable
|
||||
assert(first_seqno_ == 0 || s > first_seqno_);
|
||||
@@ -119,22 +130,20 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
}
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options) {
|
||||
std::deque<std::string>* operands, const Options& options) {
|
||||
Slice memkey = key.memtable_key();
|
||||
Table::Iterator iter(&table_);
|
||||
iter.Seek(memkey.data());
|
||||
|
||||
bool merge_in_progress = false;
|
||||
std::string operand;
|
||||
if (s->IsMergeInProgress()) {
|
||||
swap(*value, operand);
|
||||
merge_in_progress = true;
|
||||
}
|
||||
std::shared_ptr<MemTableRep::Iterator> iter(table_.get()->GetIterator());
|
||||
iter->Seek(memkey.data());
|
||||
|
||||
// It is the caller's responsibility to allocate/delete operands list
|
||||
assert(operands != nullptr);
|
||||
|
||||
bool merge_in_progress = s->IsMergeInProgress();
|
||||
auto merge_operator = options.merge_operator;
|
||||
auto logger = options.info_log;
|
||||
for (; iter.Valid(); iter.Next()) {
|
||||
std::string merge_result;
|
||||
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
// entry format is:
|
||||
// klength varint32
|
||||
// userkey char[klength-8]
|
||||
@@ -144,7 +153,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
// Check that it belongs to same user key. We do not check the
|
||||
// sequence number since the Seek() call above should have skipped
|
||||
// all entries with overly large sequence numbers.
|
||||
const char* entry = iter.key();
|
||||
const char* entry = iter->key();
|
||||
uint32_t key_length;
|
||||
const char* key_ptr = GetVarint32Ptr(entry, entry+5, &key_length);
|
||||
if (comparator_.comparator.user_comparator()->Compare(
|
||||
@@ -155,36 +164,51 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
case kTypeValue: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
*s = Status::OK();
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), &v, operand,
|
||||
value, logger.get());
|
||||
assert(merge_operator);
|
||||
if (!merge_operator->Merge(key.user_key(), &v, *operands,
|
||||
value, logger.get())) {
|
||||
*s = Status::Corruption("Error: Could not perform merge.");
|
||||
}
|
||||
} else {
|
||||
value->assign(v.data(), v.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case kTypeMerge: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), &v, operand,
|
||||
value, logger.get());
|
||||
swap(*value, operand);
|
||||
} else {
|
||||
assert(merge_operator);
|
||||
merge_in_progress = true;
|
||||
operand.assign(v.data(), v.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kTypeDeletion: {
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), nullptr, operand,
|
||||
value, logger.get());
|
||||
assert(merge_operator);
|
||||
*s = Status::OK();
|
||||
if (!merge_operator->Merge(key.user_key(), nullptr, *operands,
|
||||
value, logger.get())) {
|
||||
*s = Status::Corruption("Error: Could not perform merge.");
|
||||
}
|
||||
} else {
|
||||
*s = Status::NotFound(Slice());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case kTypeMerge: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
merge_in_progress = true;
|
||||
operands->push_front(v.ToString());
|
||||
while(operands->size() >= 2) {
|
||||
// Attempt to associative merge. (Returns true if successful)
|
||||
if (merge_operator->PartialMerge(key.user_key(),
|
||||
Slice((*operands)[0]),
|
||||
Slice((*operands)[1]),
|
||||
&merge_result,
|
||||
logger.get())) {
|
||||
operands->pop_front();
|
||||
swap(operands->front(), merge_result);
|
||||
} else {
|
||||
// Stack them because user can't associative merge
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// exit loop if user key does not match
|
||||
@@ -192,8 +216,9 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
}
|
||||
}
|
||||
|
||||
// No change to value, since we have not yet found a Put/Delete
|
||||
|
||||
if (merge_in_progress) {
|
||||
swap(*value, operand);
|
||||
*s = Status::MergeInProgress("");
|
||||
}
|
||||
return false;
|
||||
|
||||
+20
-17
@@ -6,24 +6,35 @@
|
||||
#define STORAGE_LEVELDB_DB_MEMTABLE_H_
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include "leveldb/db.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/skiplist.h"
|
||||
#include "db/version_set.h"
|
||||
#include "util/arena.h"
|
||||
#include "leveldb/memtablerep.h"
|
||||
#include "util/arena_impl.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class InternalKeyComparator;
|
||||
class Mutex;
|
||||
class MemTableIterator;
|
||||
|
||||
class MemTable {
|
||||
public:
|
||||
struct KeyComparator : public MemTableRep::KeyComparator {
|
||||
const InternalKeyComparator comparator;
|
||||
explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) { }
|
||||
virtual int operator()(const char* a, const char* b) const;
|
||||
};
|
||||
|
||||
// MemTables are reference counted. The initial reference count
|
||||
// is zero and the caller must call Ref() at least once.
|
||||
explicit MemTable(const InternalKeyComparator& comparator,
|
||||
int numlevel = 7);
|
||||
explicit MemTable(
|
||||
const InternalKeyComparator& comparator,
|
||||
std::shared_ptr<MemTableRepFactory> table_factory,
|
||||
int numlevel = 7,
|
||||
const Options& options = Options());
|
||||
|
||||
// Increase reference count.
|
||||
void Ref() { ++refs_; }
|
||||
@@ -64,11 +75,11 @@ class MemTable {
|
||||
// in *status and return true.
|
||||
// If memtable contains Merge operation as the most recent entry for a key,
|
||||
// and the merge process does not stop (not reaching a value or delete),
|
||||
// store the current merged result in value and MergeInProgress in s.
|
||||
// return false
|
||||
// prepend the current merge operand to *operands.
|
||||
// store MergeInProgress in s, and return false.
|
||||
// Else, return false.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options);
|
||||
std::deque<std::string>* operands, const Options& options);
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
@@ -87,22 +98,14 @@ class MemTable {
|
||||
|
||||
private:
|
||||
~MemTable(); // Private since only Unref() should be used to delete it
|
||||
|
||||
struct KeyComparator {
|
||||
const InternalKeyComparator comparator;
|
||||
explicit KeyComparator(const InternalKeyComparator& c) : comparator(c) { }
|
||||
int operator()(const char* a, const char* b) const;
|
||||
};
|
||||
friend class MemTableIterator;
|
||||
friend class MemTableBackwardIterator;
|
||||
friend class MemTableList;
|
||||
|
||||
typedef SkipList<const char*, KeyComparator> Table;
|
||||
|
||||
KeyComparator comparator_;
|
||||
int refs_;
|
||||
Arena arena_;
|
||||
Table table_;
|
||||
ArenaImpl arena_impl_;
|
||||
shared_ptr<MemTableRep> table_;
|
||||
|
||||
// These are used to manage memtable flushes to storage
|
||||
bool flush_in_progress_; // started the flush
|
||||
|
||||
+4
-2
@@ -193,11 +193,13 @@ size_t MemTableList::ApproximateMemoryUsage() {
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
// Operands stores the list of merge operations to apply, so far.
|
||||
bool MemTableList::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options ) {
|
||||
std::deque<std::string>* operands,
|
||||
const Options& options) {
|
||||
for (list<MemTable*>::iterator it = memlist_.begin();
|
||||
it != memlist_.end(); ++it) {
|
||||
if ((*it)->Get(key, value, s, options)) {
|
||||
if ((*it)->Get(key, value, s, operands, options)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,10 +5,10 @@
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include "leveldb/db.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/skiplist.h"
|
||||
#include "util/arena.h"
|
||||
#include "memtable.h"
|
||||
|
||||
namespace leveldb {
|
||||
@@ -71,7 +71,7 @@ class MemTableList {
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options);
|
||||
std::deque<std::string>* operands, const Options& options);
|
||||
|
||||
// Returns the list of underlying memtables.
|
||||
void GetMemTables(std::vector<MemTable*>* list);
|
||||
|
||||
+118
-45
@@ -10,23 +10,36 @@ namespace leveldb {
|
||||
|
||||
// PRE: iter points to the first merge type entry
|
||||
// POST: iter points to the first entry beyond the merge process (or the end)
|
||||
// key_, value_ are updated to reflect the merge result
|
||||
// keys_, operands_ are updated to reflect the merge result.
|
||||
// keys_ stores the list of keys encountered while merging.
|
||||
// operands_ stores the list of merge operands encountered while merging.
|
||||
// keys_[i] corresponds to operands_[i] for each i.
|
||||
void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
bool at_bottom) {
|
||||
// get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
key_.assign(iter->key().data(), iter->key().size());
|
||||
// we need to parse the internal key again as the parsed key is
|
||||
// Get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
// Also maintain the list of merge operands seen.
|
||||
keys_.clear();
|
||||
operands_.clear();
|
||||
keys_.push_front(iter->key().ToString());
|
||||
operands_.push_front(iter->value().ToString());
|
||||
|
||||
success_ = false; // Will become true if we hit Put/Delete or bottom
|
||||
|
||||
// We need to parse the internal key again as the parsed key is
|
||||
// backed by the internal key!
|
||||
ParsedInternalKey orig_ikey;
|
||||
// Assume no internal key corruption as it has been successfully parsed
|
||||
// by the caller.
|
||||
// TODO: determine a good alternative of assert (exception?)
|
||||
ParseInternalKey(key_, &orig_ikey);
|
||||
std::string operand(iter->value().data(), iter->value().size());
|
||||
// Invariant: keys_.back() will not change. Hence, orig_ikey is always valid.
|
||||
ParsedInternalKey orig_ikey;
|
||||
ParseInternalKey(keys_.back(), &orig_ikey);
|
||||
|
||||
bool hit_the_next_user_key = false;
|
||||
ParsedInternalKey ikey;
|
||||
std::string merge_result; // Temporary value for merge results
|
||||
for (iter->Next(); iter->Valid(); iter->Next()) {
|
||||
assert(operands_.size() >= 1); // Should be invariants!
|
||||
assert(keys_.size() == operands_.size());
|
||||
|
||||
if (!ParseInternalKey(iter->key(), &ikey)) {
|
||||
// stop at corrupted key
|
||||
if (assert_valid_internal_key_) {
|
||||
@@ -46,32 +59,54 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
break;
|
||||
}
|
||||
|
||||
// At this point we are guaranteed that we need to process this key.
|
||||
|
||||
if (kTypeDeletion == ikey.type) {
|
||||
// hit a delete
|
||||
// => merge nullptr with operand
|
||||
// => change the entry type to kTypeValue
|
||||
// We are done!
|
||||
user_merge_operator_->Merge(ikey.user_key, nullptr, operand,
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
// move iter to the next entry
|
||||
// => merge nullptr with operands_
|
||||
// => store result in operands_.back() (and update keys_.back())
|
||||
// => change the entry type to kTypeValue for keys_.back()
|
||||
// We are done! Return a success if the merge passes.
|
||||
success_ = user_merge_operator_->Merge(ikey.user_key, nullptr,
|
||||
operands_, &merge_result,
|
||||
logger_);
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
// if nothing went wrong (i.e.: no operand corruption on disk)
|
||||
if (success_) {
|
||||
std::string& key = keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key[0], key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
swap(operands_.back(), merge_result);
|
||||
}
|
||||
|
||||
// move iter to the next entry (before doing anything else)
|
||||
iter->Next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeValue == ikey.type) {
|
||||
// hit a put
|
||||
// => merge the put value with operand
|
||||
// => change the entry type to kTypeValue
|
||||
// We are done!
|
||||
// => merge the put value with operands_
|
||||
// => store result in operands_.back() (and update keys_.back())
|
||||
// => change the entry type to kTypeValue for keys_.back()
|
||||
// We are done! Success!
|
||||
const Slice value = iter->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, Slice(operand),
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
success_ = user_merge_operator_->Merge(ikey.user_key, &value,
|
||||
operands_, &merge_result,
|
||||
logger_);
|
||||
|
||||
// We store the result in keys_.back() and operands_.back()
|
||||
// if nothing went wrong (i.e.: no operand corruption on disk)
|
||||
if (success_) {
|
||||
std::string& key = keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key[0], key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
swap(operands_.back(), merge_result);
|
||||
}
|
||||
|
||||
// move iter to the next entry
|
||||
iter->Next();
|
||||
return;
|
||||
@@ -79,35 +114,73 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
|
||||
if (kTypeMerge == ikey.type) {
|
||||
// hit a merge
|
||||
// => merge the value with operand.
|
||||
// => put the result back to operand and continue
|
||||
const Slice value = iter->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, operand,
|
||||
&value_, logger_);
|
||||
swap(value_, operand);
|
||||
// => merge the operand into the front of the operands_ list
|
||||
// => use the user's associative merge function to determine how.
|
||||
// => then continue because we haven't yet seen a Put/Delete.
|
||||
assert(!operands_.empty()); // Should have at least one element in it
|
||||
|
||||
keys_.push_front(iter->key().ToString());
|
||||
operands_.push_front(iter->value().ToString());
|
||||
while (operands_.size() >= 2) {
|
||||
// Returns false when the merge_operator can no longer process it
|
||||
if (user_merge_operator_->PartialMerge(ikey.user_key,
|
||||
Slice(operands_[0]),
|
||||
Slice(operands_[1]),
|
||||
&merge_result,
|
||||
logger_)) {
|
||||
// Merging of operands (associative merge) was successful.
|
||||
// Replace these frontmost two operands with the merge result
|
||||
keys_.pop_front();
|
||||
operands_.pop_front();
|
||||
swap(operands_.front(), merge_result);
|
||||
} else {
|
||||
// Merging of operands (associative merge) returned false.
|
||||
// The user merge_operator does not know how to merge these operands.
|
||||
// So we just stack them up until we find a Put/Delete or end of key.
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// We have seen the root history of this key if we are at the
|
||||
// bottem level and exhausted all internal keys of this user key
|
||||
// We are sure we have seen this key's entire history if we are at the
|
||||
// last level and exhausted all internal keys of this user key.
|
||||
// NOTE: !iter->Valid() does not necessarily mean we hit the
|
||||
// beginning of a user key, as versions of a user key might be
|
||||
// split into multiple files and some files might not be included
|
||||
// in the merge.
|
||||
bool seen_the_beginning = hit_the_next_user_key && at_bottom;
|
||||
|
||||
if (seen_the_beginning) {
|
||||
// split into multiple files (even files on the same level)
|
||||
// and some files might not be included in the compaction/merge.
|
||||
//
|
||||
// There are also cases where we have seen the root of history of this
|
||||
// key without being sure of it. Then, we simply miss the opportunity
|
||||
// to combine the keys. Since VersionSet::SetupOtherInputs() always makes
|
||||
// sure that all merge-operands on the same level get compacted together,
|
||||
// this will simply lead to these merge operands moving to the next level.
|
||||
//
|
||||
// So, we only perform the following logic (to merge all operands together
|
||||
// without a Put/Delete) if we are certain that we have seen the end of key.
|
||||
bool surely_seen_the_beginning = hit_the_next_user_key && at_bottom;
|
||||
if (surely_seen_the_beginning) {
|
||||
// do a final merge with nullptr as the existing value and say
|
||||
// bye to the merge type (it's now converted to a Put)
|
||||
assert(kTypeMerge == orig_ikey.type);
|
||||
user_merge_operator_->Merge(orig_ikey.user_key, nullptr, operand,
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
} else {
|
||||
swap(value_, operand);
|
||||
assert(operands_.size() >= 1);
|
||||
assert(operands_.size() == keys_.size());
|
||||
success_ = user_merge_operator_->Merge(ikey.user_key, nullptr,
|
||||
operands_, &merge_result,
|
||||
logger_);
|
||||
|
||||
if (success_) {
|
||||
std::string& key = keys_.back(); // The original key encountered
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key[0], key.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
|
||||
// The final value() is always stored in operands_.back()
|
||||
swap(operands_.back(),merge_result);
|
||||
} else {
|
||||
// Do nothing if not success_. Leave keys() and operands() as they are.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+42
-9
@@ -4,6 +4,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include <string>
|
||||
#include <deque>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -21,7 +22,10 @@ class MergeHelper {
|
||||
: user_comparator_(user_comparator),
|
||||
user_merge_operator_(user_merge_operator),
|
||||
logger_(logger),
|
||||
assert_valid_internal_key_(assert_valid_internal_key) {}
|
||||
assert_valid_internal_key_(assert_valid_internal_key),
|
||||
keys_(),
|
||||
operands_(),
|
||||
success_(false) {}
|
||||
|
||||
// Merge entries until we hit
|
||||
// - a corrupted key
|
||||
@@ -39,12 +43,40 @@ class MergeHelper {
|
||||
bool at_bottom = false);
|
||||
|
||||
// Query the merge result
|
||||
// These are valid until the next MergeUtil call
|
||||
// IMPORTANT: the key type could change after the MergeUntil call.
|
||||
// Put/Delete + Merge + ... + Merge => Put
|
||||
// Merge + ... + Merge => Merge
|
||||
Slice key() { return Slice(key_); }
|
||||
Slice value() { return Slice(value_); }
|
||||
// These are valid until the next MergeUntil call
|
||||
// If the merging was successful:
|
||||
// - IsSuccess() will be true
|
||||
// - key() will have the latest sequence number of the merges.
|
||||
// The type will be Put or Merge. See IMPORTANT 1 note, below.
|
||||
// - value() will be the result of merging all the operands together
|
||||
// - The user should ignore keys() and values().
|
||||
//
|
||||
// IMPORTANT 1: the key type could change after the MergeUntil call.
|
||||
// Put/Delete + Merge + ... + Merge => Put
|
||||
// Merge + ... + Merge => Merge
|
||||
//
|
||||
// If the merge operator is not associative, and if a Put/Delete is not found
|
||||
// then the merging will be unsuccessful. In this case:
|
||||
// - IsSuccess() will be false
|
||||
// - keys() contains the list of internal keys seen in order of iteration.
|
||||
// - values() contains the list of values (merges) seen in the same order.
|
||||
// values() is parallel to keys() so that the first entry in
|
||||
// keys() is the key associated with the first entry in values()
|
||||
// and so on. These lists will be the same length.
|
||||
// All of these pairs will be merges over the same user key.
|
||||
// See IMPORTANT 2 note below.
|
||||
// - The user should ignore key() and value().
|
||||
//
|
||||
// IMPORTANT 2: The entries were traversed in order from BACK to FRONT.
|
||||
// So keys().back() was the first key seen by iterator.
|
||||
// TODO: Re-style this comment to be like the first one
|
||||
bool IsSuccess() { return success_; }
|
||||
Slice key() { assert(success_); return Slice(keys_.back()); }
|
||||
Slice value() { assert(success_); return Slice(operands_.back()); }
|
||||
const std::deque<std::string>& keys() { assert(!success_); return keys_; }
|
||||
const std::deque<std::string>& values() {
|
||||
assert(!success_); return operands_;
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* user_comparator_;
|
||||
@@ -55,8 +87,9 @@ class MergeHelper {
|
||||
|
||||
// the scratch area that holds the result of MergeUntil
|
||||
// valid up to the next MergeUntil call
|
||||
std::string key_;
|
||||
std::string value_;
|
||||
std::deque<std::string> keys_; // Keeps track of the sequence of keys seen
|
||||
std::deque<std::string> operands_; // Parallel with keys_; stores the values
|
||||
bool success_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Back-end implementation details specific to the Merge Operator.
|
||||
*
|
||||
* @author Deon Nicholas (dnicholas@fb.com)
|
||||
* Copyright 2013 Facebook
|
||||
*/
|
||||
|
||||
#include "leveldb/merge_operator.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Given a "real" merge from the library, call the user's
|
||||
// associative merge function one-by-one on each of the operands.
|
||||
// NOTE: It is assumed that the client's merge-operator will handle any errors.
|
||||
bool AssociativeMergeOperator::Merge(
|
||||
const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const std::deque<std::string>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const {
|
||||
|
||||
// Simply loop through the operands
|
||||
Slice temp_existing;
|
||||
std::string temp_value;
|
||||
for (const auto& operand : operand_list) {
|
||||
Slice value(operand);
|
||||
if (!Merge(key, existing_value, value, &temp_value, logger)) {
|
||||
return false;
|
||||
}
|
||||
swap(temp_value, *new_value);
|
||||
temp_existing = Slice(*new_value);
|
||||
existing_value = &temp_existing;
|
||||
}
|
||||
|
||||
// The result will be in *new_value. All merges succeeded.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call the user defined simple merge on the operands;
|
||||
// NOTE: It is assumed that the client's merge-operator will handle any errors.
|
||||
bool AssociativeMergeOperator::PartialMerge(
|
||||
const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const {
|
||||
|
||||
return Merge(key, &left_operand, right_operand, new_value, logger);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
+25
-7
@@ -8,19 +8,31 @@
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "util/testharness.h"
|
||||
#include "utilities/utility_db.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace leveldb;
|
||||
|
||||
auto mergeOperator = MergeOperators::CreateUInt64AddOperator();
|
||||
|
||||
std::shared_ptr<DB> OpenDb() {
|
||||
std::shared_ptr<DB> OpenDb(const string& dbname, const bool ttl = false) {
|
||||
DB* db;
|
||||
StackableDB* sdb;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = mergeOperator.get();
|
||||
Status s = DB::Open(options, "/tmp/testdb", &db);
|
||||
Status s;
|
||||
DestroyDB(dbname, Options());
|
||||
if (ttl) {
|
||||
cout << "Opening database with TTL\n";
|
||||
s = UtilityDB::OpenTtlDB(options, dbname, &sdb);
|
||||
db = sdb;
|
||||
} else {
|
||||
s = DB::Open(options, dbname, &db);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
cerr << s.ToString() << endl;
|
||||
assert(false);
|
||||
@@ -45,7 +57,7 @@ class Counters {
|
||||
uint64_t default_;
|
||||
|
||||
public:
|
||||
Counters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
explicit Counters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
: db_(db),
|
||||
put_option_(),
|
||||
get_option_(),
|
||||
@@ -143,7 +155,7 @@ class MergeBasedCounters : public Counters {
|
||||
WriteOptions merge_option_; // for merge
|
||||
|
||||
public:
|
||||
MergeBasedCounters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
explicit MergeBasedCounters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
: Counters(db, defaultCount),
|
||||
merge_option_() {
|
||||
}
|
||||
@@ -227,9 +239,8 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
auto db = OpenDb();
|
||||
void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
auto db = OpenDb(dbname, use_ttl);
|
||||
|
||||
{
|
||||
cout << "Test read-modify-write counters... \n";
|
||||
@@ -249,5 +260,12 @@ int main(int argc, char *argv[]) {
|
||||
testCounters(counters, db.get(), compact);
|
||||
}
|
||||
|
||||
DestroyDB(dbname, Options());
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//TODO: Make this test like a general rocksdb unit-test
|
||||
runTest(argc, test::TmpDir() + "/merge_testdb");
|
||||
runTest(argc, test::TmpDir() + "/merge_testdbttl", true); // Run test on TTL database
|
||||
return 0;
|
||||
}
|
||||
+2
-1
@@ -191,7 +191,8 @@ class Repairer {
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
MemTable* mem = new MemTable(icmp_, options_.num_levels);
|
||||
MemTable* mem = new MemTable(icmp_, options_.memtable_factory,
|
||||
options_.num_levels);
|
||||
mem->Ref();
|
||||
int counter = 0;
|
||||
while (reader.ReadRecord(&record, &scratch)) {
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include "port/port.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Arena;
|
||||
|
||||
template<typename Key, class Comparator>
|
||||
class SkipList {
|
||||
private:
|
||||
|
||||
+7
-7
@@ -5,7 +5,7 @@
|
||||
#include "db/skiplist.h"
|
||||
#include <set>
|
||||
#include "leveldb/env.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/arena_impl.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
@@ -29,9 +29,9 @@ struct TestComparator {
|
||||
class SkipTest { };
|
||||
|
||||
TEST(SkipTest, Empty) {
|
||||
Arena arena;
|
||||
ArenaImpl arena_impl;
|
||||
TestComparator cmp;
|
||||
SkipList<Key, TestComparator> list(cmp, &arena);
|
||||
SkipList<Key, TestComparator> list(cmp, &arena_impl);
|
||||
ASSERT_TRUE(!list.Contains(10));
|
||||
|
||||
SkipList<Key, TestComparator>::Iterator iter(&list);
|
||||
@@ -49,9 +49,9 @@ TEST(SkipTest, InsertAndLookup) {
|
||||
const int R = 5000;
|
||||
Random rnd(1000);
|
||||
std::set<Key> keys;
|
||||
Arena arena;
|
||||
ArenaImpl arena_impl;
|
||||
TestComparator cmp;
|
||||
SkipList<Key, TestComparator> list(cmp, &arena);
|
||||
SkipList<Key, TestComparator> list(cmp, &arena_impl);
|
||||
for (int i = 0; i < N; i++) {
|
||||
Key key = rnd.Next() % R;
|
||||
if (keys.insert(key).second) {
|
||||
@@ -204,14 +204,14 @@ class ConcurrentTest {
|
||||
// Current state of the test
|
||||
State current_;
|
||||
|
||||
Arena arena_;
|
||||
ArenaImpl arena_impl_;
|
||||
|
||||
// SkipList is not protected by mu_. We just use a single writer
|
||||
// thread to modify it.
|
||||
SkipList<Key, TestComparator> list_;
|
||||
|
||||
public:
|
||||
ConcurrentTest() : list_(TestComparator(), &arena_) { }
|
||||
ConcurrentTest() : list_(TestComparator(), &arena_impl_) { }
|
||||
|
||||
// REQUIRES: External synchronization
|
||||
void WriteStep(Random* rnd) {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#ifndef STORAGE_LEVELDB_DB_SKIPLISTREP_H_
|
||||
#define STORAGE_LEVELDB_DB_SKIPLISTREP_H_
|
||||
|
||||
#include "leveldb/memtablerep.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/skiplist.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Arena;
|
||||
|
||||
class SkipListRep : public MemTableRep {
|
||||
SkipList<const char*, MemTableRep::KeyComparator&> skip_list_;
|
||||
public:
|
||||
explicit SkipListRep(MemTableRep::KeyComparator& compare, Arena* arena)
|
||||
: skip_list_(compare, arena) {
|
||||
}
|
||||
|
||||
// Insert key into the list.
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
virtual void Insert(const char* key) {
|
||||
skip_list_.Insert(key);
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
virtual bool Contains(const char* key) const {
|
||||
return skip_list_.Contains(key);
|
||||
}
|
||||
|
||||
virtual ~SkipListRep() { }
|
||||
|
||||
// Iteration over the contents of a skip list
|
||||
class Iterator : public MemTableRep::Iterator {
|
||||
SkipList<const char*, MemTableRep::KeyComparator&>::Iterator iter_;
|
||||
public:
|
||||
// Initialize an iterator over the specified list.
|
||||
// The returned iterator is not valid.
|
||||
explicit Iterator(
|
||||
const SkipList<const char*, MemTableRep::KeyComparator&>* list
|
||||
) : iter_(list) { }
|
||||
|
||||
virtual ~Iterator() { }
|
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const {
|
||||
return iter_.Valid();
|
||||
}
|
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const {
|
||||
return iter_.key();
|
||||
}
|
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() {
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() {
|
||||
iter_.Prev();
|
||||
}
|
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
virtual void Seek(const char* target) {
|
||||
iter_.Seek(target);
|
||||
}
|
||||
|
||||
// Position at the first entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
virtual void SeekToFirst() {
|
||||
iter_.SeekToFirst();
|
||||
}
|
||||
|
||||
// Position at the last entry in list.
|
||||
// Final state of iterator is Valid() iff list is not empty.
|
||||
virtual void SeekToLast() {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
};
|
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator() {
|
||||
return std::shared_ptr<MemTableRep::Iterator>(
|
||||
new SkipListRep::Iterator(&skip_list_)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class SkipListFactory : public MemTableRepFactory {
|
||||
public:
|
||||
virtual std::shared_ptr<MemTableRep> CreateMemTableRep (
|
||||
MemTableRep::KeyComparator& compare, Arena* arena) {
|
||||
return std::shared_ptr<MemTableRep>(new SkipListRep(compare, arena));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // STORAGE_LEVELDB_DB_SKIPLISTREP_H_
|
||||
+16
-6
@@ -39,15 +39,19 @@ TableCache::~TableCache() {
|
||||
|
||||
Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
Cache::Handle** handle, bool* tableIO) {
|
||||
Cache::Handle** handle, bool* table_io,
|
||||
const bool no_io) {
|
||||
Status s;
|
||||
char buf[sizeof(file_number)];
|
||||
EncodeFixed64(buf, file_number);
|
||||
Slice key(buf, sizeof(buf));
|
||||
*handle = cache_->Lookup(key);
|
||||
if (*handle == nullptr) {
|
||||
if (tableIO != nullptr) {
|
||||
*tableIO = true; // we had to do IO from storage
|
||||
if (no_io) { // Dont do IO and return a not-found status
|
||||
return Status::NotFound("Table not found in table_cache, no_io is set");
|
||||
}
|
||||
if (table_io != nullptr) {
|
||||
*table_io = true; // we had to do IO from storage
|
||||
}
|
||||
std::string fname = TableFileName(dbname_, file_number);
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
@@ -112,15 +116,21 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
const Slice& k,
|
||||
void* arg,
|
||||
bool (*saver)(void*, const Slice&, const Slice&, bool),
|
||||
bool* tableIO) {
|
||||
bool* table_io,
|
||||
void (*mark_key_may_exist)(void*),
|
||||
const bool no_io) {
|
||||
Cache::Handle* handle = nullptr;
|
||||
Status s = FindTable(storage_options_, file_number, file_size,
|
||||
&handle, tableIO);
|
||||
&handle, table_io, no_io);
|
||||
if (s.ok()) {
|
||||
Table* t =
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
s = t->InternalGet(options, k, arg, saver);
|
||||
s = t->InternalGet(options, k, arg, saver, mark_key_may_exist, no_io);
|
||||
cache_->Release(handle);
|
||||
} else if (no_io && s.IsNotFound()) {
|
||||
// Couldnt find Table in cache but treat as kFound if no_io set
|
||||
(*mark_key_may_exist)(arg);
|
||||
return Status::OK();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
+6
-4
@@ -48,7 +48,9 @@ class TableCache {
|
||||
const Slice& k,
|
||||
void* arg,
|
||||
bool (*handle_result)(void*, const Slice&, const Slice&, bool),
|
||||
bool* tableIO);
|
||||
bool* table_io,
|
||||
void (*mark_key_may_exist)(void*) = nullptr,
|
||||
const bool no_io = false);
|
||||
|
||||
// Evict any entry for the specified file number
|
||||
void Evict(uint64_t file_number);
|
||||
@@ -60,9 +62,9 @@ class TableCache {
|
||||
const EnvOptions& storage_options_;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
|
||||
Status FindTable(const EnvOptions& toptions,
|
||||
uint64_t file_number, uint64_t file_size, Cache::Handle**,
|
||||
bool* tableIO = nullptr);
|
||||
Status FindTable(const EnvOptions& toptions, uint64_t file_number,
|
||||
uint64_t file_size, Cache::Handle**, bool* table_io=nullptr,
|
||||
const bool no_io = false);
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
+262
-87
@@ -232,20 +232,41 @@ enum SaverState {
|
||||
kFound,
|
||||
kDeleted,
|
||||
kCorrupt,
|
||||
kMerge // value contains the current merge result (the operand)
|
||||
kMerge // saver contains the current merge result (the operands)
|
||||
};
|
||||
struct Saver {
|
||||
SaverState state;
|
||||
const Comparator* ucmp;
|
||||
Slice user_key;
|
||||
bool* value_found; // Is value set correctly? Used by KeyMayExist
|
||||
std::string* value;
|
||||
const MergeOperator* merge_operator;
|
||||
std::deque<std::string>* merge_operands; // the merge operations encountered
|
||||
Logger* logger;
|
||||
bool didIO; // did we do any disk io?
|
||||
};
|
||||
}
|
||||
|
||||
// Called from TableCache::Get and InternalGet when file/block in which key may
|
||||
// exist are not there in TableCache/BlockCache respectively. In this case we
|
||||
// can't guarantee that key does not exist and are not permitted to do IO to be
|
||||
// certain.Set the status=kFound and value_found=false to let the caller know
|
||||
// that key may exist but is not there in memory
|
||||
static void MarkKeyMayExist(void* arg) {
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
s->state = kFound;
|
||||
if (s->value_found != nullptr) {
|
||||
*(s->value_found) = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool SaveValue(void* arg, const Slice& ikey, const Slice& v, bool didIO){
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
std::deque<std::string>* const ops = s->merge_operands; // shorter alias
|
||||
std::string merge_result; // temporary area for merge results later
|
||||
|
||||
assert(s != nullptr && ops != nullptr);
|
||||
|
||||
ParsedInternalKey parsed_key;
|
||||
// TODO: didIO and Merge?
|
||||
s->didIO = didIO;
|
||||
@@ -254,49 +275,57 @@ static bool SaveValue(void* arg, const Slice& ikey, const Slice& v, bool didIO){
|
||||
s->state = kCorrupt;
|
||||
} else {
|
||||
if (s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) {
|
||||
// Key matches. Process it
|
||||
switch (parsed_key.type) {
|
||||
case kTypeValue:
|
||||
if (kNotFound == s->state) {
|
||||
s->state = kFound;
|
||||
s->value->assign(v.data(), v.size());
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, &v, operand,
|
||||
s->value, s->logger);
|
||||
assert(s->merge_operator != nullptr);
|
||||
s->state = kFound;
|
||||
if (!s->merge_operator->Merge(s->user_key, &v, *ops,
|
||||
s->value, s->logger)) {
|
||||
s->state = kCorrupt;
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
s->state = kFound;
|
||||
return false;
|
||||
|
||||
case kTypeMerge:
|
||||
if (kNotFound == s->state) {
|
||||
s->state = kMerge;
|
||||
s->value->assign(v.data(), v.size());
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, &v, operand,
|
||||
s->value, s->logger);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return true;
|
||||
|
||||
case kTypeDeletion:
|
||||
if (kNotFound == s->state) {
|
||||
s->state = kDeleted;
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, nullptr, operand,
|
||||
s->value, s->logger);
|
||||
s->state = kFound;
|
||||
if (!s->merge_operator->Merge(s->user_key, nullptr, *ops,
|
||||
s->value, s->logger)) {
|
||||
s->state = kCorrupt;
|
||||
}
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return false;
|
||||
|
||||
case kTypeMerge:
|
||||
assert(s->state == kNotFound || s->state == kMerge);
|
||||
s->state = kMerge;
|
||||
ops->push_front(v.ToString());
|
||||
while (ops->size() >= 2) {
|
||||
// Attempt to merge operands together via user associateive merge
|
||||
if (s->merge_operator->PartialMerge(s->user_key,
|
||||
Slice((*ops)[0]),
|
||||
Slice((*ops)[1]),
|
||||
&merge_result,
|
||||
s->logger)) {
|
||||
ops->pop_front();
|
||||
swap(ops->front(), merge_result);
|
||||
} else {
|
||||
// Associative merge returns false ==> stack the operands
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,9 +355,12 @@ Version::Version(VersionSet* vset, uint64_t version_number)
|
||||
void Version::Get(const ReadOptions& options,
|
||||
const LookupKey& k,
|
||||
std::string* value,
|
||||
Status *status,
|
||||
Status* status,
|
||||
std::deque<std::string>* operands,
|
||||
GetStats* stats,
|
||||
const Options& db_options) {
|
||||
const Options& db_options,
|
||||
const bool no_io,
|
||||
bool* value_found) {
|
||||
Slice ikey = k.internal_key();
|
||||
Slice user_key = k.user_key();
|
||||
const Comparator* ucmp = vset_->icmp_.user_comparator();
|
||||
@@ -337,12 +369,17 @@ void Version::Get(const ReadOptions& options,
|
||||
auto logger = db_options.info_log;
|
||||
|
||||
assert(status->ok() || status->IsMergeInProgress());
|
||||
if (no_io) {
|
||||
assert(status->ok());
|
||||
}
|
||||
Saver saver;
|
||||
saver.state = status->ok()? kNotFound : kMerge;
|
||||
saver.ucmp = ucmp;
|
||||
saver.user_key = user_key;
|
||||
saver.value_found = value_found;
|
||||
saver.value = value;
|
||||
saver.merge_operator = merge_operator;
|
||||
saver.merge_operands = operands;
|
||||
saver.logger = logger.get();
|
||||
saver.didIO = false;
|
||||
|
||||
@@ -353,58 +390,72 @@ void Version::Get(const ReadOptions& options,
|
||||
|
||||
// We can search level-by-level since entries never hop across
|
||||
// levels. Therefore we are guaranteed that if we find data
|
||||
// in an smaller level, later levels are irrelevant.
|
||||
std::vector<FileMetaData*> tmp;
|
||||
FileMetaData* tmp2;
|
||||
// in an smaller level, later levels are irrelevant (unless we
|
||||
// are MergeInProgress).
|
||||
std::vector<FileMetaData*> important_files;
|
||||
for (int level = 0; level < vset_->NumberLevels(); level++) {
|
||||
size_t num_files = files_[level].size();
|
||||
if (num_files == 0) continue;
|
||||
|
||||
// Get the list of files to search in this level
|
||||
FileMetaData* const* files = &files_[level][0];
|
||||
if (level == 0) {
|
||||
// Level-0 files may overlap each other. Find all files that
|
||||
// overlap user_key and process them in order from newest to oldest.
|
||||
tmp.reserve(num_files);
|
||||
for (uint32_t i = 0; i < num_files; i++) {
|
||||
FileMetaData* f = files[i];
|
||||
if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
|
||||
ucmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
tmp.push_back(f);
|
||||
}
|
||||
}
|
||||
if (tmp.empty()) continue;
|
||||
important_files.clear();
|
||||
important_files.reserve(num_files);
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), NewestFirst);
|
||||
files = &tmp[0];
|
||||
num_files = tmp.size();
|
||||
// Some files may overlap each other. We find
|
||||
// all files that overlap user_key and process them in order from
|
||||
// newest to oldest. In the context of merge-operator,
|
||||
// this can occur at any level. Otherwise, it only occurs
|
||||
// at Level-0 (since Put/Deletes are always compacted into a single entry).
|
||||
uint32_t start_index;
|
||||
if (level == 0) {
|
||||
// On Level-0, we read through all files to check for overlap.
|
||||
start_index = 0;
|
||||
} else {
|
||||
// On Level-n (n>=1), files are sorted.
|
||||
// Binary search to find earliest index whose largest key >= ikey.
|
||||
uint32_t index = FindFile(vset_->icmp_, files_[level], ikey);
|
||||
if (index >= num_files) {
|
||||
files = nullptr;
|
||||
num_files = 0;
|
||||
} else {
|
||||
tmp2 = files[index];
|
||||
if (ucmp->Compare(user_key, tmp2->smallest.user_key()) < 0) {
|
||||
// All of "tmp2" is past any data for user_key
|
||||
files = nullptr;
|
||||
num_files = 0;
|
||||
} else {
|
||||
files = &tmp2;
|
||||
num_files = 1;
|
||||
// TODO, is level 1-n files all disjoint in user key space?
|
||||
}
|
||||
// We will also stop when the file no longer overlaps ikey
|
||||
start_index = FindFile(vset_->icmp_, files_[level], ikey);
|
||||
}
|
||||
|
||||
// Traverse the list, finding all overlapping files.
|
||||
for (uint32_t i = start_index; i < num_files; i++) {
|
||||
FileMetaData* f = files[i];
|
||||
if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
|
||||
ucmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
important_files.push_back(f);
|
||||
} else if (level > 0) {
|
||||
// If on Level-n (n>=1) then the files are sorted.
|
||||
// So we can stop looking when we are past the ikey.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (important_files.empty()) continue;
|
||||
|
||||
if (level == 0) {
|
||||
std::sort(important_files.begin(), important_files.end(), NewestFirst);
|
||||
} else {
|
||||
// Sanity check to make sure that the files are correctly sorted
|
||||
#ifndef NDEBUG
|
||||
num_files = important_files.size();
|
||||
for (uint32_t i = 1; i < num_files; ++i) {
|
||||
FileMetaData* a = important_files[i-1];
|
||||
FileMetaData* b = important_files[i];
|
||||
int comp_sign = vset_->icmp_.Compare(a->largest, b->smallest);
|
||||
assert(comp_sign < 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Traverse each relevant file to find the desired key
|
||||
num_files = important_files.size();
|
||||
for (uint32_t i = 0; i < num_files; ++i) {
|
||||
FileMetaData* f = files[i];
|
||||
FileMetaData* f = important_files[i];
|
||||
bool tableIO = false;
|
||||
*status = vset_->table_cache_->Get(options, f->number, f->file_size,
|
||||
ikey, &saver, SaveValue, &tableIO);
|
||||
ikey, &saver, SaveValue, &tableIO,
|
||||
MarkKeyMayExist, no_io);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
return;
|
||||
@@ -447,17 +498,17 @@ void Version::Get(const ReadOptions& options,
|
||||
|
||||
|
||||
if (kMerge == saver.state) {
|
||||
// merge operand is in *value and we hit the beginning of the key history
|
||||
// do a final merge of nullptr and operand;
|
||||
std::string operand;
|
||||
swap(operand, *value);
|
||||
merge_operator->Merge(user_key, nullptr, operand,
|
||||
value, logger.get());
|
||||
*status = Status::OK();
|
||||
return;
|
||||
// merge_operands are in saver and we hit the beginning of the key history
|
||||
// do a final merge of nullptr and operands;
|
||||
if (merge_operator->Merge(user_key, nullptr, *saver.merge_operands,
|
||||
value, logger.get())) {
|
||||
*status = Status::OK();
|
||||
} else {
|
||||
*status = Status::Corruption("could not perform end-of-key merge for ",
|
||||
user_key);
|
||||
}
|
||||
} else {
|
||||
*status = Status::NotFound(Slice()); // Use an empty error message for speed
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,6 +746,53 @@ void Version::ExtendOverlappingInputs(
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true iff the first or last file in inputs contains
|
||||
// an overlapping user key to the file "just outside" of it (i.e.
|
||||
// just after the last file, or just before the first file)
|
||||
// REQUIRES: "*inputs" is a sorted list of non-overlapping files
|
||||
bool Version::HasOverlappingUserKey(
|
||||
const std::vector<FileMetaData*>* inputs,
|
||||
int level) {
|
||||
|
||||
// If inputs empty, there is no overlap.
|
||||
// If level == 0, it is assumed that all needed files were already included.
|
||||
if (inputs->empty() || level == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
const Comparator* user_cmp = vset_->icmp_.user_comparator();
|
||||
const std::vector<FileMetaData*>& files = files_[level];
|
||||
const size_t kNumFiles = files.size();
|
||||
|
||||
// Check the last file in inputs against the file after it
|
||||
size_t last_file = FindFile(vset_->icmp_, files,
|
||||
inputs->back()->largest.Encode());
|
||||
assert(0 <= last_file && last_file < kNumFiles); // File should exist!
|
||||
if (last_file < kNumFiles-1) { // If not the last file
|
||||
const Slice last_key_in_input = files[last_file]->largest.user_key();
|
||||
const Slice first_key_after = files[last_file+1]->smallest.user_key();
|
||||
if (user_cmp->Compare(last_key_in_input, first_key_after) == 0) {
|
||||
// The last user key in input overlaps with the next file's first key
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the first file in inputs against the file just before it
|
||||
size_t first_file = FindFile(vset_->icmp_, files,
|
||||
inputs->front()->smallest.Encode());
|
||||
assert(0 <= first_file && first_file <= last_file); // File should exist!
|
||||
if (first_file > 0) { // If not first file
|
||||
const Slice& first_key_in_input = files[first_file]->smallest.user_key();
|
||||
const Slice& last_key_before = files[first_file-1]->largest.user_key();
|
||||
if (user_cmp->Compare(first_key_in_input, last_key_before) == 0) {
|
||||
// The first user key in input overlaps with the previous file's last key
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Version::DebugString(bool hex) const {
|
||||
std::string r;
|
||||
for (int level = 0; level < vset_->NumberLevels(); level++) {
|
||||
@@ -1544,7 +1642,7 @@ void VersionSet::Finalize(Version* v,
|
||||
|
||||
// sort all the levels based on their score. Higher scores get listed
|
||||
// first. Use bubble sort because the number of entries are small.
|
||||
for(int i = 0; i < NumberLevels()-2; i++) {
|
||||
for (int i = 0; i < NumberLevels()-2; i++) {
|
||||
for (int j = i+1; j < NumberLevels()-1; j++) {
|
||||
if (v->compaction_score_[i] < v->compaction_score_[j]) {
|
||||
double score = v->compaction_score_[i];
|
||||
@@ -1974,7 +2072,7 @@ Compaction* VersionSet::PickCompactionBySize(int level, double score) {
|
||||
|
||||
assert(level >= 0);
|
||||
assert(level+1 < NumberLevels());
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level),
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level+1),
|
||||
MaxGrandParentOverlapBytes(level), NumberLevels());
|
||||
c->score_ = score;
|
||||
|
||||
@@ -2038,7 +2136,7 @@ Compaction* VersionSet::PickCompaction() {
|
||||
Compaction* c = nullptr;
|
||||
int level = -1;
|
||||
|
||||
// compute the compactions needed. It is better to do it here
|
||||
// Compute the compactions needed. It is better to do it here
|
||||
// and also in LogAndApply(), otherwise the values could be stale.
|
||||
std::vector<uint64_t> size_being_compacted(NumberLevels()-1);
|
||||
current_->vset_->SizeBeingCompacted(size_being_compacted);
|
||||
@@ -2054,6 +2152,7 @@ Compaction* VersionSet::PickCompaction() {
|
||||
level = current_->compaction_level_[i];
|
||||
if ((current_->compaction_score_[i] >= 1)) {
|
||||
c = PickCompactionBySize(level, current_->compaction_score_[i]);
|
||||
ExpandWhileOverlapping(c);
|
||||
if (c != nullptr) {
|
||||
break;
|
||||
}
|
||||
@@ -2070,13 +2169,14 @@ Compaction* VersionSet::PickCompaction() {
|
||||
// Only allow one level 0 compaction at a time.
|
||||
// Do not pick this file if its parents at level+1 are being compacted.
|
||||
if (level != 0 || compactions_in_progress_[0].empty()) {
|
||||
if(!ParentRangeInCompaction(&f->smallest, &f->largest, level,
|
||||
&parent_index)) {
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level),
|
||||
if (!ParentRangeInCompaction(&f->smallest, &f->largest, level,
|
||||
&parent_index)) {
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level+1),
|
||||
MaxGrandParentOverlapBytes(level), NumberLevels(), true);
|
||||
c->inputs_[0].push_back(f);
|
||||
c->parent_index_ = parent_index;
|
||||
current_->file_to_compact_ = nullptr;
|
||||
ExpandWhileOverlapping(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2088,7 +2188,6 @@ Compaction* VersionSet::PickCompaction() {
|
||||
c->input_version_ = current_;
|
||||
c->input_version_->Ref();
|
||||
|
||||
// Files in level 0 may overlap each other, so pick up all overlapping ones
|
||||
// Two level 0 compaction won't run at the same time, so don't need to worry
|
||||
// about files on level 0 being compacted.
|
||||
if (level == 0) {
|
||||
@@ -2113,6 +2212,8 @@ Compaction* VersionSet::PickCompaction() {
|
||||
assert(!c->inputs_[0].empty());
|
||||
}
|
||||
|
||||
|
||||
// Setup "level+1" files (inputs_[1])
|
||||
SetupOtherInputs(c);
|
||||
|
||||
// mark all the files that are being compacted
|
||||
@@ -2144,11 +2245,76 @@ bool VersionSet::FilesInCompaction(std::vector<FileMetaData*>& files) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void VersionSet::SetupOtherInputs(Compaction* c) {
|
||||
// Add more files to the inputs on "level" to make sure that
|
||||
// no newer version of a key is compacted to "level+1" while leaving an older
|
||||
// version in a "level". Otherwise, any Get() will search "level" first,
|
||||
// and will likely return an old/stale value for the key, since it always
|
||||
// searches in increasing order of level to find the value. This could
|
||||
// also scramble the order of merge operands. This function should be
|
||||
// called any time a new Compaction is created, and its inputs_[0] are
|
||||
// populated.
|
||||
//
|
||||
// Will set c to nullptr if it is impossible to apply this compaction.
|
||||
void VersionSet::ExpandWhileOverlapping(Compaction* c) {
|
||||
// If inputs are empty then there is nothing to expand.
|
||||
if (!c || c->inputs_[0].empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GetOverlappingInputs will always do the right thing for level-0.
|
||||
// So we don't need to do any expansion if level == 0.
|
||||
if (c->level() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int level = c->level();
|
||||
InternalKey smallest, largest;
|
||||
|
||||
// Keep expanding c->inputs_[0] until we are sure that there is a
|
||||
// "clean cut" boundary between the files in input and the surrounding files.
|
||||
// This will ensure that no parts of a key are lost during compaction.
|
||||
int hint_index = -1;
|
||||
size_t old_size;
|
||||
do {
|
||||
old_size = c->inputs_[0].size();
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
c->inputs_[0].clear();
|
||||
current_->GetOverlappingInputs(level, &smallest, &largest, &c->inputs_[0],
|
||||
hint_index, &hint_index);
|
||||
} while(c->inputs_[0].size() > old_size);
|
||||
|
||||
// Get the new range
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
|
||||
// If, after the expansion, there are files that are already under
|
||||
// compaction, then we must drop/cancel this compaction.
|
||||
int parent_index = -1;
|
||||
if (FilesInCompaction(c->inputs_[0]) ||
|
||||
ParentRangeInCompaction(&smallest, &largest, level, &parent_index)) {
|
||||
c->inputs_[0].clear();
|
||||
c->inputs_[1].clear();
|
||||
delete c;
|
||||
c = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Populates the set of inputs from "level+1" that overlap with "level".
|
||||
// Will also attempt to expand "level" if that doesn't expand "level+1"
|
||||
// or cause "level" to include a file for compaction that has an overlapping
|
||||
// user-key with another file.
|
||||
void VersionSet::SetupOtherInputs(Compaction* c) {
|
||||
// If inputs are empty, then there is nothing to expand.
|
||||
if (c->inputs_[0].empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int level = c->level();
|
||||
InternalKey smallest, largest;
|
||||
|
||||
// Get the range one last time.
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
|
||||
// Populate the set of next-level files (inputs_[1]) to include in compaction
|
||||
current_->GetOverlappingInputs(level+1, &smallest, &largest, &c->inputs_[1],
|
||||
c->parent_index_, &c->parent_index_);
|
||||
|
||||
@@ -2156,8 +2322,11 @@ void VersionSet::SetupOtherInputs(Compaction* c) {
|
||||
InternalKey all_start, all_limit;
|
||||
GetRange2(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
|
||||
|
||||
// See if we can grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up.
|
||||
// See if we can further grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up. We also choose NOT
|
||||
// to expand if this would cause "level" to include some entries for some
|
||||
// user key, while excluding other entries for the same user key. This
|
||||
// can happen when one user key spans multiple files.
|
||||
if (!c->inputs_[1].empty()) {
|
||||
std::vector<FileMetaData*> expanded0;
|
||||
current_->GetOverlappingInputs(level, &all_start, &all_limit, &expanded0,
|
||||
@@ -2168,7 +2337,8 @@ void VersionSet::SetupOtherInputs(Compaction* c) {
|
||||
int64_t limit = ExpandedCompactionByteSizeLimit(level);
|
||||
if (expanded0.size() > c->inputs_[0].size() &&
|
||||
inputs1_size + expanded0_size < limit &&
|
||||
!FilesInCompaction(expanded0)) {
|
||||
!FilesInCompaction(expanded0) &&
|
||||
!current_->HasOverlappingUserKey(&expanded0, level)) {
|
||||
InternalKey new_start, new_limit;
|
||||
GetRange(expanded0, &new_start, &new_limit);
|
||||
std::vector<FileMetaData*> expanded1;
|
||||
@@ -2227,13 +2397,11 @@ Compaction* VersionSet::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if (level > 0) {
|
||||
|
||||
const uint64_t limit = MaxFileSizeForLevel(level) *
|
||||
options_->source_compaction_factor;
|
||||
uint64_t total = 0;
|
||||
@@ -2246,12 +2414,18 @@ Compaction* VersionSet::CompactRange(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Compaction* c = new Compaction(level, MaxFileSizeForLevel(level),
|
||||
Compaction* c = new Compaction(level, MaxFileSizeForLevel(level+1),
|
||||
MaxGrandParentOverlapBytes(level), NumberLevels());
|
||||
|
||||
c->inputs_[0] = inputs;
|
||||
ExpandWhileOverlapping(c);
|
||||
if (c == nullptr) {
|
||||
Log(options_->info_log, "Could not compact due to expansion failure.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
c->input_version_ = current_;
|
||||
c->input_version_->Ref();
|
||||
c->inputs_[0] = inputs;
|
||||
SetupOtherInputs(c);
|
||||
|
||||
// These files that are to be manaully compacted do not trample
|
||||
@@ -2396,8 +2570,9 @@ void Compaction::Summary(char* output, int len) {
|
||||
int write = snprintf(output, len,
|
||||
"Base version %ld Base level %d, seek compaction:%d, inputs:",
|
||||
input_version_->GetVersionNumber(), level_, seek_compaction_);
|
||||
if(write < 0 || write > len)
|
||||
if (write < 0 || write > len) {
|
||||
return;
|
||||
}
|
||||
|
||||
char level_low_summary[100];
|
||||
InputSummary(inputs_[0], level_low_summary, sizeof(level_low_summary));
|
||||
|
||||
+15
-1
@@ -68,13 +68,16 @@ class Version {
|
||||
|
||||
// Lookup the value for key. If found, store it in *val and
|
||||
// return OK. Else return a non-OK status. Fills *stats.
|
||||
// Uses *operands to store merge_operator operations to apply later
|
||||
// REQUIRES: lock is not held
|
||||
struct GetStats {
|
||||
FileMetaData* seek_file;
|
||||
int seek_file_level;
|
||||
};
|
||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
Status* status, GetStats* stats, const Options& db_option);
|
||||
Status* status, std::deque<std::string>* operands, GetStats* stats,
|
||||
const Options& db_option, const bool no_io = false,
|
||||
bool* value_found = nullptr);
|
||||
|
||||
// Adds "stats" into the current state. Returns true if a new
|
||||
// compaction may need to be triggered, false otherwise.
|
||||
@@ -117,6 +120,14 @@ class Version {
|
||||
const Slice* smallest_user_key,
|
||||
const Slice* largest_user_key);
|
||||
|
||||
// Returns true iff the first or last file in inputs contains
|
||||
// an overlapping user key to the file "just outside" of it (i.e.
|
||||
// just after the last file, or just before the first file)
|
||||
// REQUIRES: "*inputs" is a sorted list of non-overlapping files
|
||||
bool HasOverlappingUserKey(const std::vector<FileMetaData*>* inputs,
|
||||
int level);
|
||||
|
||||
|
||||
// Return the level at which we should place a new memtable compaction
|
||||
// result that covers the range [smallest_user_key,largest_user_key].
|
||||
int PickLevelForMemTableOutput(const Slice& smallest_user_key,
|
||||
@@ -135,6 +146,7 @@ class Version {
|
||||
private:
|
||||
friend class Compaction;
|
||||
friend class VersionSet;
|
||||
friend class DBImpl;
|
||||
|
||||
class LevelFileNumIterator;
|
||||
Iterator* NewConcatenatingIterator(const ReadOptions&,
|
||||
@@ -403,6 +415,8 @@ class VersionSet {
|
||||
InternalKey* smallest,
|
||||
InternalKey* largest);
|
||||
|
||||
void ExpandWhileOverlapping(Compaction* c);
|
||||
|
||||
void SetupOtherInputs(Compaction* c);
|
||||
|
||||
// Save current contents to *log
|
||||
|
||||
+41
-6
@@ -16,9 +16,12 @@
|
||||
|
||||
#include "leveldb/write_batch.h"
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/statistics.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/snapshot.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/coding.h"
|
||||
#include <stdexcept>
|
||||
@@ -45,6 +48,10 @@ void WriteBatch::Clear() {
|
||||
rep_.resize(kHeader);
|
||||
}
|
||||
|
||||
int WriteBatch::Count() const {
|
||||
return WriteBatchInternal::Count(this);
|
||||
}
|
||||
|
||||
Status WriteBatch::Iterate(Handler* handler) const {
|
||||
Slice input(rep_);
|
||||
if (input.size() < kHeader) {
|
||||
@@ -135,6 +142,23 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
public:
|
||||
SequenceNumber sequence_;
|
||||
MemTable* mem_;
|
||||
const Options* options_;
|
||||
DBImpl* db_;
|
||||
const bool filter_deletes_;
|
||||
|
||||
MemTableInserter(SequenceNumber sequence, MemTable* mem, const Options* opts,
|
||||
DB* db, const bool filter_deletes)
|
||||
: sequence_(sequence),
|
||||
mem_(mem),
|
||||
options_(opts),
|
||||
db_(reinterpret_cast<DBImpl*>(db)),
|
||||
filter_deletes_(filter_deletes) {
|
||||
assert(mem_);
|
||||
if (filter_deletes_) {
|
||||
assert(options_);
|
||||
assert(db_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Put(const Slice& key, const Slice& value) {
|
||||
mem_->Add(sequence_, kTypeValue, key, value);
|
||||
@@ -145,17 +169,28 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
sequence_++;
|
||||
}
|
||||
virtual void Delete(const Slice& key) {
|
||||
if (filter_deletes_) {
|
||||
SnapshotImpl read_from_snapshot;
|
||||
read_from_snapshot.number_ = sequence_;
|
||||
ReadOptions ropts;
|
||||
ropts.snapshot = &read_from_snapshot;
|
||||
std::string value;
|
||||
if (!db_->KeyMayExist(ropts, key, &value)) {
|
||||
RecordTick(options_->statistics, NUMBER_FILTERED_DELETES);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mem_->Add(sequence_, kTypeDeletion, key, Slice());
|
||||
sequence_++;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* b,
|
||||
MemTable* memtable) {
|
||||
MemTableInserter inserter;
|
||||
inserter.sequence_ = WriteBatchInternal::Sequence(b);
|
||||
inserter.mem_ = memtable;
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* b, MemTable* mem,
|
||||
const Options* opts, DB* db,
|
||||
const bool filter_deletes) {
|
||||
MemTableInserter inserter(WriteBatchInternal::Sequence(b), mem, opts, db,
|
||||
filter_deletes);
|
||||
return b->Iterate(&inserter);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "leveldb/types.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/options.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -39,7 +41,12 @@ class WriteBatchInternal {
|
||||
|
||||
static void SetContents(WriteBatch* batch, const Slice& contents);
|
||||
|
||||
static Status InsertInto(const WriteBatch* batch, MemTable* memtable);
|
||||
// Inserts batch entries into memtable
|
||||
// Drops deletes in batch if filter_del is set to true and
|
||||
// db->KeyMayExist returns false
|
||||
static Status InsertInto(const WriteBatch* batch, MemTable* memtable,
|
||||
const Options* opts = nullptr, DB* db = nullptr,
|
||||
const bool filter_del = false);
|
||||
|
||||
static void Append(WriteBatch* dst, const WriteBatch* src);
|
||||
};
|
||||
|
||||
+10
-1
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "leveldb/db.h"
|
||||
|
||||
#include <memory>
|
||||
#include "db/skiplistrep.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "leveldb/env.h"
|
||||
@@ -14,7 +16,8 @@ namespace leveldb {
|
||||
|
||||
static std::string PrintContents(WriteBatch* b) {
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
MemTable* mem = new MemTable(cmp);
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
MemTable* mem = new MemTable(cmp, factory);
|
||||
mem->Ref();
|
||||
std::string state;
|
||||
Status s = WriteBatchInternal::InsertInto(b, mem);
|
||||
@@ -67,6 +70,7 @@ TEST(WriteBatchTest, Empty) {
|
||||
WriteBatch batch;
|
||||
ASSERT_EQ("", PrintContents(&batch));
|
||||
ASSERT_EQ(0, WriteBatchInternal::Count(&batch));
|
||||
ASSERT_EQ(0, batch.Count());
|
||||
}
|
||||
|
||||
TEST(WriteBatchTest, Multiple) {
|
||||
@@ -81,6 +85,7 @@ TEST(WriteBatchTest, Multiple) {
|
||||
"Delete(box)@101"
|
||||
"Put(foo, bar)@100",
|
||||
PrintContents(&batch));
|
||||
ASSERT_EQ(3, batch.Count());
|
||||
}
|
||||
|
||||
TEST(WriteBatchTest, Corruption) {
|
||||
@@ -103,16 +108,19 @@ TEST(WriteBatchTest, Append) {
|
||||
WriteBatchInternal::Append(&b1, &b2);
|
||||
ASSERT_EQ("",
|
||||
PrintContents(&b1));
|
||||
ASSERT_EQ(0, b1.Count());
|
||||
b2.Put("a", "va");
|
||||
WriteBatchInternal::Append(&b1, &b2);
|
||||
ASSERT_EQ("Put(a, va)@200",
|
||||
PrintContents(&b1));
|
||||
ASSERT_EQ(1, b1.Count());
|
||||
b2.Clear();
|
||||
b2.Put("b", "vb");
|
||||
WriteBatchInternal::Append(&b1, &b2);
|
||||
ASSERT_EQ("Put(a, va)@200"
|
||||
"Put(b, vb)@201",
|
||||
PrintContents(&b1));
|
||||
ASSERT_EQ(2, b1.Count());
|
||||
b2.Delete("foo");
|
||||
WriteBatchInternal::Append(&b1, &b2);
|
||||
ASSERT_EQ("Put(a, va)@200"
|
||||
@@ -120,6 +128,7 @@ TEST(WriteBatchTest, Append) {
|
||||
"Put(b, vb)@201"
|
||||
"Delete(foo)@203",
|
||||
PrintContents(&b1));
|
||||
ASSERT_EQ(4, b1.Count());
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Arena class defines memory allocation methods. It's used by memtable and
|
||||
// skiplist.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_ARENA_H_
|
||||
#define STORAGE_LEVELDB_INCLUDE_ARENA_H_
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Arena {
|
||||
public:
|
||||
Arena() {};
|
||||
virtual ~Arena() {};
|
||||
|
||||
// Return a pointer to a newly allocated memory block of "bytes" bytes.
|
||||
virtual char* Allocate(size_t bytes) = 0;
|
||||
|
||||
// Allocate memory with the normal alignment guarantees provided by malloc.
|
||||
virtual char* AllocateAligned(size_t bytes) = 0;
|
||||
|
||||
// Returns an estimate of the total memory used by arena.
|
||||
virtual const size_t ApproximateMemoryUsage() = 0;
|
||||
|
||||
// Returns the total number of bytes in all blocks allocated so far.
|
||||
virtual const size_t MemoryAllocatedBytes() = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Arena(const Arena&);
|
||||
void operator=(const Arena&);
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_INCLUDE_ARENA_H_
|
||||
+29
-4
@@ -19,8 +19,8 @@ namespace leveldb {
|
||||
using std::unique_ptr;
|
||||
|
||||
// Update Makefile if you change these
|
||||
static const int kMajorVersion = 1;
|
||||
static const int kMinorVersion = 5;
|
||||
static const int kMajorVersion = 2;
|
||||
static const int kMinorVersion = 0;
|
||||
|
||||
struct Options;
|
||||
struct ReadOptions;
|
||||
@@ -104,7 +104,8 @@ class DB {
|
||||
//
|
||||
// May return some other Status on an error.
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key, std::string* value) = 0;
|
||||
const Slice& key,
|
||||
std::string* value) = 0;
|
||||
|
||||
// If keys[i] does not exist in the database, then the i'th returned
|
||||
// status will be one for which Status::IsNotFound() is true, and
|
||||
@@ -120,6 +121,23 @@ class DB {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) = 0;
|
||||
|
||||
// If the key definitely does not exist in the database, then this method
|
||||
// returns false, else true. If the caller wants to obtain value when the key
|
||||
// is found in memory, a bool for 'value_found' must be passed. 'value_found'
|
||||
// will be true on return if value has been set properly.
|
||||
// This check is potentially lighter-weight than invoking DB::Get(). One way
|
||||
// to make this lighter weight is to avoid doing any IOs.
|
||||
// Default implementation here returns true and sets 'value_found' to false
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) {
|
||||
if (value_found != nullptr) {
|
||||
*value_found = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return a heap-allocated iterator over the contents of the database.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
@@ -175,7 +193,14 @@ class DB {
|
||||
// end==nullptr is treated as a key after all keys in the database.
|
||||
// Therefore the following call will compact the entire database:
|
||||
// db->CompactRange(nullptr, nullptr);
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end) = 0;
|
||||
// Note that after the entire database is compacted, all data are pushed
|
||||
// down to the last level containing any data. If the total data size
|
||||
// after compaction is reduced, that level might not be appropriate for
|
||||
// hosting all the files. In this case, client could set reduce_level
|
||||
// to true, to move the files back to the minimum level capable of holding
|
||||
// the data set.
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false) = 0;
|
||||
|
||||
// Number of levels used for this DB.
|
||||
virtual int NumberLevels() = 0;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// This file contains the interface that must be implemented by any collection
|
||||
// to be used as the backing store for a MemTable. Such a collection must
|
||||
// satisfy the following properties:
|
||||
// (1) It does not store duplicate items.
|
||||
// (2) It uses MemTableRep::KeyComparator to compare items for iteration and
|
||||
// equality.
|
||||
// (3) It can be accessed concurrently by multiple readers but need not support
|
||||
// concurrent writes.
|
||||
// (4) Items are never deleted.
|
||||
// The liberal use of assertions is encouraged to enforce (1).
|
||||
|
||||
#ifndef STORAGE_LEVELDB_DB_TABLE_H_
|
||||
#define STORAGE_LEVELDB_DB_TABLE_H_
|
||||
|
||||
#include <memory>
|
||||
#include "leveldb/arena.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class MemTableRep {
|
||||
public:
|
||||
// KeyComparator(a, b) returns a negative value if a is less than b, 0 if they
|
||||
// are equal, and a positive value if b is greater than a
|
||||
class KeyComparator {
|
||||
public:
|
||||
virtual int operator()(const char* a, const char* b) const = 0;
|
||||
virtual ~KeyComparator() { }
|
||||
};
|
||||
|
||||
// Insert key into the collection. (The caller will pack key and value into a
|
||||
// single buffer and pass that in as the parameter to Insert)
|
||||
// REQUIRES: nothing that compares equal to key is currently in the
|
||||
// collection.
|
||||
virtual void Insert(const char* key) = 0;
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
virtual bool Contains(const char* key) const = 0;
|
||||
|
||||
virtual ~MemTableRep() { }
|
||||
|
||||
// Iteration over the contents of a skip collection
|
||||
class Iterator {
|
||||
public:
|
||||
// Initialize an iterator over the specified collection.
|
||||
// The returned iterator is not valid.
|
||||
// explicit Iterator(const MemTableRep* collection);
|
||||
virtual ~Iterator() { };
|
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const = 0;
|
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const = 0;
|
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() = 0;
|
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() = 0;
|
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
virtual void Seek(const char* target) = 0;
|
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToFirst() = 0;
|
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToLast() = 0;
|
||||
};
|
||||
|
||||
virtual std::shared_ptr<Iterator> GetIterator() = 0;
|
||||
};
|
||||
|
||||
class MemTableRepFactory {
|
||||
public:
|
||||
virtual ~MemTableRepFactory() { };
|
||||
virtual std::shared_ptr<MemTableRep> CreateMemTableRep(
|
||||
MemTableRep::KeyComparator&, Arena* arena) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // STORAGE_LEVELDB_DB_TABLE_H_
|
||||
@@ -6,26 +6,38 @@
|
||||
#define STORAGE_LEVELDB_INCLUDE_MERGE_OPERATOR_H_
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include "leveldb/slice.h" // TODO: Remove this when migration is done;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Slice;
|
||||
class Logger;
|
||||
|
||||
// The Merge Operator interface.
|
||||
// Client needs to provide an object implementing this interface if Merge
|
||||
// operation is accessed.
|
||||
// Essentially, MergeOperator specifies the SEMANTICS of a merge, which only
|
||||
// The Merge Operator
|
||||
//
|
||||
// Essentially, a MergeOperator specifies the SEMANTICS of a merge, which only
|
||||
// client knows. It could be numeric addition, list append, string
|
||||
// concatenation, ... , anything.
|
||||
// concatenation, edit data structure, ... , anything.
|
||||
// The library, on the other hand, is concerned with the exercise of this
|
||||
// interface, at the right time (during get, iteration, compaction...)
|
||||
// Note that, even though in principle we don't require any special property
|
||||
// of the merge operator, the current rocksdb compaction order does imply that
|
||||
// an associative operator could be exercised more naturally (and more
|
||||
// efficiently).
|
||||
//
|
||||
// Refer to my_test.cc for an example of implementation
|
||||
// To use merge, the client needs to provide an object implementing one of
|
||||
// the following interfaces:
|
||||
// a) AssociativeMergeOperator - for most simple semantics (always take
|
||||
// two values, and merge them into one value, which is then put back
|
||||
// into rocksdb); numeric addition and string concatenation are examples;
|
||||
//
|
||||
// b) MergeOperator - the generic class for all the more abstract / complex
|
||||
// operations; one method to merge a Put/Delete value with a merge operand;
|
||||
// and another method (PartialMerge) that merges two operands together.
|
||||
// this is especially useful if your key values have a complex structure,
|
||||
// but you would still like to support client-specific incremental updates.
|
||||
//
|
||||
// AssociativeMergeOperator is simpler to implement. MergeOperator is simply
|
||||
// more powerful.
|
||||
//
|
||||
// Refer to rocksdb-merge wiki for more details and example implementations.
|
||||
//
|
||||
class MergeOperator {
|
||||
public:
|
||||
@@ -38,35 +50,95 @@ class MergeOperator {
|
||||
// refer to different types of data which have different
|
||||
// merge operation semantics
|
||||
// existing: (IN) null indicates that the key does not exist before this op
|
||||
// value: (IN) The passed-in merge operand value (when Merge is issued)
|
||||
// operand_list:(IN) the sequence of merge operations to apply, front() first.
|
||||
// new_value:(OUT) Client is responsible for filling the merge result here
|
||||
// logger: (IN) Client could use this to log errors during merge.
|
||||
//
|
||||
// Note: Merge does not return anything to indicate if a merge is successful
|
||||
// or not.
|
||||
// Rationale: If a merge failed due to, say de-serialization error, we still
|
||||
// need to define a consistent merge result. Should we throw away
|
||||
// the existing value? the merge operand? Or reset the merged value
|
||||
// to sth? The rocksdb library is not in a position to make the
|
||||
// right choice. On the other hand, client knows exactly what
|
||||
// happened during Merge, thus is able to make the best decision.
|
||||
// Just save the final decision in new_value. logger is passed in,
|
||||
// in case client wants to leave a trace of what went wrong.
|
||||
virtual void Merge(const Slice& key,
|
||||
// Return true on success.
|
||||
// All values passed in will be client-specific values. So if this method
|
||||
// returns false, it is because client specified bad data or there was
|
||||
// internal corruption. This will be treated as an error by the library.
|
||||
//
|
||||
// Also make use of the *logger for error messages.
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
const std::deque<std::string>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const = 0;
|
||||
|
||||
// This function performs merge(left_op, right_op)
|
||||
// when both the operands are themselves merge operation types
|
||||
// that you would have passed to a DB::Merge() call in the same order
|
||||
// (i.e.: DB::Merge(key,left_op), followed by DB::Merge(key,right_op)).
|
||||
//
|
||||
// PartialMerge should combine them into a single merge operation that is
|
||||
// saved into *new_value, and then it should return true.
|
||||
// *new_value should be constructed such that a call to
|
||||
// DB::Merge(key, *new_value) would yield the same result as a call
|
||||
// to DB::Merge(key, left_op) followed by DB::Merge(key, right_op).
|
||||
//
|
||||
// If it is impossible or infeasible to combine the two operations,
|
||||
// leave new_value unchanged and return false. The library will
|
||||
// internally keep track of the operations, and apply them in the
|
||||
// correct order once a base-value (a Put/Delete/End-of-Database) is seen.
|
||||
//
|
||||
// TODO: Presently there is no way to differentiate between error/corruption
|
||||
// and simply "return false". For now, the client should simply return
|
||||
// false in any case it cannot perform partial-merge, regardless of reason.
|
||||
// If there is corruption in the data, handle it in the above Merge() function,
|
||||
// and return false there.
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const = 0;
|
||||
|
||||
// The name of the MergeOperator. Used to check for MergeOperator
|
||||
// The name of the MergeOperator. Used to check for MergeOperator
|
||||
// mismatches (i.e., a DB created with one MergeOperator is
|
||||
// accessed using a different MergeOperator)
|
||||
// TODO: the name is currently not stored persistently and thus
|
||||
// no checking is enforced. Client is responsible for providing
|
||||
// consistent MergeOperator between DB opens.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
// The simpler, associative merge operator.
|
||||
class AssociativeMergeOperator : public MergeOperator {
|
||||
public:
|
||||
virtual ~AssociativeMergeOperator() {}
|
||||
|
||||
// Gives the client a way to express the read -> modify -> write semantics
|
||||
// key: (IN) The key that's associated with this merge operation.
|
||||
// existing_value:(IN) null indicates the key does not exist before this op
|
||||
// value: (IN) the value to update/merge the existing_value with
|
||||
// new_value: (OUT) Client is responsible for filling the merge result here
|
||||
// logger: (IN) Client could use this to log errors during merge.
|
||||
//
|
||||
// Return true on success.
|
||||
// All values passed in will be client-specific values. So if this method
|
||||
// returns false, it is because client specified bad data or there was
|
||||
// internal corruption. The client should assume that this will be treated
|
||||
// as an error by the library.
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
Logger* logger) const = 0;
|
||||
|
||||
|
||||
private:
|
||||
// Default implementations of the MergeOperator functions
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const std::deque<std::string>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
+39
-20
@@ -12,6 +12,7 @@
|
||||
#include <stdint.h>
|
||||
#include "leveldb/slice.h"
|
||||
#include "leveldb/statistics.h"
|
||||
#include "leveldb/memtablerep.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -132,7 +133,7 @@ struct Options {
|
||||
int max_write_buffer_number;
|
||||
|
||||
// The minimum number of write buffers that will be merged together
|
||||
// before writing to storage. If set to 1, then
|
||||
// before writing to storage. If set to 1, then
|
||||
// all write buffers are fushed to L0 as individual files and this increases
|
||||
// read amplification because a get request has to check in all of these
|
||||
// files. Also, an in-memory merge may result in writing lesser
|
||||
@@ -218,9 +219,9 @@ struct Options {
|
||||
// level-0 compaction will not be triggered by number of files at all.
|
||||
int level0_file_num_compaction_trigger;
|
||||
|
||||
// Soft limit on number of level-0 files. We slow down writes at this point.
|
||||
// A value <0 means that no writing slow down will be triggered by number
|
||||
// of files in level-0.
|
||||
// Soft limit on number of level-0 files. We start slowing down writes at this
|
||||
// point. A value <0 means that no writing slow down will be triggered by
|
||||
// number of files in level-0.
|
||||
int level0_slowdown_writes_trigger;
|
||||
|
||||
// Maximum number of level-0 files. We stop writes at this point.
|
||||
@@ -354,12 +355,22 @@ struct Options {
|
||||
// Default: 1000
|
||||
size_t keep_log_file_num;
|
||||
|
||||
// Puts are delayed when any level has a compaction score that
|
||||
// exceeds rate_limit. This is ignored when <= 1.0.
|
||||
double rate_limit;
|
||||
// Puts are delayed 0-1 ms when any level has a compaction score that exceeds
|
||||
// soft_rate_limit. This is ignored when == 0.0.
|
||||
// CONSTRAINT: soft_rate_limit <= hard_rate_limit. If this constraint does not
|
||||
// hold, RocksDB will set soft_rate_limit = hard_rate_limit
|
||||
// Default: 0 (disabled)
|
||||
double soft_rate_limit;
|
||||
|
||||
// Max time a put will be stalled when rate_limit is enforced
|
||||
unsigned int rate_limit_delay_milliseconds;
|
||||
// Puts are delayed 1ms at a time when any level has a compaction score that
|
||||
// exceeds hard_rate_limit. This is ignored when <= 1.0.
|
||||
// Default: 0 (disabled)
|
||||
double hard_rate_limit;
|
||||
|
||||
// Max time a put will be stalled when hard_rate_limit is enforced. If 0, then
|
||||
// there is no limit.
|
||||
// Default: 1000
|
||||
unsigned int rate_limit_delay_max_milliseconds;
|
||||
|
||||
// manifest file is rolled over on reaching this limit.
|
||||
// The older manifest file be deleted.
|
||||
@@ -374,6 +385,13 @@ struct Options {
|
||||
// Number of shards used for table cache.
|
||||
int table_cache_numshardbits;
|
||||
|
||||
// size of one block in arena memory allocation.
|
||||
// If <= 0, a proper value is automatically calculated (usually 1/10 of
|
||||
// writer_buffer_size).
|
||||
//
|
||||
// Default: 0
|
||||
size_t arena_block_size;
|
||||
|
||||
// Create an Options object with default values for all fields.
|
||||
Options();
|
||||
|
||||
@@ -416,17 +434,6 @@ struct Options {
|
||||
// Default: true
|
||||
bool allow_os_buffer;
|
||||
|
||||
// Reading a single block from a file can cause the OS/FS to start
|
||||
// readaheads of other blocks from the file. Default: true
|
||||
// Note: Deprecated
|
||||
bool allow_readahead;
|
||||
|
||||
// The reads triggered by compaction allows data to be readahead
|
||||
// by the OS/FS. This overrides the setting of 'allow_readahead'
|
||||
// for compaction-reads. Default: true
|
||||
// Note: Deprecated
|
||||
bool allow_readahead_compactions;
|
||||
|
||||
// Allow the OS to mmap file for reading sst tables. Default: false
|
||||
bool allow_mmap_reads;
|
||||
|
||||
@@ -476,6 +483,18 @@ struct Options {
|
||||
// Default: 0
|
||||
uint64_t bytes_per_sync;
|
||||
|
||||
// Use KeyMayExist API to filter deletes when this is true.
|
||||
// If KeyMayExist returns false, i.e. the key definitely does not exist, then
|
||||
// the delete is a noop. KeyMayExist only incurs in-memory look up.
|
||||
// This optimization avoids writing the delete to storage when appropriate.
|
||||
// Default: false
|
||||
bool filter_deletes;
|
||||
|
||||
// This is a factory that provides MemTableRep objects.
|
||||
// Default: a factory that provides a skip-list-based implementation of
|
||||
// MemTableRep.
|
||||
std::shared_ptr<MemTableRepFactory> memtable_factory;
|
||||
|
||||
};
|
||||
|
||||
// Options that control read operations
|
||||
|
||||
@@ -58,7 +58,9 @@ enum Tickers {
|
||||
NUMBER_MULTIGET_KEYS_READ = 19,
|
||||
NUMBER_MULTIGET_BYTES_READ = 20,
|
||||
|
||||
TICKER_ENUM_MAX = 21
|
||||
NUMBER_FILTERED_DELETES = 21,
|
||||
|
||||
TICKER_ENUM_MAX = 22
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
@@ -78,11 +80,12 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{ STALL_L0_SLOWDOWN_MICROS, "rocksdb.l0.slowdown.micros" },
|
||||
{ STALL_MEMTABLE_COMPACTION_MICROS, "rocksdb.memtable.compaction.micros" },
|
||||
{ STALL_L0_NUM_FILES_MICROS, "rocksdb.l0.num.files.stall.micros" },
|
||||
{ RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.dleay.millis" },
|
||||
{ RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.delay.millis" },
|
||||
{ NO_ITERATORS, "rocksdb.num.iterators" },
|
||||
{ NUMBER_MULTIGET_CALLS, "rocksdb.number.multiget.get" },
|
||||
{ NUMBER_MULTIGET_KEYS_READ, "rocksdb.number.multiget.keys.read" },
|
||||
{ NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read" }
|
||||
{ NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read" },
|
||||
{ NUMBER_FILTERED_DELETES, "rocksdb.number.deletes.filtered" }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -106,7 +109,13 @@ enum Histograms {
|
||||
READ_BLOCK_COMPACTION_MICROS = 9,
|
||||
READ_BLOCK_GET_MICROS = 10,
|
||||
WRITE_RAW_BLOCK_MICROS = 11,
|
||||
HISTOGRAM_ENUM_MAX = 12
|
||||
|
||||
STALL_L0_SLOWDOWN_COUNT = 12,
|
||||
STALL_MEMTABLE_COMPACTION_COUNT = 13,
|
||||
STALL_L0_NUM_FILES_COUNT = 14,
|
||||
HARD_RATE_LIMIT_DELAY_COUNT = 15,
|
||||
SOFT_RATE_LIMIT_DELAY_COUNT = 16,
|
||||
HISTOGRAM_ENUM_MAX = 17
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
@@ -121,7 +130,12 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{ DB_MULTIGET, "rocksdb.db.multiget.micros" },
|
||||
{ READ_BLOCK_COMPACTION_MICROS, "rocksdb.read.block.compaction.micros" },
|
||||
{ READ_BLOCK_GET_MICROS, "rocksdb.read.block.get.micros" },
|
||||
{ WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros" }
|
||||
{ WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros" },
|
||||
{ STALL_L0_SLOWDOWN_COUNT, "rocksdb.l0.slowdown.count"},
|
||||
{ STALL_MEMTABLE_COMPACTION_COUNT, "rocksdb.memtable.compaction.count"},
|
||||
{ STALL_L0_NUM_FILES_COUNT, "rocksdb.num.files.stall.count"},
|
||||
{ HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"},
|
||||
{ SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"}
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
|
||||
@@ -59,9 +59,12 @@ class WriteBatch {
|
||||
};
|
||||
Status Iterate(Handler* handler) const;
|
||||
|
||||
// Retrive the serialized version of this batch.
|
||||
// Retrieve the serialized version of this batch.
|
||||
std::string Data() { return rep_; }
|
||||
|
||||
// Returns the number of updates in the batch
|
||||
int Count() const;
|
||||
|
||||
// Constructor with a serialized string object
|
||||
WriteBatch(std::string rep): rep_(rep) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef LEVELDB_INCLUDE_UTILITIES_STACKABLE_DB_H_
|
||||
#define LEVELDB_INCLUDE_UTILITIES_STACKABLE_DB_H_
|
||||
|
||||
#include "leveldb/db.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// This class contains APIs to stack rocksdb wrappers.Eg. Stack TTL over base d
|
||||
class StackableDB : public DB {
|
||||
public:
|
||||
explicit StackableDB(StackableDB* sdb) : sdb_(sdb) {}
|
||||
|
||||
// Returns the DB object that is the lowermost component in the stack of DBs
|
||||
virtual DB* GetRawDB() {
|
||||
return sdb_->GetRawDB();
|
||||
}
|
||||
|
||||
// convert a DB to StackableDB
|
||||
static StackableDB* DBToStackableDB(DB* db) {
|
||||
class NewStackableDB : public StackableDB {
|
||||
public:
|
||||
NewStackableDB(DB* db)
|
||||
: StackableDB(nullptr),
|
||||
db_(db) {}
|
||||
|
||||
DB* GetRawDB() {
|
||||
return db_;
|
||||
}
|
||||
|
||||
private:
|
||||
DB* db_;
|
||||
};
|
||||
return new NewStackableDB(db);
|
||||
}
|
||||
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
const Slice& key,
|
||||
const Slice& val) override {
|
||||
return sdb_->Put(options, key, val);
|
||||
}
|
||||
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value) override {
|
||||
return sdb_->Get(options, key, value);
|
||||
}
|
||||
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values)
|
||||
override {
|
||||
return sdb_->MultiGet(options, keys, values);
|
||||
}
|
||||
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) override {
|
||||
return KeyMayExist(options, key, value, value_found);
|
||||
}
|
||||
|
||||
virtual Status Delete(const WriteOptions& wopts, const Slice& key) override {
|
||||
return sdb_->Delete(wopts, key);
|
||||
}
|
||||
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
const Slice& key,
|
||||
const Slice& value) override {
|
||||
return sdb_->Merge(options, key, value);
|
||||
}
|
||||
|
||||
|
||||
virtual Status Write(const WriteOptions& opts, WriteBatch* updates)
|
||||
override {
|
||||
return sdb_->Write(opts, updates);
|
||||
}
|
||||
|
||||
virtual Iterator* NewIterator(const ReadOptions& opts) override {
|
||||
return sdb_->NewIterator(opts);
|
||||
}
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override {
|
||||
return sdb_->GetSnapshot();
|
||||
}
|
||||
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
|
||||
return sdb_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
virtual bool GetProperty(const Slice& property, std::string* value)
|
||||
override {
|
||||
return sdb_->GetProperty(property, value);
|
||||
}
|
||||
|
||||
virtual void GetApproximateSizes(const Range* r, int n, uint64_t* sizes)
|
||||
override {
|
||||
return sdb_->GetApproximateSizes(r, n, sizes);
|
||||
}
|
||||
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false) override {
|
||||
return sdb_->CompactRange(begin, end, reduce_level);
|
||||
}
|
||||
|
||||
virtual int NumberLevels() override {
|
||||
return sdb_->NumberLevels();
|
||||
}
|
||||
|
||||
virtual int MaxMemCompactionLevel() override {
|
||||
return sdb_->MaxMemCompactionLevel();
|
||||
}
|
||||
|
||||
virtual int Level0StopWriteTrigger() override {
|
||||
return sdb_->Level0StopWriteTrigger();
|
||||
}
|
||||
|
||||
virtual Status Flush(const FlushOptions& fopts) override {
|
||||
return sdb_->Flush(fopts);
|
||||
}
|
||||
|
||||
virtual Status DisableFileDeletions() override {
|
||||
return sdb_->DisableFileDeletions();
|
||||
}
|
||||
|
||||
virtual Status EnableFileDeletions() override {
|
||||
return sdb_->EnableFileDeletions();
|
||||
}
|
||||
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs)
|
||||
override {
|
||||
return sdb_->GetLiveFiles(vec, mfs);
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() override {
|
||||
return sdb_->GetLatestSequenceNumber();
|
||||
}
|
||||
|
||||
virtual Status GetUpdatesSince(SequenceNumber seq_number,
|
||||
unique_ptr<TransactionLogIterator>* iter)
|
||||
override {
|
||||
return sdb_->GetUpdatesSince(seq_number, iter);
|
||||
}
|
||||
|
||||
protected:
|
||||
StackableDB* sdb_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // LEVELDB_INCLUDE_UTILITIES_STACKABLE_DB_H_
|
||||
@@ -5,7 +5,7 @@
|
||||
#ifndef LEVELDB_INCLUDE_UTILITIES_UTILITY_DB_H_
|
||||
#define LEVELDB_INCLUDE_UTILITIES_UTILITY_DB_H_
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "stackable_db.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -34,7 +34,6 @@ class UtilityDB {
|
||||
// triggered(neither manual nor automatic), so no expired entries removed
|
||||
//
|
||||
// CONSTRAINTS:
|
||||
// The caller must not specify any compaction-filter in options
|
||||
// Not specifying/passing or non-positive TTL behaves like TTL = infinity
|
||||
//
|
||||
// !!!WARNING!!!:
|
||||
@@ -45,7 +44,7 @@ class UtilityDB {
|
||||
// whole database may be deleted in a small amount of time
|
||||
static Status OpenTtlDB(const Options& options,
|
||||
const std::string& name,
|
||||
DB** dbptr,
|
||||
StackableDB** dbptr,
|
||||
int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
};
|
||||
|
||||
+19
-4
@@ -235,7 +235,8 @@ Iterator* Table::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value,
|
||||
bool* didIO,
|
||||
bool for_compaction) {
|
||||
bool for_compaction,
|
||||
const bool no_io) {
|
||||
Table* table = reinterpret_cast<Table*>(arg);
|
||||
Cache* block_cache = table->rep_->options.block_cache.get();
|
||||
std::shared_ptr<Statistics> statistics = table->rep_->options.statistics;
|
||||
@@ -264,6 +265,8 @@ Iterator* Table::BlockReader(void* arg,
|
||||
block = reinterpret_cast<Block*>(block_cache->Value(cache_handle));
|
||||
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
} else if (no_io) {
|
||||
return nullptr; // Did not find in block_cache and can't do IO
|
||||
} else {
|
||||
Histograms histogram = for_compaction ?
|
||||
READ_BLOCK_COMPACTION_MICROS : READ_BLOCK_GET_MICROS;
|
||||
@@ -286,7 +289,9 @@ Iterator* Table::BlockReader(void* arg,
|
||||
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
}
|
||||
} else {
|
||||
} else if (no_io) {
|
||||
return nullptr; // Could not read from block_cache and can't do IO
|
||||
}else {
|
||||
s = ReadBlock(table->rep_->file.get(), options, handle, &block, didIO);
|
||||
}
|
||||
}
|
||||
@@ -322,7 +327,9 @@ Iterator* Table::NewIterator(const ReadOptions& options) const {
|
||||
Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
void* arg,
|
||||
bool (*saver)(void*, const Slice&, const Slice&,
|
||||
bool)) {
|
||||
bool),
|
||||
void (*mark_key_may_exist)(void*),
|
||||
const bool no_io) {
|
||||
Status s;
|
||||
Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
|
||||
bool done = false;
|
||||
@@ -341,8 +348,16 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
} else {
|
||||
bool didIO = false;
|
||||
Iterator* block_iter = BlockReader(this, options, iiter->value(),
|
||||
&didIO);
|
||||
&didIO, no_io);
|
||||
|
||||
if (no_io && !block_iter) { // couldn't get block from block_cache
|
||||
// Update Saver.state to Found because we are only looking for whether
|
||||
// we can guarantee the key is not there when "no_io" is set
|
||||
(*mark_key_may_exist)(arg);
|
||||
break;
|
||||
}
|
||||
|
||||
// Call the *saver function on each entry/block until it returns false
|
||||
for (block_iter->Seek(k); block_iter->Valid(); block_iter->Next()) {
|
||||
if (!(*saver)(arg, block_iter->key(), block_iter->value(), didIO)) {
|
||||
done = true;
|
||||
|
||||
+5
-2
@@ -77,7 +77,8 @@ class Table {
|
||||
const EnvOptions& soptions, const Slice&,
|
||||
bool for_compaction);
|
||||
static Iterator* BlockReader(void*, const ReadOptions&, const Slice&,
|
||||
bool* didIO, bool for_compaction = false);
|
||||
bool* didIO, bool for_compaction = false,
|
||||
const bool no_io = false);
|
||||
|
||||
// Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found
|
||||
// after a call to Seek(key), until handle_result returns false.
|
||||
@@ -86,7 +87,9 @@ class Table {
|
||||
Status InternalGet(
|
||||
const ReadOptions&, const Slice& key,
|
||||
void* arg,
|
||||
bool (*handle_result)(void* arg, const Slice& k, const Slice& v, bool));
|
||||
bool (*handle_result)(void* arg, const Slice& k, const Slice& v, bool),
|
||||
void (*mark_key_may_exist)(void*) = nullptr,
|
||||
const bool no_io = false);
|
||||
|
||||
|
||||
void ReadMeta(const Footer& footer);
|
||||
|
||||
+9
-4
@@ -3,8 +3,10 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/skiplistrep.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
@@ -342,8 +344,9 @@ class MemTableConstructor: public Constructor {
|
||||
public:
|
||||
explicit MemTableConstructor(const Comparator* cmp)
|
||||
: Constructor(cmp),
|
||||
internal_comparator_(cmp) {
|
||||
memtable_ = new MemTable(internal_comparator_);
|
||||
internal_comparator_(cmp),
|
||||
table_factory_(new SkipListFactory) {
|
||||
memtable_ = new MemTable(internal_comparator_, table_factory_);
|
||||
memtable_->Ref();
|
||||
}
|
||||
~MemTableConstructor() {
|
||||
@@ -351,7 +354,7 @@ class MemTableConstructor: public Constructor {
|
||||
}
|
||||
virtual Status FinishImpl(const Options& options, const KVMap& data) {
|
||||
memtable_->Unref();
|
||||
memtable_ = new MemTable(internal_comparator_);
|
||||
memtable_ = new MemTable(internal_comparator_, table_factory_);
|
||||
memtable_->Ref();
|
||||
int seq = 1;
|
||||
for (KVMap::const_iterator it = data.begin();
|
||||
@@ -369,6 +372,7 @@ class MemTableConstructor: public Constructor {
|
||||
private:
|
||||
InternalKeyComparator internal_comparator_;
|
||||
MemTable* memtable_;
|
||||
std::shared_ptr<SkipListFactory> table_factory_;
|
||||
};
|
||||
|
||||
class DBConstructor: public Constructor {
|
||||
@@ -805,7 +809,8 @@ class MemTableTest { };
|
||||
|
||||
TEST(MemTableTest, Simple) {
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
MemTable* memtable = new MemTable(cmp);
|
||||
auto table_factory = std::make_shared<SkipListFactory>();
|
||||
MemTable* memtable = new MemTable(cmp, table_factory);
|
||||
memtable->Ref();
|
||||
WriteBatch batch;
|
||||
WriteBatchInternal::SetSequence(&batch, 100);
|
||||
|
||||
@@ -79,7 +79,8 @@ def main(argv):
|
||||
' --target_file_size_multiplier=2 ' + \
|
||||
' --max_write_buffer_number=3 ' + \
|
||||
' --max_background_compactions=20 ' + \
|
||||
' --max_bytes_for_level_base=10485760'
|
||||
' --max_bytes_for_level_base=10485760 ' + \
|
||||
' --filter_deletes=' + str(random.randint(0, 1))
|
||||
killtime = time.time() + interval
|
||||
child = subprocess.Popen(['./db_stress \
|
||||
--test_batches_snapshots=1 \
|
||||
|
||||
@@ -84,7 +84,8 @@ def main(argv):
|
||||
' --target_file_size_multiplier=2 ' + \
|
||||
' --max_write_buffer_number=3 ' + \
|
||||
' --max_background_compactions=20 ' + \
|
||||
' --max_bytes_for_level_base=10485760'
|
||||
' --max_bytes_for_level_base=10485760 ' + \
|
||||
' --filter_deletes=' + str(random.randint(0, 1))
|
||||
print ("Running db_stress with additional options=\n"
|
||||
+ additional_opts + "\n")
|
||||
|
||||
|
||||
+14
-6
@@ -180,6 +180,9 @@ static uint32_t FLAGS_log2_keys_per_lock = 2; // implies 2^2 keys per lock
|
||||
// Percentage of times we want to purge redundant keys in memory before flushing
|
||||
static uint32_t FLAGS_purge_redundant_percent = 50;
|
||||
|
||||
// On true, deletes use KeyMayExist to drop the delete if key not present
|
||||
static bool FLAGS_filter_deletes = false;
|
||||
|
||||
// Level0 compaction start trigger
|
||||
static int FLAGS_level0_file_num_compaction_trigger = 0;
|
||||
|
||||
@@ -898,8 +901,10 @@ class StressTest {
|
||||
fprintf(stdout, "Num times DB reopens: %d\n", FLAGS_reopen);
|
||||
fprintf(stdout, "Batches/snapshots : %d\n",
|
||||
FLAGS_test_batches_snapshots);
|
||||
fprintf(stdout, "Purge redundant %% : %d\n",
|
||||
fprintf(stdout, "Purge redundant %% : %d\n",
|
||||
FLAGS_purge_redundant_percent);
|
||||
fprintf(stdout, "Deletes use filter : %d\n",
|
||||
FLAGS_filter_deletes);
|
||||
fprintf(stdout, "Num keys per lock : %d\n",
|
||||
1 << FLAGS_log2_keys_per_lock);
|
||||
|
||||
@@ -955,6 +960,7 @@ class StressTest {
|
||||
options.delete_obsolete_files_period_micros =
|
||||
FLAGS_delete_obsolete_files_period_micros;
|
||||
options.max_manifest_file_size = 1024;
|
||||
options.filter_deletes = FLAGS_filter_deletes;
|
||||
static Random purge_percent(1000); // no benefit from non-determinism here
|
||||
if (purge_percent.Uniform(100) < FLAGS_purge_redundant_percent - 1) {
|
||||
options.purge_redundant_kvs_while_flush = false;
|
||||
@@ -966,7 +972,8 @@ class StressTest {
|
||||
if (FLAGS_ttl == -1) {
|
||||
s = DB::Open(options, FLAGS_db, &db_);
|
||||
} else {
|
||||
s = UtilityDB::OpenTtlDB(options, FLAGS_db, &db_, FLAGS_ttl);
|
||||
s = UtilityDB::OpenTtlDB(options, FLAGS_db, &sdb_, FLAGS_ttl);
|
||||
db_ = sdb_;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "open error: %s\n", s.ToString().c_str());
|
||||
@@ -994,10 +1001,7 @@ class StressTest {
|
||||
|
||||
void PrintStatistics() {
|
||||
if (dbstats) {
|
||||
fprintf(stdout, "File opened:%ld closed:%ld errors:%ld\n",
|
||||
dbstats->getTickerCount(NO_FILE_OPENS),
|
||||
dbstats->getTickerCount(NO_FILE_CLOSES),
|
||||
dbstats->getTickerCount(NO_FILE_ERRORS));
|
||||
fprintf(stdout, "STATISTICS:\n%s\n", dbstats->ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,6 +1009,7 @@ class StressTest {
|
||||
shared_ptr<Cache> cache_;
|
||||
const FilterPolicy* filter_policy_;
|
||||
DB* db_;
|
||||
StackableDB* sdb_;
|
||||
int num_times_reopened_;
|
||||
};
|
||||
|
||||
@@ -1157,6 +1162,9 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--purge_redundant_percent=%d%c", &n, &junk) == 1
|
||||
&& (n >= 0 && n <= 100)) {
|
||||
FLAGS_purge_redundant_percent = n;
|
||||
} else if (sscanf(argv[i], "--filter_deletes=%d%c", &n, &junk)
|
||||
== 1 && (n == 0 || n == 1)) {
|
||||
FLAGS_filter_deletes = n;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
|
||||
@@ -2,27 +2,32 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/arena.h"
|
||||
#include <assert.h>
|
||||
#include "util/arena_impl.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kBlockSize = 4096;
|
||||
ArenaImpl::ArenaImpl(size_t block_size) {
|
||||
if (block_size < kMinBlockSize) {
|
||||
block_size_ = kMinBlockSize;
|
||||
} else if (block_size > kMaxBlockSize) {
|
||||
block_size_ = kMaxBlockSize;
|
||||
} else {
|
||||
block_size_ = block_size;
|
||||
}
|
||||
|
||||
Arena::Arena() {
|
||||
blocks_memory_ = 0;
|
||||
alloc_ptr_ = nullptr; // First allocation will allocate a block
|
||||
alloc_bytes_remaining_ = 0;
|
||||
}
|
||||
|
||||
Arena::~Arena() {
|
||||
ArenaImpl::~ArenaImpl() {
|
||||
for (size_t i = 0; i < blocks_.size(); i++) {
|
||||
delete[] blocks_[i];
|
||||
}
|
||||
}
|
||||
|
||||
char* Arena::AllocateFallback(size_t bytes) {
|
||||
if (bytes > kBlockSize / 4) {
|
||||
char* ArenaImpl::AllocateFallback(size_t bytes) {
|
||||
if (bytes > block_size_ / 4) {
|
||||
// Object is more than a quarter of our block size. Allocate it separately
|
||||
// to avoid wasting too much space in leftover bytes.
|
||||
char* result = AllocateNewBlock(bytes);
|
||||
@@ -30,8 +35,8 @@ char* Arena::AllocateFallback(size_t bytes) {
|
||||
}
|
||||
|
||||
// We waste the remaining space in the current block.
|
||||
alloc_ptr_ = AllocateNewBlock(kBlockSize);
|
||||
alloc_bytes_remaining_ = kBlockSize;
|
||||
alloc_ptr_ = AllocateNewBlock(block_size_);
|
||||
alloc_bytes_remaining_ = block_size_;
|
||||
|
||||
char* result = alloc_ptr_;
|
||||
alloc_ptr_ += bytes;
|
||||
@@ -39,7 +44,7 @@ char* Arena::AllocateFallback(size_t bytes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Arena::AllocateAligned(size_t bytes) {
|
||||
char* ArenaImpl::AllocateAligned(size_t bytes) {
|
||||
const int align = sizeof(void*); // We'll align to pointer size
|
||||
assert((align & (align-1)) == 0); // Pointer size should be a power of 2
|
||||
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
|
||||
@@ -58,7 +63,7 @@ char* Arena::AllocateAligned(size_t bytes) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Arena::AllocateNewBlock(size_t block_bytes) {
|
||||
char* ArenaImpl::AllocateNewBlock(size_t block_bytes) {
|
||||
char* result = new char[block_bytes];
|
||||
blocks_memory_ += block_bytes;
|
||||
blocks_.push_back(result);
|
||||
@@ -2,38 +2,53 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
// ArenaImpl is an implementation of Arena class. For a request of small size,
|
||||
// it allocates a block with pre-defined block size. For a request of big
|
||||
// size, it uses malloc to directly get the requested size.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ARENA_IMPL_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ARENA_IMPL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include "leveldb/arena.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Arena {
|
||||
class ArenaImpl : public Arena {
|
||||
public:
|
||||
Arena();
|
||||
~Arena();
|
||||
explicit ArenaImpl(size_t block_size = kMinBlockSize);
|
||||
virtual ~ArenaImpl();
|
||||
|
||||
// Return a pointer to a newly allocated memory block of "bytes" bytes.
|
||||
char* Allocate(size_t bytes);
|
||||
virtual char* Allocate(size_t bytes);
|
||||
|
||||
// Allocate memory with the normal alignment guarantees provided by malloc
|
||||
char* AllocateAligned(size_t bytes);
|
||||
virtual char* AllocateAligned(size_t bytes);
|
||||
|
||||
// Returns an estimate of the total memory usage of data allocated
|
||||
// by the arena (including space allocated but not yet used for user
|
||||
// allocations).
|
||||
size_t MemoryUsage() const {
|
||||
//
|
||||
// TODO: Do we need to exclude space allocated but not used?
|
||||
virtual const size_t ApproximateMemoryUsage() {
|
||||
return blocks_memory_ + blocks_.capacity() * sizeof(char*);
|
||||
}
|
||||
|
||||
virtual const size_t MemoryAllocatedBytes() {
|
||||
return blocks_memory_;
|
||||
}
|
||||
|
||||
private:
|
||||
char* AllocateFallback(size_t bytes);
|
||||
char* AllocateNewBlock(size_t block_bytes);
|
||||
|
||||
static const size_t kMinBlockSize = 4096;
|
||||
static const size_t kMaxBlockSize = 2 << 30;
|
||||
|
||||
// Number of bytes allocated in one block
|
||||
size_t block_size_;
|
||||
|
||||
// Allocation state
|
||||
char* alloc_ptr_;
|
||||
size_t alloc_bytes_remaining_;
|
||||
@@ -45,11 +60,11 @@ class Arena {
|
||||
size_t blocks_memory_;
|
||||
|
||||
// No copying allowed
|
||||
Arena(const Arena&);
|
||||
void operator=(const Arena&);
|
||||
ArenaImpl(const ArenaImpl&);
|
||||
void operator=(const ArenaImpl&);
|
||||
};
|
||||
|
||||
inline char* Arena::Allocate(size_t bytes) {
|
||||
inline char* ArenaImpl::Allocate(size_t bytes) {
|
||||
// The semantics of what to return are a bit messy if we allow
|
||||
// 0-byte allocations, so we disallow them here (we don't need
|
||||
// them for our internal use).
|
||||
@@ -65,4 +80,4 @@ inline char* Arena::Allocate(size_t bytes) {
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
#endif // STORAGE_LEVELDB_UTIL_ARENA_IMPL_H_
|
||||
+48
-11
@@ -2,22 +2,59 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/arena.h"
|
||||
|
||||
#include "util/arena_impl.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class ArenaTest { };
|
||||
class ArenaImplTest { };
|
||||
|
||||
TEST(ArenaTest, Empty) {
|
||||
Arena arena;
|
||||
TEST(ArenaImplTest, Empty) {
|
||||
ArenaImpl arena0;
|
||||
}
|
||||
|
||||
TEST(ArenaTest, Simple) {
|
||||
TEST(ArenaImplTest, MemoryAllocatedBytes) {
|
||||
const int N = 17;
|
||||
size_t req_sz; //requested size
|
||||
size_t bsz = 8192; // block size
|
||||
size_t expected_memory_allocated;
|
||||
|
||||
ArenaImpl arena_impl(bsz);
|
||||
|
||||
// requested size > quarter of a block:
|
||||
// allocate requested size separately
|
||||
req_sz = 3001;
|
||||
for (int i = 0; i < N; i++) {
|
||||
arena_impl.Allocate(req_sz);
|
||||
}
|
||||
expected_memory_allocated = req_sz * N;
|
||||
ASSERT_EQ(arena_impl.MemoryAllocatedBytes(), expected_memory_allocated);
|
||||
|
||||
// requested size < quarter of a block:
|
||||
// allocate a block with the default size, then try to use unused part
|
||||
// of the block. So one new block will be allocated for the first
|
||||
// Allocate(99) call. All the remaining calls won't lead to new allocation.
|
||||
req_sz = 99;
|
||||
for (int i = 0; i < N; i++) {
|
||||
arena_impl.Allocate(req_sz);
|
||||
}
|
||||
expected_memory_allocated += bsz;
|
||||
ASSERT_EQ(arena_impl.MemoryAllocatedBytes(), expected_memory_allocated);
|
||||
|
||||
// requested size > quarter of a block:
|
||||
// allocate requested size separately
|
||||
req_sz = 99999999;
|
||||
for (int i = 0; i < N; i++) {
|
||||
arena_impl.Allocate(req_sz);
|
||||
}
|
||||
expected_memory_allocated += req_sz * N;
|
||||
ASSERT_EQ(arena_impl.MemoryAllocatedBytes(), expected_memory_allocated);
|
||||
}
|
||||
|
||||
TEST(ArenaImplTest, Simple) {
|
||||
std::vector<std::pair<size_t, char*> > allocated;
|
||||
Arena arena;
|
||||
ArenaImpl arena_impl;
|
||||
const int N = 100000;
|
||||
size_t bytes = 0;
|
||||
Random rnd(301);
|
||||
@@ -35,9 +72,9 @@ TEST(ArenaTest, Simple) {
|
||||
}
|
||||
char* r;
|
||||
if (rnd.OneIn(10)) {
|
||||
r = arena.AllocateAligned(s);
|
||||
r = arena_impl.AllocateAligned(s);
|
||||
} else {
|
||||
r = arena.Allocate(s);
|
||||
r = arena_impl.Allocate(s);
|
||||
}
|
||||
|
||||
for (unsigned int b = 0; b < s; b++) {
|
||||
@@ -46,9 +83,9 @@ TEST(ArenaTest, Simple) {
|
||||
}
|
||||
bytes += s;
|
||||
allocated.push_back(std::make_pair(s, r));
|
||||
ASSERT_GE(arena.MemoryUsage(), bytes);
|
||||
ASSERT_GE(arena_impl.ApproximateMemoryUsage(), bytes);
|
||||
if (i > N/10) {
|
||||
ASSERT_LE(arena.MemoryUsage(), bytes * 1.10);
|
||||
ASSERT_LE(arena_impl.ApproximateMemoryUsage(), bytes * 1.10);
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < allocated.size(); i++) {
|
||||
|
||||
+141
-13
@@ -5,6 +5,7 @@
|
||||
#include "util/ldb_cmd.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -45,7 +46,7 @@ const char* LDBCommand::DELIM = " ==> ";
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
int argc,
|
||||
char** argv,
|
||||
Options options
|
||||
const Options& options
|
||||
) {
|
||||
vector<string> args;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@@ -66,7 +67,7 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
*/
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
const vector<string>& args,
|
||||
Options options
|
||||
const Options& options
|
||||
) {
|
||||
// --x=y command line arguments are added as x->y map entries.
|
||||
map<string, string> option_map;
|
||||
@@ -80,9 +81,7 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
|
||||
const string OPTION_PREFIX = "--";
|
||||
|
||||
for (vector<string>::const_iterator itr = args.begin();
|
||||
itr != args.end(); itr++) {
|
||||
string arg = *itr;
|
||||
for (const auto& arg : args) {
|
||||
if (arg[0] == '-' && arg[1] == '-'){
|
||||
vector<string> splits = stringSplit(arg, '=');
|
||||
if (splits.size() == 2) {
|
||||
@@ -93,7 +92,7 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
flags.push_back(optionKey);
|
||||
}
|
||||
} else {
|
||||
cmdTokens.push_back(string(arg));
|
||||
cmdTokens.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +118,9 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
|
||||
LDBCommand* LDBCommand::SelectCommand(
|
||||
const std::string& cmd,
|
||||
vector<string>& cmdParams,
|
||||
map<string, string>& option_map,
|
||||
vector<string>& flags
|
||||
const vector<string>& cmdParams,
|
||||
const map<string, string>& option_map,
|
||||
const vector<string>& flags
|
||||
) {
|
||||
|
||||
if (cmd == GetCommand::Name()) {
|
||||
@@ -150,6 +149,8 @@ LDBCommand* LDBCommand::SelectCommand(
|
||||
return new DBLoaderCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == ManifestDumpCommand::Name()) {
|
||||
return new ManifestDumpCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == InternalDumpCommand::Name()) {
|
||||
return new InternalDumpCommand(cmdParams, option_map, flags);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -163,7 +164,8 @@ LDBCommand* LDBCommand::SelectCommand(
|
||||
* updated.
|
||||
*/
|
||||
bool LDBCommand::ParseIntOption(const map<string, string>& options,
|
||||
string option, int& value, LDBCommandExecuteResult& exec_state) {
|
||||
const string& option, int& value,
|
||||
LDBCommandExecuteResult& exec_state) {
|
||||
|
||||
map<string, string>::const_iterator itr = option_map_.find(option);
|
||||
if (itr != option_map_.end()) {
|
||||
@@ -181,6 +183,21 @@ bool LDBCommand::ParseIntOption(const map<string, string>& options,
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the specified option and fills in the value.
|
||||
* Returns true if the option is found.
|
||||
* Returns false otherwise.
|
||||
*/
|
||||
bool LDBCommand::ParseStringOption(const map<string, string>& options,
|
||||
const string& option, string* value) {
|
||||
auto itr = option_map_.find(option);
|
||||
if (itr != option_map_.end()) {
|
||||
*value = itr->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
|
||||
Options opt = options_;
|
||||
@@ -453,7 +470,7 @@ void ManifestDumpCommand::Help(string& ret) {
|
||||
ManifestDumpCommand::ManifestDumpCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, false,
|
||||
BuildCmdLineOptions({ARG_VERBOSE,ARG_PATH})),
|
||||
BuildCmdLineOptions({ARG_VERBOSE, ARG_PATH, ARG_HEX})),
|
||||
verbose_(false),
|
||||
path_("")
|
||||
{
|
||||
@@ -559,6 +576,115 @@ void PrintBucketCounts(const vector<uint64_t>& bucket_counts, int ttl_start,
|
||||
ReadableTime(ttl_end).c_str(), bucket_counts[num_buckets - 1]);
|
||||
}
|
||||
|
||||
const string InternalDumpCommand::ARG_COUNT_ONLY = "count_only";
|
||||
const string InternalDumpCommand::ARG_STATS = "stats";
|
||||
|
||||
InternalDumpCommand::InternalDumpCommand(const vector<string>& params,
|
||||
const map<string, string>& options,
|
||||
const vector<string>& flags) :
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_FROM, ARG_TO, ARG_MAX_KEYS,
|
||||
ARG_COUNT_ONLY, ARG_STATS})),
|
||||
has_from_(false),
|
||||
has_to_(false),
|
||||
max_keys_(-1),
|
||||
count_only_(false),
|
||||
print_stats_(false) {
|
||||
|
||||
has_from_ = ParseStringOption(options, ARG_FROM, &from_);
|
||||
has_to_ = ParseStringOption(options, ARG_TO, &to_);
|
||||
|
||||
ParseIntOption(options, ARG_MAX_KEYS, max_keys_, exec_state_);
|
||||
|
||||
print_stats_ = IsFlagPresent(flags, ARG_STATS);
|
||||
count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY);
|
||||
|
||||
if (is_key_hex_) {
|
||||
if (has_from_) {
|
||||
from_ = HexToString(from_);
|
||||
}
|
||||
if (has_to_) {
|
||||
to_ = HexToString(to_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InternalDumpCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(InternalDumpCommand::Name());
|
||||
ret.append(HelpRangeCmdArgs());
|
||||
ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
|
||||
ret.append(" [--" + ARG_COUNT_ONLY + "]");
|
||||
ret.append(" [--" + ARG_STATS + "]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
void InternalDumpCommand::DoCommand() {
|
||||
if (!db_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (print_stats_) {
|
||||
string stats;
|
||||
if (db_->GetProperty("leveldb.stats", &stats)) {
|
||||
fprintf(stdout, "%s\n", stats.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Cast as DBImpl to get internal iterator
|
||||
DBImpl* idb = dynamic_cast<DBImpl*>(db_);
|
||||
if (!idb) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("DB is not DBImpl");
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup internal key iterator
|
||||
auto iter = unique_ptr<Iterator>(idb->TEST_NewInternalIterator());
|
||||
Status st = iter->status();
|
||||
if (!st.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("Iterator error:"
|
||||
+ st.ToString());
|
||||
}
|
||||
|
||||
if (has_from_) {
|
||||
InternalKey ikey(from_, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
iter->Seek(ikey.Encode());
|
||||
} else {
|
||||
iter->SeekToFirst();
|
||||
}
|
||||
|
||||
long long count = 0;
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(iter->key(), &ikey)) {
|
||||
fprintf(stderr, "Internal Key [%s] parse error!\n",
|
||||
iter->key().ToString(true /* in hex*/).data());
|
||||
// TODO: add error counter
|
||||
continue;
|
||||
}
|
||||
|
||||
// If end marker was specified, we stop before it
|
||||
if (has_to_ && options_.comparator->Compare(ikey.user_key, to_) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
++count;
|
||||
|
||||
if (!count_only_) {
|
||||
string key = ikey.DebugString(is_key_hex_);
|
||||
string value = iter->value().ToString(is_value_hex_);
|
||||
fprintf(stdout, "%s => %s\n", key.data(), value.data());
|
||||
}
|
||||
|
||||
// Terminate if maximum number of keys have been dumped
|
||||
if (max_keys_ > 0 && count >= max_keys_) break;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Internal keys in range: %lld\n", (long long) count);
|
||||
}
|
||||
|
||||
|
||||
const string DBDumperCommand::ARG_COUNT_ONLY = "count_only";
|
||||
const string DBDumperCommand::ARG_STATS = "stats";
|
||||
const string DBDumperCommand::ARG_TTL_BUCKET = "bucket";
|
||||
@@ -695,7 +821,8 @@ void DBDumperCommand::DoCommand() {
|
||||
if (max_keys == 0)
|
||||
break;
|
||||
if (is_db_ttl_) {
|
||||
TtlIterator* it_ttl = (TtlIterator*)iter;
|
||||
TtlIterator* it_ttl = dynamic_cast<TtlIterator*>(iter);
|
||||
assert(it_ttl);
|
||||
rawtime = it_ttl->timestamp();
|
||||
if (rawtime < ttl_start || rawtime >= ttl_end) {
|
||||
continue;
|
||||
@@ -1216,7 +1343,8 @@ void ScanCommand::DoCommand() {
|
||||
it->Next()) {
|
||||
string key = it->key().ToString();
|
||||
if (is_db_ttl_) {
|
||||
TtlIterator* it_ttl = (TtlIterator*)it;
|
||||
TtlIterator* it_ttl = dynamic_cast<TtlIterator*>(it);
|
||||
assert(it_ttl);
|
||||
int rawtime = it_ttl->timestamp();
|
||||
if (rawtime < ttl_start || rawtime >= ttl_end) {
|
||||
continue;
|
||||
|
||||
+42
-10
@@ -55,13 +55,13 @@ public:
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
const vector<string>& args,
|
||||
Options options = Options()
|
||||
const Options& options = Options()
|
||||
);
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
int argc,
|
||||
char** argv,
|
||||
Options options = Options()
|
||||
const Options& options = Options()
|
||||
);
|
||||
|
||||
bool ValidateCmdLineOptions();
|
||||
@@ -150,6 +150,7 @@ protected:
|
||||
LDBCommandExecuteResult exec_state_;
|
||||
string db_path_;
|
||||
DB* db_;
|
||||
StackableDB* sdb_;
|
||||
|
||||
/**
|
||||
* true implies that this command can work if the db is opened in read-only
|
||||
@@ -217,10 +218,11 @@ protected:
|
||||
Status st;
|
||||
if (is_db_ttl_) {
|
||||
if (is_read_only_) {
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &db_, 0, true);
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &sdb_, 0, true);
|
||||
} else {
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &db_);
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &sdb_);
|
||||
}
|
||||
db_ = sdb_;
|
||||
} else if (is_read_only_) {
|
||||
st = DB::OpenForReadOnly(opt, db_path_, &db_);
|
||||
} else {
|
||||
@@ -230,6 +232,8 @@ protected:
|
||||
string msg = st.ToString();
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(msg);
|
||||
}
|
||||
|
||||
options_ = opt;
|
||||
}
|
||||
|
||||
void CloseDB () {
|
||||
@@ -281,13 +285,16 @@ protected:
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ParseIntOption(const map<string, string>& options, string option,
|
||||
int& value, LDBCommandExecuteResult& exec_state);
|
||||
bool ParseIntOption(const map<string, string>& options, const string& option,
|
||||
int& value, LDBCommandExecuteResult& exec_state);
|
||||
|
||||
private:
|
||||
bool ParseStringOption(const map<string, string>& options,
|
||||
const string& option, string* value);
|
||||
|
||||
Options options_;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the key
|
||||
* should be input/output in hex.
|
||||
@@ -347,9 +354,9 @@ private:
|
||||
|
||||
static LDBCommand* SelectCommand(
|
||||
const string& cmd,
|
||||
vector<string>& cmdParams,
|
||||
map<string, string>& option_map,
|
||||
vector<string>& flags
|
||||
const vector<string>& cmdParams,
|
||||
const map<string, string>& option_map,
|
||||
const vector<string>& flags
|
||||
);
|
||||
|
||||
};
|
||||
@@ -397,6 +404,31 @@ private:
|
||||
static const string ARG_TTL_BUCKET;
|
||||
};
|
||||
|
||||
class InternalDumpCommand: public LDBCommand {
|
||||
public:
|
||||
static string Name() { return "idump"; }
|
||||
|
||||
InternalDumpCommand(const vector<string>& params,
|
||||
const map<string, string>& options,
|
||||
const vector<string>& flags);
|
||||
|
||||
static void Help(string& ret);
|
||||
|
||||
virtual void DoCommand();
|
||||
|
||||
private:
|
||||
bool has_from_;
|
||||
string from_;
|
||||
bool has_to_;
|
||||
string to_;
|
||||
int max_keys_;
|
||||
bool count_only_;
|
||||
bool print_stats_;
|
||||
|
||||
static const string ARG_COUNT_ONLY;
|
||||
static const string ARG_STATS;
|
||||
};
|
||||
|
||||
class DBLoaderCommand: public LDBCommand {
|
||||
public:
|
||||
static string Name() { return "load"; }
|
||||
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
DBDumperCommand::Help(ret);
|
||||
DBLoaderCommand::Help(ret);
|
||||
ManifestDumpCommand::Help(ret);
|
||||
InternalDumpCommand::Help(ret);
|
||||
|
||||
fprintf(stderr, "%s\n", ret.c_str());
|
||||
}
|
||||
|
||||
+17
-16
@@ -12,6 +12,7 @@
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/filter_policy.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "db/skiplistrep.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -55,18 +56,18 @@ Options::Options()
|
||||
max_log_file_size(0),
|
||||
log_file_time_to_roll(0),
|
||||
keep_log_file_num(1000),
|
||||
rate_limit(0.0),
|
||||
rate_limit_delay_milliseconds(1000),
|
||||
soft_rate_limit(0.0),
|
||||
hard_rate_limit(0.0),
|
||||
rate_limit_delay_max_milliseconds(1000),
|
||||
max_manifest_file_size(std::numeric_limits<uint64_t>::max()),
|
||||
no_block_cache(false),
|
||||
table_cache_numshardbits(4),
|
||||
arena_block_size(0),
|
||||
disable_auto_compactions(false),
|
||||
WAL_ttl_seconds(0),
|
||||
manifest_preallocation_size(4 * 1024 * 1024),
|
||||
purge_redundant_kvs_while_flush(true),
|
||||
allow_os_buffer(true),
|
||||
allow_readahead(true),
|
||||
allow_readahead_compactions(true),
|
||||
allow_mmap_reads(false),
|
||||
allow_mmap_writes(true),
|
||||
is_fd_close_on_exec(true),
|
||||
@@ -76,7 +77,10 @@ Options::Options()
|
||||
advise_random_on_open(true),
|
||||
access_hint_on_compaction_start(NORMAL),
|
||||
use_adaptive_mutex(false),
|
||||
bytes_per_sync(0) {
|
||||
bytes_per_sync(0),
|
||||
filter_deletes(false),
|
||||
memtable_factory(std::shared_ptr<SkipListFactory>(new SkipListFactory)) {
|
||||
assert(memtable_factory.get() != nullptr);
|
||||
}
|
||||
|
||||
static const char* const access_hints[] = {
|
||||
@@ -126,13 +130,10 @@ Options::Dump(Logger* log) const
|
||||
Log(log," Options.db_stats_log_interval: %d",
|
||||
db_stats_log_interval);
|
||||
Log(log," Options.allow_os_buffer: %d", allow_os_buffer);
|
||||
Log(log," Options.allow_readahead: %d", allow_readahead);
|
||||
Log(log," Options.allow_mmap_reads: %d", allow_mmap_reads);
|
||||
Log(log," Options.allow_mmap_writes: %d", allow_mmap_writes);
|
||||
Log(log," Options.min_write_buffer_number_to_merge: %d",
|
||||
min_write_buffer_number_to_merge);
|
||||
Log(log," Options.allow_readahead_compactions: %d",
|
||||
allow_readahead_compactions);
|
||||
Log(log," Options.purge_redundant_kvs_while_flush: %d",
|
||||
purge_redundant_kvs_while_flush);
|
||||
Log(log," Options.compression_opts.window_bits: %d",
|
||||
@@ -175,14 +176,16 @@ Options::Dump(Logger* log) const
|
||||
no_block_cache);
|
||||
Log(log," Options.table_cache_numshardbits: %d",
|
||||
table_cache_numshardbits);
|
||||
Log(log," Options.arena_block_size: %ld",
|
||||
arena_block_size);
|
||||
Log(log," Options.delete_obsolete_files_period_micros: %ld",
|
||||
delete_obsolete_files_period_micros);
|
||||
Log(log," Options.max_background_compactions: %d",
|
||||
max_background_compactions);
|
||||
Log(log," Options.rate_limit: %.2f",
|
||||
rate_limit);
|
||||
Log(log," Options.rate_limit_delay_milliseconds: %d",
|
||||
rate_limit_delay_milliseconds);
|
||||
Log(log," Options.hard_rate_limit: %.2f",
|
||||
hard_rate_limit);
|
||||
Log(log," Options.rate_limit_delay_max_milliseconds: %d",
|
||||
rate_limit_delay_max_milliseconds);
|
||||
Log(log," Options.disable_auto_compactions: %d",
|
||||
disable_auto_compactions);
|
||||
Log(log," Options.WAL_ttl_seconds: %ld",
|
||||
@@ -193,10 +196,6 @@ Options::Dump(Logger* log) const
|
||||
purge_redundant_kvs_while_flush);
|
||||
Log(log," Options.allow_os_buffer: %d",
|
||||
allow_os_buffer);
|
||||
Log(log," Options.allow_readahead: %d",
|
||||
allow_readahead);
|
||||
Log(log," Options.allow_readahead_compactions: %d",
|
||||
allow_readahead_compactions);
|
||||
Log(log," Options.allow_mmap_reads: %d",
|
||||
allow_mmap_reads);
|
||||
Log(log," Options.allow_mmap_writes: %d",
|
||||
@@ -217,6 +216,8 @@ Options::Dump(Logger* log) const
|
||||
use_adaptive_mutex);
|
||||
Log(log," Options.bytes_per_sync: %ld",
|
||||
bytes_per_sync);
|
||||
Log(log," Options.filter_deletes: %d",
|
||||
filter_deletes);
|
||||
} // Options::Dump
|
||||
|
||||
//
|
||||
|
||||
@@ -8,15 +8,34 @@ using namespace leveldb;
|
||||
namespace { // anonymous namespace
|
||||
|
||||
// A merge operator that mimics Put semantics
|
||||
// Since this merge-operator will not be used in production,
|
||||
// it is implemented as a non-associative merge operator to illustrate the
|
||||
// new interface and for testing purposes. (That is, we inherit from
|
||||
// the MergeOperator class rather than the AssociativeMergeOperator
|
||||
// which would be simpler in this case).
|
||||
//
|
||||
// From the client-perspective, semantics are the same.
|
||||
class PutOperator : public MergeOperator {
|
||||
public:
|
||||
virtual void Merge(const Slice& key,
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
const std::deque<std::string>& operand_sequence,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
// put basically only looks at the current value
|
||||
new_value->assign(value.data(), value.size());
|
||||
// Put basically only looks at the current/latest value
|
||||
assert(!operand_sequence.empty());
|
||||
assert(new_value != nullptr);
|
||||
new_value->assign(operand_sequence.back());
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
new_value->assign(right_operand.data(), right_operand.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
|
||||
// Constructor: also specify the delimiter character.
|
||||
StringAppendOperator::StringAppendOperator(char delim_char)
|
||||
: delim_(delim_char) {
|
||||
}
|
||||
|
||||
// Implementation for the merge operation (concatenates two strings)
|
||||
void StringAppendOperator::Merge(const Slice& key,
|
||||
bool StringAppendOperator::Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
@@ -43,6 +42,8 @@ void StringAppendOperator::Merge(const Slice& key,
|
||||
new_value->append(1,delim_);
|
||||
new_value->append(value.data(), value.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* StringAppendOperator::Name() const {
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class StringAppendOperator : public MergeOperator {
|
||||
class StringAppendOperator : public AssociativeMergeOperator {
|
||||
public:
|
||||
|
||||
StringAppendOperator(char delim_char); /// Constructor: specify delimiter
|
||||
|
||||
virtual void Merge(const Slice& key,
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
@@ -23,7 +23,7 @@ class StringAppendOperator : public MergeOperator {
|
||||
virtual const char* Name() const override;
|
||||
|
||||
private:
|
||||
char delim_; // The delimiter is inserted between elements
|
||||
char delim_; // The delimiter is inserted between elements
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @author Deon Nicholas (dnicholas@fb.com)
|
||||
* Copyright 2013 Facebook
|
||||
*/
|
||||
|
||||
#include "stringappend2.h"
|
||||
|
||||
#include <memory>
|
||||
#include <assert.h>
|
||||
|
||||
#include "leveldb/slice.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Constructor: also specify the delimiter character.
|
||||
StringAppendTESTOperator::StringAppendTESTOperator(char delim_char)
|
||||
: delim_(delim_char) {
|
||||
}
|
||||
|
||||
// Implementation for the merge operation (concatenates two strings)
|
||||
bool StringAppendTESTOperator::Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const std::deque<std::string>& operands,
|
||||
std::string* new_value,
|
||||
Logger* logger) const {
|
||||
|
||||
// Clear the *new_value for writing.
|
||||
assert(new_value);
|
||||
new_value->clear();
|
||||
|
||||
// Compute the space needed for the final result.
|
||||
int numBytes = 0;
|
||||
for(auto it = operands.begin(); it != operands.end(); ++it) {
|
||||
numBytes += it->size() + 1; // Plus 1 for the delimiter
|
||||
}
|
||||
|
||||
// Only print the delimiter after the first entry has been printed
|
||||
bool printDelim = false;
|
||||
|
||||
// Prepend the *existing_value if one exists.
|
||||
if (existing_value) {
|
||||
new_value->reserve(numBytes + existing_value->size());
|
||||
new_value->append(existing_value->data(), existing_value->size());
|
||||
printDelim = true;
|
||||
} else if (numBytes) {
|
||||
new_value->reserve(numBytes-1); // Minus 1 since we have one less delimiter
|
||||
}
|
||||
|
||||
// Concatenate the sequence of strings (and add a delimiter between each)
|
||||
for(auto it = operands.begin(); it != operands.end(); ++it) {
|
||||
if (printDelim) {
|
||||
new_value->append(1,delim_);
|
||||
}
|
||||
new_value->append(*it);
|
||||
printDelim = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StringAppendTESTOperator::PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const {
|
||||
return false;
|
||||
|
||||
// // Clear the *new_value for writing.
|
||||
// assert(new_value);
|
||||
// new_value->clear();
|
||||
//
|
||||
// // Generic append
|
||||
// // Reserve correct size for *new_value, and apply concatenation.
|
||||
// new_value->reserve(left_operand.size() + 1 + right_operand.size());
|
||||
// new_value->assign(left_operand.data(), left_operand.size());
|
||||
// new_value->append(1,delim_);
|
||||
// new_value->append(right_operand.data(), right_operand.size());
|
||||
//
|
||||
// return true;
|
||||
|
||||
}
|
||||
|
||||
const char* StringAppendTESTOperator::Name() const {
|
||||
return "StringAppendTESTOperator";
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* A TEST MergeOperator for rocksdb/leveldb that implements string append.
|
||||
* It is built using the MergeOperator interface rather than the simpler
|
||||
* AssociativeMergeOperator interface. This is useful for testing/benchmarking.
|
||||
* While the two operators are semantically the same, all production code
|
||||
* should use the StringAppendOperator defined in stringappend.{h,cc}. The
|
||||
* operator defined in the present file is primarily for testing.
|
||||
*
|
||||
* @author Deon Nicholas (dnicholas@fb.com)
|
||||
* Copyright 2013 Facebook
|
||||
*/
|
||||
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "leveldb/slice.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class StringAppendTESTOperator : public MergeOperator {
|
||||
public:
|
||||
|
||||
StringAppendTESTOperator(char delim_char); /// Constructor with delimiter
|
||||
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const std::deque<std::string>& operand_sequence,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override;
|
||||
|
||||
virtual const char* Name() const override;
|
||||
|
||||
private:
|
||||
char delim_; // The delimiter is inserted between elements
|
||||
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -20,7 +20,8 @@ using namespace leveldb;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
const std::string kDbName = "/tmp/mergetestdb"; // Path to the database on file system
|
||||
// Path to the database on file system
|
||||
const std::string kDbName = "/tmp/mergetestdb";
|
||||
|
||||
// OpenDb opens a (possibly new) rocksdb database with a StringAppendOperator
|
||||
std::shared_ptr<DB> OpenDb(StringAppendOperator* append_op) {
|
||||
@@ -251,7 +252,7 @@ TEST(StringAppendOperatorTest,BIGRandomMixGetAppend) {
|
||||
|
||||
// Generate a bunch of random queries (Append and Get)!
|
||||
enum query_t { APPEND_OP, GET_OP, NUM_OPS };
|
||||
Random randomGen(9138204); //deterministic seed; always get same results!
|
||||
Random randomGen(9138204); // deterministic seed
|
||||
|
||||
const int kNumQueries = 1000;
|
||||
for (int q=0; q<kNumQueries; ++q) {
|
||||
@@ -329,6 +330,26 @@ TEST(StringAppendOperatorTest,PersistentVariousKeys) {
|
||||
slists.Append("b","l;");
|
||||
slists.Append("c","rogosh");
|
||||
|
||||
// The previous changes should be on disk (L0)
|
||||
// The most recent changes should be in memory (MemTable)
|
||||
// Hence, this will test both Get() paths.
|
||||
std::string a,b,c;
|
||||
slists.Get("a",&a);
|
||||
slists.Get("b",&b);
|
||||
slists.Get("c",&c);
|
||||
|
||||
ASSERT_EQ(a,"x\nt\nr\nsa\ngh\njk");
|
||||
ASSERT_EQ(b,"y\n2\ndf\nl;");
|
||||
ASSERT_EQ(c,"asdasd\nasdasd\nbbnagnagsx\nrogosh");
|
||||
}
|
||||
|
||||
// Reopen the database (the previous changes should persist / be remembered)
|
||||
{
|
||||
StringAppendOperator append_op('\n');
|
||||
auto db = OpenDb(&append_op);
|
||||
StringLists slists(db);
|
||||
|
||||
// All changes should be on disk. This will test VersionSet Get()
|
||||
std::string a,b,c;
|
||||
slists.Get("a",&a);
|
||||
slists.Get("b",&b);
|
||||
|
||||
@@ -5,51 +5,53 @@
|
||||
#include "util/coding.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
|
||||
using namespace leveldb;
|
||||
|
||||
namespace { // anonymous namespace
|
||||
|
||||
// A 'model' merge operator with uint64 addition semantics
|
||||
class UInt64AddOperator : public MergeOperator {
|
||||
// Implemented as an AssociativeMergeOperator for simplicty and example.
|
||||
class UInt64AddOperator : public AssociativeMergeOperator {
|
||||
public:
|
||||
virtual void Merge(const Slice& key,
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
// assuming 0 if no existing value
|
||||
uint64_t existing = 0;
|
||||
if (existing_value) {
|
||||
if (existing_value->size() == sizeof(uint64_t)) {
|
||||
existing = DecodeFixed64(existing_value->data());
|
||||
} else {
|
||||
// if existing_value is corrupted, treat it as 0
|
||||
Log(logger, "existing value corruption, size: %zu > %zu",
|
||||
existing_value->size(), sizeof(uint64_t));
|
||||
existing = 0;
|
||||
}
|
||||
uint64_t orig_value = 0;
|
||||
if (existing_value){
|
||||
orig_value = DecodeInteger(*existing_value, logger);
|
||||
}
|
||||
uint64_t operand = DecodeInteger(value, logger);
|
||||
|
||||
uint64_t operand;
|
||||
if (value.size() == sizeof(uint64_t)) {
|
||||
operand = DecodeFixed64(value.data());
|
||||
} else {
|
||||
// if operand is corrupted, treat it as 0
|
||||
Log(logger, "operand value corruption, size: %zu > %zu",
|
||||
value.size(), sizeof(uint64_t));
|
||||
operand = 0;
|
||||
}
|
||||
assert(new_value);
|
||||
new_value->clear();
|
||||
PutFixed64(new_value, orig_value + operand);
|
||||
|
||||
new_value->resize(sizeof(uint64_t));
|
||||
EncodeFixed64(&(*new_value)[0], existing + operand);
|
||||
|
||||
return;
|
||||
return true; // Return true always since corruption will be treated as 0
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "UInt64AddOperator";
|
||||
}
|
||||
|
||||
private:
|
||||
// Takes the string and decodes it into a uint64_t
|
||||
// On error, prints a message and returns 0
|
||||
uint64_t DecodeInteger(const Slice& value, Logger* logger) const {
|
||||
uint64_t result = 0;
|
||||
|
||||
if (value.size() == sizeof(uint64_t)) {
|
||||
result = DecodeFixed64(value.data());
|
||||
} else if (logger != nullptr) {
|
||||
// If value is corrupted, treat it as 0
|
||||
Log(logger, "uint64 value corruption, size: %zu > %zu",
|
||||
value.size(), sizeof(uint64_t));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+45
-41
@@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "utilities/ttl/db_ttl.h"
|
||||
#include "include/utilities/utility_db.h"
|
||||
#include "db/filename.h"
|
||||
#include "util/coding.h"
|
||||
#include "include/leveldb/env.h"
|
||||
@@ -17,10 +16,19 @@ DBWithTTL::DBWithTTL(const int32_t ttl,
|
||||
const std::string& dbname,
|
||||
Status& st,
|
||||
bool read_only)
|
||||
: ttl_(ttl) {
|
||||
assert(options.compaction_filter == nullptr);
|
||||
: StackableDB(nullptr),
|
||||
ttl_(ttl) {
|
||||
Options options_to_open = options;
|
||||
options_to_open.compaction_filter = this;
|
||||
|
||||
ttl_comp_filter_.reset(new TtlCompactionFilter(ttl,
|
||||
options.compaction_filter));
|
||||
options_to_open.compaction_filter = ttl_comp_filter_.get();
|
||||
|
||||
if (options.merge_operator) {
|
||||
ttl_merge_op_.reset(new TtlMergeOperator(options.merge_operator));
|
||||
options_to_open.merge_operator = ttl_merge_op_.get();
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
st = DB::OpenForReadOnly(options_to_open, dbname, &db_);
|
||||
} else {
|
||||
@@ -35,7 +43,7 @@ DBWithTTL::~DBWithTTL() {
|
||||
Status UtilityDB::OpenTtlDB(
|
||||
const Options& options,
|
||||
const std::string& dbname,
|
||||
DB** dbptr,
|
||||
StackableDB** dbptr,
|
||||
int32_t ttl,
|
||||
bool read_only) {
|
||||
Status st;
|
||||
@@ -46,21 +54,6 @@ Status UtilityDB::OpenTtlDB(
|
||||
return st;
|
||||
}
|
||||
|
||||
// returns true(i.e. key-value to be deleted) if its TS has expired based on ttl
|
||||
bool DBWithTTL::Filter(
|
||||
int level,
|
||||
const Slice& key,
|
||||
const Slice& old_val,
|
||||
std::string* new_val,
|
||||
bool* value_changed) const {
|
||||
return IsStale(old_val, ttl_);
|
||||
}
|
||||
|
||||
const char* DBWithTTL::Name() const {
|
||||
return "Delete By TTL";
|
||||
}
|
||||
|
||||
|
||||
// Gives back the current time
|
||||
Status DBWithTTL::GetCurrentTime(int32_t& curtime) {
|
||||
return Env::Default()->GetCurrentTime((int64_t*)&curtime);
|
||||
@@ -85,7 +78,7 @@ Status DBWithTTL::AppendTS(const Slice& val, std::string& val_with_ts) {
|
||||
// Returns corruption if the length of the string is lesser than timestamp, or
|
||||
// timestamp refers to a time lesser than ttl-feature release time
|
||||
Status DBWithTTL::SanityCheckTimestamp(const Slice& str) {
|
||||
if (str.size() < (unsigned)kTSLength) {
|
||||
if (str.size() < kTSLength) {
|
||||
return Status::Corruption("Error: value's length less than timestamp's\n");
|
||||
}
|
||||
// Checks that TS is not lesser than kMinTimestamp
|
||||
@@ -106,34 +99,30 @@ bool DBWithTTL::IsStale(const Slice& value, int32_t ttl) {
|
||||
int32_t curtime;
|
||||
if (!GetCurrentTime(curtime).ok()) {
|
||||
return false; // Treat the data as fresh if could not get current time
|
||||
} else {
|
||||
int32_t timestamp_value =
|
||||
DecodeFixed32(value.data() + value.size() - kTSLength);
|
||||
if ((timestamp_value + ttl) < curtime) {
|
||||
return true; // Data is stale
|
||||
}
|
||||
}
|
||||
return false;
|
||||
int32_t timestamp_value =
|
||||
DecodeFixed32(value.data() + value.size() - kTSLength);
|
||||
return (timestamp_value + ttl) < curtime;
|
||||
}
|
||||
|
||||
// Strips the TS from the end of the string
|
||||
Status DBWithTTL::StripTS(std::string* str) {
|
||||
Status st;
|
||||
if (str->length() < kTSLength) {
|
||||
return Status::Corruption("Bad timestamp in key-value");
|
||||
}
|
||||
// Erasing characters which hold the TS
|
||||
str->erase(str->length() - kTSLength, kTSLength);
|
||||
return st;
|
||||
}
|
||||
|
||||
Status DBWithTTL::Put(
|
||||
const WriteOptions& o,
|
||||
const WriteOptions& opt,
|
||||
const Slice& key,
|
||||
const Slice& val) {
|
||||
std::string value_with_ts;
|
||||
Status st = AppendTS(val, value_with_ts);
|
||||
if (!st.ok()) {
|
||||
return st;
|
||||
}
|
||||
return db_->Put(o, key, value_with_ts);
|
||||
WriteBatch batch;
|
||||
batch.Put(key, val);
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
Status DBWithTTL::Get(const ReadOptions& options,
|
||||
@@ -158,14 +147,23 @@ std::vector<Status> DBWithTTL::MultiGet(const ReadOptions& options,
|
||||
supported with TTL"));
|
||||
}
|
||||
|
||||
bool DBWithTTL::KeyMayExist(ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found) {
|
||||
return db_->KeyMayExist(options, key, value, value_found);
|
||||
}
|
||||
|
||||
Status DBWithTTL::Delete(const WriteOptions& wopts, const Slice& key) {
|
||||
return db_->Delete(wopts, key);
|
||||
}
|
||||
|
||||
Status DBWithTTL::Merge(const WriteOptions& options,
|
||||
Status DBWithTTL::Merge(const WriteOptions& opt,
|
||||
const Slice& key,
|
||||
const Slice& value) {
|
||||
return Status::NotSupported("Merge operation not supported.");
|
||||
WriteBatch batch;
|
||||
batch.Merge(key, value);
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
Status DBWithTTL::Write(const WriteOptions& opts, WriteBatch* updates) {
|
||||
@@ -183,8 +181,13 @@ Status DBWithTTL::Write(const WriteOptions& opts, WriteBatch* updates) {
|
||||
}
|
||||
}
|
||||
virtual void Merge(const Slice& key, const Slice& value) {
|
||||
// TTL doesn't support merge operation
|
||||
batch_rewrite_status = Status::NotSupported("TTL doesn't support Merge");
|
||||
std::string value_with_ts;
|
||||
Status st = AppendTS(value, value_with_ts);
|
||||
if (!st.ok()) {
|
||||
batch_rewrite_status = st;
|
||||
} else {
|
||||
updates_ttl.Merge(key, value_with_ts);
|
||||
}
|
||||
}
|
||||
virtual void Delete(const Slice& key) {
|
||||
updates_ttl.Delete(key);
|
||||
@@ -219,8 +222,9 @@ void DBWithTTL::GetApproximateSizes(const Range* r, int n, uint64_t* sizes) {
|
||||
db_->GetApproximateSizes(r, n, sizes);
|
||||
}
|
||||
|
||||
void DBWithTTL::CompactRange(const Slice* begin, const Slice* end) {
|
||||
db_->CompactRange(begin, end);
|
||||
void DBWithTTL::CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level) {
|
||||
db_->CompactRange(begin, end, reduce_level);
|
||||
}
|
||||
|
||||
int DBWithTTL::NumberLevels() {
|
||||
|
||||
+161
-13
@@ -5,13 +5,16 @@
|
||||
#ifndef LEVELDB_UTILITIES_TTL_DB_TTL_H_
|
||||
#define LEVELDB_UTILITIES_TTL_DB_TTL_H_
|
||||
|
||||
#include "include/leveldb/db.h"
|
||||
#include "include/leveldb/compaction_filter.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/compaction_filter.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "utilities/utility_db.h"
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class DBWithTTL : public DB, CompactionFilter {
|
||||
class DBWithTTL : public StackableDB {
|
||||
public:
|
||||
DBWithTTL(const int32_t ttl,
|
||||
const Options& options,
|
||||
@@ -33,6 +36,11 @@ class DBWithTTL : public DB, CompactionFilter {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values);
|
||||
|
||||
virtual bool KeyMayExist(ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr);
|
||||
|
||||
virtual Status Delete(const WriteOptions& wopts, const Slice& key);
|
||||
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
@@ -52,7 +60,8 @@ class DBWithTTL : public DB, CompactionFilter {
|
||||
|
||||
virtual void GetApproximateSizes(const Range* r, int n, uint64_t* sizes);
|
||||
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end);
|
||||
virtual void CompactRange(const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false);
|
||||
|
||||
virtual int NumberLevels();
|
||||
|
||||
@@ -76,14 +85,9 @@ class DBWithTTL : public DB, CompactionFilter {
|
||||
// Simulate a db crash, no elegant closing of database.
|
||||
void TEST_Destroy_DBWithTtl();
|
||||
|
||||
// The following two methods are for CompactionFilter
|
||||
virtual bool Filter(int level,
|
||||
const Slice& key,
|
||||
const Slice& old_val,
|
||||
std::string* new_val,
|
||||
bool* value_changed) const override;
|
||||
|
||||
virtual const char* Name() const override;
|
||||
virtual DB* GetRawDB() {
|
||||
return db_;
|
||||
}
|
||||
|
||||
static bool IsStale(const Slice& value, int32_t ttl);
|
||||
|
||||
@@ -95,7 +99,7 @@ class DBWithTTL : public DB, CompactionFilter {
|
||||
|
||||
static Status GetCurrentTime(int32_t& curtime);
|
||||
|
||||
static const int32_t kTSLength = sizeof(int32_t); // size of timestamp
|
||||
static const uint32_t kTSLength = sizeof(int32_t); // size of timestamp
|
||||
|
||||
static const int32_t kMinTimestamp = 1368146402; // 05/09/2013:5:40PM GMT-8
|
||||
|
||||
@@ -104,6 +108,8 @@ class DBWithTTL : public DB, CompactionFilter {
|
||||
private:
|
||||
DB* db_;
|
||||
int32_t ttl_;
|
||||
unique_ptr<MergeOperator> ttl_merge_op_;
|
||||
unique_ptr<CompactionFilter> ttl_comp_filter_;
|
||||
};
|
||||
|
||||
class TtlIterator : public Iterator {
|
||||
@@ -167,5 +173,147 @@ class TtlIterator : public Iterator {
|
||||
Iterator* iter_;
|
||||
};
|
||||
|
||||
class TtlCompactionFilter : public CompactionFilter {
|
||||
|
||||
public:
|
||||
TtlCompactionFilter(int32_t ttl, const CompactionFilter* comp_filter)
|
||||
: ttl_(ttl),
|
||||
user_comp_filter_(comp_filter) {
|
||||
// Unlike the merge operator, compaction filter is necessary for TTL, hence
|
||||
// this would be called even if user doesn't specify any compaction-filter
|
||||
}
|
||||
|
||||
virtual bool Filter(int level,
|
||||
const Slice& key,
|
||||
const Slice& old_val,
|
||||
std::string* new_val,
|
||||
bool* value_changed) const override {
|
||||
if (DBWithTTL::IsStale(old_val, ttl_)) {
|
||||
return true;
|
||||
}
|
||||
if (user_comp_filter_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
assert(old_val.size() >= DBWithTTL::kTSLength);
|
||||
Slice old_val_without_ts(old_val.data(),
|
||||
old_val.size() - DBWithTTL::kTSLength);
|
||||
if (user_comp_filter_->Filter(level, key, old_val_without_ts, new_val,
|
||||
value_changed)) {
|
||||
return true;
|
||||
}
|
||||
if (*value_changed) {
|
||||
new_val->append(old_val.data() + old_val.size() - DBWithTTL::kTSLength,
|
||||
DBWithTTL::kTSLength);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "Delete By TTL";
|
||||
}
|
||||
|
||||
private:
|
||||
int32_t ttl_;
|
||||
const CompactionFilter* user_comp_filter_;
|
||||
};
|
||||
|
||||
class TtlMergeOperator : public MergeOperator {
|
||||
|
||||
public:
|
||||
explicit TtlMergeOperator(const MergeOperator* merge_op)
|
||||
: user_merge_op_(merge_op) {
|
||||
assert(merge_op);
|
||||
}
|
||||
|
||||
virtual bool Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const std::deque<std::string>& operands,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
const uint32_t ts_len = DBWithTTL::kTSLength;
|
||||
if (existing_value && existing_value->size() < ts_len) {
|
||||
Log(logger, "Error: Could not remove timestamp from existing value.");
|
||||
return false;
|
||||
// TODO: Change Merge semantics and add a counter here
|
||||
}
|
||||
|
||||
// Extract time-stamp from each operand to be passed to user_merge_op_
|
||||
std::deque<std::string> operands_without_ts;
|
||||
for (auto it = operands.begin(); it != operands.end(); ++it) {
|
||||
if (it->size() < ts_len) {
|
||||
Log(logger, "Error: Could not remove timestamp from operand value.");
|
||||
return false;
|
||||
}
|
||||
operands_without_ts.push_back(it->substr(0, it->size() - ts_len));
|
||||
}
|
||||
|
||||
// Apply the user merge operator (store result in *new_value)
|
||||
if (existing_value) {
|
||||
Slice existing_value_without_ts(existing_value->data(),
|
||||
existing_value->size() - ts_len);
|
||||
user_merge_op_->Merge(key, &existing_value_without_ts,
|
||||
operands_without_ts, new_value, logger);
|
||||
} else {
|
||||
user_merge_op_->Merge(key, nullptr, operands_without_ts, new_value,
|
||||
logger);
|
||||
}
|
||||
|
||||
// Augment the *new_value with the ttl time-stamp
|
||||
int32_t curtime;
|
||||
if (!DBWithTTL::GetCurrentTime(curtime).ok()) {
|
||||
Log(logger, "Error: Could not get current time to be attached internally "
|
||||
"to the new value.");
|
||||
return false;
|
||||
} else {
|
||||
char ts_string[ts_len];
|
||||
EncodeFixed32(ts_string, curtime);
|
||||
new_value->append(ts_string, ts_len);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool PartialMerge(const Slice& key,
|
||||
const Slice& left_operand,
|
||||
const Slice& right_operand,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
const uint32_t ts_len = DBWithTTL::kTSLength;
|
||||
|
||||
if (left_operand.size() < ts_len || right_operand.size() < ts_len) {
|
||||
Log(logger, "Error: Could not remove timestamp from value.");
|
||||
return false;
|
||||
//TODO: Change Merge semantics and add a counter here
|
||||
}
|
||||
|
||||
// Apply the user partial-merge operator (store result in *new_value)
|
||||
assert(new_value);
|
||||
Slice left_without_ts(left_operand.data(), left_operand.size() - ts_len);
|
||||
Slice right_without_ts(right_operand.data(), right_operand.size() - ts_len);
|
||||
user_merge_op_->PartialMerge(key, left_without_ts, right_without_ts,
|
||||
new_value, logger);
|
||||
|
||||
// Augment the *new_value with the ttl time-stamp
|
||||
int32_t curtime;
|
||||
if (!DBWithTTL::GetCurrentTime(curtime).ok()) {
|
||||
Log(logger, "Error: Could not get current time to be attached internally "
|
||||
"to the new value.");
|
||||
return false;
|
||||
} else {
|
||||
char ts_string[ts_len];
|
||||
EncodeFixed32(ts_string, curtime);
|
||||
new_value->append(ts_string, ts_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "Merge By TTL";
|
||||
}
|
||||
|
||||
private:
|
||||
const MergeOperator* user_merge_op_;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // LEVELDB_UTILITIES_TTL_DB_TTL_H_
|
||||
+150
-53
@@ -2,7 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "include/utilities/utility_db.h"
|
||||
#include "leveldb/compaction_filter.h"
|
||||
#include "utilities/utility_db.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/logging.h"
|
||||
#include <map>
|
||||
@@ -11,11 +12,14 @@
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef std::map<std::string, std::string> KVMap;
|
||||
|
||||
enum BatchOperation {
|
||||
PUT = 0,
|
||||
DELETE = 1
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class TtlTest {
|
||||
@@ -48,6 +52,13 @@ class TtlTest {
|
||||
ASSERT_OK(UtilityDB::OpenTtlDB(options_, dbname_, &db_ttl_, ttl));
|
||||
}
|
||||
|
||||
// Open with TestFilter compaction filter
|
||||
void OpenTtlWithTestCompaction(int32_t ttl) {
|
||||
test_comp_filter_.reset(new TestFilter(kSampleSize_, kNewValue_));
|
||||
options_.compaction_filter = test_comp_filter_.get();
|
||||
OpenTtl(ttl);
|
||||
}
|
||||
|
||||
// Open database with TTL support in read_only mode
|
||||
void OpenReadOnlyTtl(int32_t ttl) {
|
||||
assert(db_ttl_ == nullptr);
|
||||
@@ -62,10 +73,19 @@ class TtlTest {
|
||||
// Populates and returns a kv-map
|
||||
void MakeKVMap(int64_t num_entries) {
|
||||
kvmap_.clear();
|
||||
|
||||
int digits = 1;
|
||||
for (int dummy = num_entries; dummy /= 10 ; ++digits);
|
||||
int digits_in_i = 1;
|
||||
for (int64_t i = 0; i < num_entries; i++) {
|
||||
std::string key = "key";
|
||||
std::string value = "value";
|
||||
if (i % 10 == 0) {
|
||||
digits_in_i++;
|
||||
}
|
||||
for(int j = digits_in_i; j < digits; j++) {
|
||||
key.append("0");
|
||||
value.append("0");
|
||||
}
|
||||
AppendNumberTo(&key, i);
|
||||
AppendNumberTo(&value, i);
|
||||
kvmap_[key] = value;
|
||||
@@ -120,8 +140,10 @@ class TtlTest {
|
||||
// Sleeps for slp_tim then runs a manual compaction
|
||||
// Checks span starting from st_pos from kvmap_ in the db and
|
||||
// Gets should return true if check is true and false otherwise
|
||||
// Also checks that value that we got is the same as inserted
|
||||
void SleepCompactCheck(int slp_tim, int st_pos, int span, bool check = true) {
|
||||
// Also checks that value that we got is the same as inserted; and =kNewValue
|
||||
// if test_compaction_change is true
|
||||
void SleepCompactCheck(int slp_tim, int st_pos, int span, bool check = true,
|
||||
bool test_compaction_change = false) {
|
||||
assert(db_ttl_);
|
||||
sleep(slp_tim);
|
||||
ManualCompact();
|
||||
@@ -132,19 +154,25 @@ class TtlTest {
|
||||
for (int i = 0; kv_it_ != kvmap_.end(), i < span; i++, kv_it_++) {
|
||||
Status s = db_ttl_->Get(ropts, kv_it_->first, &v);
|
||||
if (s.ok() != check) {
|
||||
fprintf(stderr, "key=%s ",
|
||||
kv_it_->first.c_str());
|
||||
fprintf(stderr, "key=%s ", kv_it_->first.c_str());
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "is absent from db but was expected to be present\n");
|
||||
} else {
|
||||
fprintf(stderr, "is present in db but was expected to be absent\n");
|
||||
}
|
||||
assert(false);
|
||||
} else if (s.ok() && (v.compare(kv_it_->second) != 0)) {
|
||||
fprintf(stderr, " value for key=%s present in database is %s but "
|
||||
" should be %s\n", kv_it_->first.c_str(), v.c_str(),
|
||||
kv_it_->second.c_str());
|
||||
assert(false);
|
||||
} else if (s.ok()) {
|
||||
if (test_compaction_change && v.compare(kNewValue_) != 0) {
|
||||
fprintf(stderr, " value for key=%s present in database is %s but "
|
||||
" should be %s\n", kv_it_->first.c_str(), v.c_str(),
|
||||
kNewValue_.c_str());
|
||||
assert(false);
|
||||
} else if (!test_compaction_change && v.compare(kv_it_->second) !=0) {
|
||||
fprintf(stderr, " value for key=%s present in database is %s but "
|
||||
" should be %s\n", kv_it_->first.c_str(), v.c_str(),
|
||||
kv_it_->second.c_str());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,15 +204,65 @@ class TtlTest {
|
||||
delete dbiter;
|
||||
}
|
||||
|
||||
class TestFilter : public CompactionFilter {
|
||||
public:
|
||||
TestFilter(const int64_t kSampleSize, const std::string kNewValue)
|
||||
: kSampleSize_(kSampleSize),
|
||||
kNewValue_(kNewValue) {
|
||||
}
|
||||
|
||||
// Works on keys of the form "key<number>"
|
||||
// Drops key if number at the end of key is in [0, kSampleSize_/3),
|
||||
// Keeps key if it is in [kSampleSize_/3, 2*kSampleSize_/3),
|
||||
// Change value if it is in [2*kSampleSize_/3, kSampleSize_)
|
||||
// Eg. kSampleSize_=6. Drop:key0-1...Keep:key2-3...Change:key4-5...
|
||||
virtual bool Filter(int level, const Slice& key,
|
||||
const Slice& value, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
assert(new_value != nullptr);
|
||||
|
||||
std::string search_str = "0123456789";
|
||||
std::string key_string = key.ToString();
|
||||
size_t pos = key_string.find_first_of(search_str);
|
||||
int num_key_end;
|
||||
if (pos != std::string::npos) {
|
||||
num_key_end = stoi(key_string.substr(pos, key.size() - pos));
|
||||
} else {
|
||||
return false; // Keep keys not matching the format "key<NUMBER>"
|
||||
}
|
||||
|
||||
int partition = kSampleSize_ / 3;
|
||||
if (num_key_end < partition) {
|
||||
return true;
|
||||
} else if (num_key_end < partition * 2) {
|
||||
return false;
|
||||
} else {
|
||||
*new_value = kNewValue_;
|
||||
*value_changed = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "TestFilter";
|
||||
}
|
||||
|
||||
private:
|
||||
const int64_t kSampleSize_;
|
||||
const std::string kNewValue_;
|
||||
};
|
||||
|
||||
// Choose carefully so that Put, Gets & Compaction complete in 1 second buffer
|
||||
const int64_t kSampleSize = 100;
|
||||
const int64_t kSampleSize_ = 100;
|
||||
|
||||
private:
|
||||
std::string dbname_;
|
||||
DB* db_ttl_;
|
||||
StackableDB* db_ttl_;
|
||||
Options options_;
|
||||
KVMap kvmap_;
|
||||
KVMap::iterator kv_it_;
|
||||
const std::string kNewValue_ = "new_value";
|
||||
unique_ptr<CompactionFilter> test_comp_filter_;
|
||||
}; // class TtlTest
|
||||
|
||||
// If TTL is non positive or not provided, the behaviour is TTL = infinity
|
||||
@@ -192,8 +270,8 @@ class TtlTest {
|
||||
// bunch of kvs each time. All kvs should accummulate in the db till the end
|
||||
// Partitions the sample-size provided into 3 sets over boundary1 and boundary2
|
||||
TEST(TtlTest, NoEffect) {
|
||||
MakeKVMap(kSampleSize);
|
||||
int boundary1 = kSampleSize / 3;
|
||||
MakeKVMap(kSampleSize_);
|
||||
int boundary1 = kSampleSize_ / 3;
|
||||
int boundary2 = 2 * boundary1;
|
||||
|
||||
OpenTtl();
|
||||
@@ -207,135 +285,154 @@ TEST(TtlTest, NoEffect) {
|
||||
CloseTtl();
|
||||
|
||||
OpenTtl(-1);
|
||||
PutValues(boundary2, kSampleSize - boundary2); //T=3: Set3 never deleted
|
||||
SleepCompactCheck(1, 0, kSampleSize, true); //T=4: Sets 1,2,3 still there
|
||||
PutValues(boundary2, kSampleSize_ - boundary2); //T=3: Set3 never deleted
|
||||
SleepCompactCheck(1, 0, kSampleSize_, true); //T=4: Sets 1,2,3 still there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Puts a set of values and checks its presence using Get during ttl
|
||||
TEST(TtlTest, PresentDuringTTL) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(2); // T=0:Open the db with ttl = 2
|
||||
PutValues(0, kSampleSize); // T=0:Insert Set1. Delete at t=2
|
||||
SleepCompactCheck(1, 0, kSampleSize, true); // T=1:Set1 should still be there
|
||||
PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
|
||||
SleepCompactCheck(1, 0, kSampleSize_, true); // T=1:Set1 should still be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Puts a set of values and checks its absence using Get after ttl
|
||||
TEST(TtlTest, AbsentAfterTTL) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(1); // T=0:Open the db with ttl = 2
|
||||
PutValues(0, kSampleSize); // T=0:Insert Set1. Delete at t=2
|
||||
SleepCompactCheck(2, 0, kSampleSize, false); // T=2:Set1 should not be there
|
||||
PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
|
||||
SleepCompactCheck(2, 0, kSampleSize_, false); // T=2:Set1 should not be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Resets the timestamp of a set of kvs by updating them and checks that they
|
||||
// are not deleted according to the old timestamp
|
||||
TEST(TtlTest, ResetTimestamp) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(3);
|
||||
PutValues(0, kSampleSize); // T=0: Insert Set1. Delete at t=3
|
||||
PutValues(0, kSampleSize_); // T=0: Insert Set1. Delete at t=3
|
||||
sleep(2); // T=2
|
||||
PutValues(0, kSampleSize); // T=2: Insert Set1. Delete at t=5
|
||||
SleepCompactCheck(2, 0, kSampleSize); // T=4: Set1 should still be there
|
||||
PutValues(0, kSampleSize_); // T=2: Insert Set1. Delete at t=5
|
||||
SleepCompactCheck(2, 0, kSampleSize_); // T=4: Set1 should still be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Similar to PresentDuringTTL but uses Iterator
|
||||
TEST(TtlTest, IterPresentDuringTTL) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(2);
|
||||
PutValues(0, kSampleSize); // T=0: Insert. Delete at t=2
|
||||
SleepCompactCheckIter(1, 0, kSampleSize); // T=1: Set should be there
|
||||
PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
|
||||
SleepCompactCheckIter(1, 0, kSampleSize_); // T=1: Set should be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Similar to AbsentAfterTTL but uses Iterator
|
||||
TEST(TtlTest, IterAbsentAfterTTL) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(1);
|
||||
PutValues(0, kSampleSize); // T=0: Insert. Delete at t=1
|
||||
SleepCompactCheckIter(2, 0, kSampleSize, false); // T=2: Should not be there
|
||||
PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
|
||||
SleepCompactCheckIter(2, 0, kSampleSize_, false); // T=2: Should not be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks presence while opening the same db more than once with the same ttl
|
||||
// Note: The second open will open the same db
|
||||
TEST(TtlTest, MultiOpenSamePresent) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(2);
|
||||
PutValues(0, kSampleSize); // T=0: Insert. Delete at t=2
|
||||
PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
|
||||
CloseTtl();
|
||||
|
||||
OpenTtl(2); // T=0. Delete at t=2
|
||||
SleepCompactCheck(1, 0, kSampleSize); // T=1: Set should be there
|
||||
SleepCompactCheck(1, 0, kSampleSize_); // T=1: Set should be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks absence while opening the same db more than once with the same ttl
|
||||
// Note: The second open will open the same db
|
||||
TEST(TtlTest, MultiOpenSameAbsent) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(1);
|
||||
PutValues(0, kSampleSize); // T=0: Insert. Delete at t=1
|
||||
PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
|
||||
CloseTtl();
|
||||
|
||||
OpenTtl(1); // T=0.Delete at t=1
|
||||
SleepCompactCheck(2, 0, kSampleSize, false); // T=2: Set should not be there
|
||||
SleepCompactCheck(2, 0, kSampleSize_, false); // T=2: Set should not be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks presence while opening the same db more than once with bigger ttl
|
||||
TEST(TtlTest, MultiOpenDifferent) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(1);
|
||||
PutValues(0, kSampleSize); // T=0: Insert. Delete at t=1
|
||||
PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
|
||||
CloseTtl();
|
||||
|
||||
OpenTtl(3); // T=0: Set deleted at t=3
|
||||
SleepCompactCheck(2, 0, kSampleSize); // T=2: Set should be there
|
||||
SleepCompactCheck(2, 0, kSampleSize_); // T=2: Set should be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks presence during ttl in read_only mode
|
||||
TEST(TtlTest, ReadOnlyPresentForever) {
|
||||
MakeKVMap(kSampleSize);
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtl(1); // T=0:Open the db normally
|
||||
PutValues(0, kSampleSize); // T=0:Insert Set1. Delete at t=1
|
||||
PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
|
||||
CloseTtl();
|
||||
|
||||
OpenReadOnlyTtl(1);
|
||||
SleepCompactCheck(2, 0, kSampleSize); // T=2:Set1 should still be there
|
||||
SleepCompactCheck(2, 0, kSampleSize_); // T=2:Set1 should still be there
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks whether WriteBatch works well with TTL
|
||||
// Puts all kvs in kvmap_ in a batch and writes first, then deletes first half
|
||||
TEST(TtlTest, WriteBatchTest) {
|
||||
MakeKVMap(kSampleSize);
|
||||
BatchOperation batch_ops[kSampleSize];
|
||||
for (int i = 0; i < kSampleSize; i++) {
|
||||
MakeKVMap(kSampleSize_);
|
||||
BatchOperation batch_ops[kSampleSize_];
|
||||
for (int i = 0; i < kSampleSize_; i++) {
|
||||
batch_ops[i] = PUT;
|
||||
}
|
||||
|
||||
OpenTtl(2);
|
||||
MakePutWriteBatch(batch_ops, kSampleSize);
|
||||
for (int i = 0; i < kSampleSize / 2; i++) {
|
||||
MakePutWriteBatch(batch_ops, kSampleSize_);
|
||||
for (int i = 0; i < kSampleSize_ / 2; i++) {
|
||||
batch_ops[i] = DELETE;
|
||||
}
|
||||
MakePutWriteBatch(batch_ops, kSampleSize / 2);
|
||||
SleepCompactCheck(0, 0, kSampleSize / 2, false);
|
||||
SleepCompactCheck(0, kSampleSize / 2, kSampleSize - kSampleSize / 2);
|
||||
MakePutWriteBatch(batch_ops, kSampleSize_ / 2);
|
||||
SleepCompactCheck(0, 0, kSampleSize_ / 2, false);
|
||||
SleepCompactCheck(0, kSampleSize_ / 2, kSampleSize_ - kSampleSize_ / 2);
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
// Checks user's compaction filter for correctness with TTL logic
|
||||
TEST(TtlTest, CompactionFilter) {
|
||||
MakeKVMap(kSampleSize_);
|
||||
|
||||
OpenTtlWithTestCompaction(1);
|
||||
PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
|
||||
// T=2: TTL logic takes precedence over TestFilter:-Set1 should not be there
|
||||
SleepCompactCheck(2, 0, kSampleSize_, false);
|
||||
CloseTtl();
|
||||
|
||||
OpenTtlWithTestCompaction(3);
|
||||
PutValues(0, kSampleSize_); // T=0:Insert Set1.
|
||||
int partition = kSampleSize_ / 3;
|
||||
SleepCompactCheck(1, 0, partition, false); // Part dropped
|
||||
SleepCompactCheck(0, partition, partition); // Part kept
|
||||
SleepCompactCheck(0, 2 * partition, partition, true, true); // Part changed
|
||||
CloseTtl();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user