mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b58c73c7c | ||
|
|
a84234a61b | ||
|
|
8ed70fc209 | ||
|
|
7f19bb93c6 | ||
|
|
8438a19360 | ||
|
|
6614a48418 | ||
|
|
076bd01a29 | ||
|
|
990df99a61 | ||
|
|
7dcadb1d37 | ||
|
|
985a31cf0e | ||
|
|
f09329cb01 | ||
|
|
7e9f28cb23 | ||
|
|
d20b8cfaa1 | ||
|
|
0f9c43ea36 | ||
|
|
722d80c374 | ||
|
|
22a0a60dc4 | ||
|
|
be25ee44fe | ||
|
|
0c26e76b28 | ||
|
|
1d23b5c470 | ||
|
|
2a8faf797c | ||
|
|
7f71448388 | ||
|
|
d977e55596 | ||
|
|
d5bd6c772b | ||
|
|
6b46f78434 | ||
|
|
528a11c635 | ||
|
|
536e9973e3 | ||
|
|
4142a3e783 | ||
|
|
1913ce27b9 | ||
|
|
808e809366 | ||
|
|
0c39f54dfb | ||
|
|
b6fd7811eb | ||
|
|
9dcb75b6d9 | ||
|
|
1755581f19 | ||
|
|
d9c0785812 | ||
|
|
bda6f3363d | ||
|
|
0db6b028e7 | ||
|
|
ff6ec0eb17 | ||
|
|
23861857c4 | ||
|
|
a98badff16 | ||
|
|
66f62e5c78 | ||
|
|
384400128f | ||
|
|
17b54aea53 | ||
|
|
050869177e | ||
|
|
42ea795209 | ||
|
|
bb530c0f47 | ||
|
|
f76eda74d6 | ||
|
|
08be7f5266 | ||
|
|
47b452cfcf | ||
|
|
562b7a1f28 | ||
|
|
63a2215c63 | ||
|
|
e173bf9c19 | ||
|
|
10720a5587 | ||
|
|
01cbdd2aae | ||
|
|
045575ad0c | ||
|
|
7c5173d27f | ||
|
|
6929b08616 | ||
|
|
50b790c6d4 | ||
|
|
162b8151f1 | ||
|
|
28b5c76004 | ||
|
|
5585e00279 | ||
|
|
68eed8c9f0 | ||
|
|
36e759d199 | ||
|
|
a6fd14c881 | ||
|
|
c7037153c3 | ||
|
|
c8ecfaedd0 | ||
|
|
570ba5aca8 | ||
|
|
625b9ef4e0 | ||
|
|
59a2763d5c | ||
|
|
f611935e9a | ||
|
|
58b0f9d890 | ||
|
|
2da53b1e06 | ||
|
|
6c4c159b2c | ||
|
|
8dfe2fdd51 | ||
|
|
58c49466d2 | ||
|
|
6a2be31f14 | ||
|
|
e91ebf1399 | ||
|
|
5a5953b388 | ||
|
|
5e642403a9 | ||
|
|
0c9dc9f8e0 | ||
|
|
bcefedeb6d | ||
|
|
48081777f3 | ||
|
|
0138b8eba8 | ||
|
|
1562653ba0 | ||
|
|
218857b3f5 | ||
|
|
5d0074c471 | ||
|
|
3fcf7b26b9 | ||
|
|
2fa643466d | ||
|
|
06a52bda64 | ||
|
|
9674c11d01 | ||
|
|
37c6740c38 | ||
|
|
18efdba8d5 | ||
|
|
d3f2ec694f | ||
|
|
93e6b5e9d9 | ||
|
|
54153ab07a | ||
|
|
4632239d13 | ||
|
|
5e0868147d | ||
|
|
7c88249f51 | ||
|
|
894a77abdf | ||
|
|
323d6e3542 | ||
|
|
f8d6a2981f | ||
|
|
1a1957ba4d | ||
|
|
63d5cc72fa | ||
|
|
76dcf7eefd | ||
|
|
7abe9655d3 | ||
|
|
38e8b727a8 | ||
|
|
c1f588af71 | ||
|
|
1242bfcad7 | ||
|
|
606a126703 | ||
|
|
2124c85cc6 | ||
|
|
02c4023666 | ||
|
|
1129921e9b | ||
|
|
5e3d5c5f6e | ||
|
|
9c5a3f4746 | ||
|
|
e4c3673923 | ||
|
|
0c9d03ba10 | ||
|
|
1903aa5cc7 | ||
|
|
67dae255a9 | ||
|
|
2105ecac4d | ||
|
|
b0999011e2 | ||
|
|
2ea5e78af7 | ||
|
|
7ef7df005f | ||
|
|
3215967205 | ||
|
|
e9269e6ece | ||
|
|
49ee5a4ac4 | ||
|
|
8f09d53fd1 | ||
|
|
76286ee67e | ||
|
|
473e829784 | ||
|
|
99e03bcbf1 |
No files matched your search
@@ -32,3 +32,4 @@ coverage/COVERAGE_REPORT
|
||||
tags
|
||||
java/*.log
|
||||
java/include/org_rocksdb_*.h
|
||||
unity.cc
|
||||
+17
@@ -2,8 +2,24 @@
|
||||
|
||||
### Unreleased
|
||||
### New Features
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utilitiy class to query data out of WriteBatch when building it.
|
||||
* Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include:
|
||||
no_block_cache, block_cache, block_cache_compressed, block_size, block_size_deviation, block_restart_interval, filter_policy, whole_key_filtering. filter_policy is changed to shared_ptr from a raw pointer.
|
||||
* Remove deprecated options: disable_seek_compaction and db_stats_log_interval
|
||||
* OptimizeForPointLookup() takes one parameter for block cache size. It now builds hash index, bloom filter, and block cache.
|
||||
|
||||
### Public API changes
|
||||
* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
|
||||
|
||||
|
||||
----- Past Releases -----
|
||||
|
||||
|
||||
## 3.4.0 (8/18/2014)
|
||||
### New Features
|
||||
* Support Multiple DB paths in universal style compactions
|
||||
* Add feature of storing plain table index and bloom filter in SST file.
|
||||
* CompactRange() will never output compacted files to level 0. This used to be the case when all the compaction input files were at level 0.
|
||||
|
||||
### Public API changes
|
||||
* DBOptions.db_paths now is a vector of a DBPath structure which indicates both of path and target size
|
||||
@@ -12,6 +28,7 @@
|
||||
* Statistics APIs now take uint32_t as type instead of Tickers. Also make two access functions getTickerCount and histogramData const
|
||||
* Add DB property rocksdb.estimate-num-keys, estimated number of live keys in DB.
|
||||
* Add DB::GetIntProperty(), which returns DB properties that are integer as uint64_t.
|
||||
* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
|
||||
|
||||
## 3.3.0 (7/10/2014)
|
||||
### New Features
|
||||
|
||||
@@ -116,10 +116,12 @@ TESTS = \
|
||||
table_test \
|
||||
thread_local_test \
|
||||
geodb_test \
|
||||
rate_limiter_test \
|
||||
cuckoo_table_builder_test \
|
||||
rate_limiter_test \
|
||||
options_test \
|
||||
cuckoo_table_reader_test
|
||||
cuckoo_table_builder_test \
|
||||
cuckoo_table_reader_test \
|
||||
cuckoo_table_db_test \
|
||||
write_batch_with_index_test
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@@ -234,8 +236,14 @@ valgrind_check: all $(PROGRAMS) $(TESTS)
|
||||
echo $$t $$((etime - stime)) >> $(VALGRIND_DIR)/valgrind_tests_times; \
|
||||
done
|
||||
|
||||
unity.cc:
|
||||
$(shell (export ROCKSDB_ROOT="$(CURDIR)"; "$(CURDIR)/build_tools/unity" "$(CURDIR)/unity.cc"))
|
||||
|
||||
unity: unity.cc unity.o
|
||||
$(CXX) unity.o $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
clean:
|
||||
-rm -f $(PROGRAMS) $(TESTS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) build_config.mk
|
||||
-rm -f $(PROGRAMS) $(TESTS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) build_config.mk unity.cc
|
||||
-rm -rf ios-x86/* ios-arm/*
|
||||
-find . -name "*.[od]" -exec rm {} \;
|
||||
-find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
@@ -367,6 +375,9 @@ spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNE
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
write_batch_with_index_test: utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
@@ -424,6 +435,9 @@ cuckoo_table_builder_test: table/cuckoo_table_builder_test.o $(LIBOBJECTS) $(TES
|
||||
cuckoo_table_reader_test: table/cuckoo_table_reader_test.o $(LIBOBJECTS) $(TESTHARNESS) $(BENCHHARNESS)
|
||||
$(CXX) table/cuckoo_table_reader_test.o $(LIBOBJECTS) $(TESTHARNESS) $(BENCHHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
cuckoo_table_db_test: db/cuckoo_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/cuckoo_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
options_test: util/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/options_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Create the unity file
|
||||
#
|
||||
|
||||
OUTPUT=$1
|
||||
if test -z "$OUTPUT"; then
|
||||
echo "usage: $0 <output-filename>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Delete existing file, if it exists
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
# Detect OS
|
||||
if test -z "$TARGET_OS"; then
|
||||
TARGET_OS=`uname -s`
|
||||
fi
|
||||
|
||||
# generic port files (working on all platform by #ifdef) go directly in /port
|
||||
GENERIC_PORT_FILES=`cd "$ROCKSDB_ROOT"; find port -name '*.cc' | tr "\n" " "`
|
||||
|
||||
# On GCC, we pick libc's memcmp over GCC's memcmp via -fno-builtin-memcmp
|
||||
case "$TARGET_OS" in
|
||||
Darwin)
|
||||
# PORT_FILES=port/darwin/darwin_specific.cc
|
||||
;;
|
||||
IOS)
|
||||
;;
|
||||
Linux)
|
||||
# PORT_FILES=port/linux/linux_specific.cc
|
||||
;;
|
||||
SunOS)
|
||||
# PORT_FILES=port/sunos/sunos_specific.cc
|
||||
;;
|
||||
FreeBSD)
|
||||
# PORT_FILES=port/freebsd/freebsd_specific.cc
|
||||
;;
|
||||
NetBSD)
|
||||
# PORT_FILES=port/netbsd/netbsd_specific.cc
|
||||
;;
|
||||
OpenBSD)
|
||||
# PORT_FILES=port/openbsd/openbsd_specific.cc
|
||||
;;
|
||||
DragonFly)
|
||||
# PORT_FILES=port/dragonfly/dragonfly_specific.cc
|
||||
;;
|
||||
OS_ANDROID_CROSSCOMPILE)
|
||||
# PORT_FILES=port/android/android.cc
|
||||
;;
|
||||
*)
|
||||
echo "Unknown platform!" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
# We want to make a list of all cc files within util, db, table, and helpers
|
||||
# except for the test and benchmark files. By default, find will output a list
|
||||
# of all files matching either rule, so we need to append -print to make the
|
||||
# prune take effect.
|
||||
DIRS="util db table utilities"
|
||||
|
||||
set -f # temporarily disable globbing so that our patterns arent expanded
|
||||
PRUNE_TEST="-name *test*.cc -prune"
|
||||
PRUNE_BENCH="-name *bench*.cc -prune"
|
||||
PORTABLE_FILES=`cd "$ROCKSDB_ROOT"; find $DIRS $PRUNE_TEST -o $PRUNE_BENCH -o -name '*.cc' -print | sort`
|
||||
PORTABLE_CPP=`cd "$ROCKSDB_ROOT"; find $DIRS $PRUNE_TEST -o $PRUNE_BENCH -o -name '*.cpp' -print | sort`
|
||||
set +f # re-enable globbing
|
||||
|
||||
# The sources consist of the portable files, plus the platform-specific port
|
||||
# file.
|
||||
for SOURCE_FILE in $PORTABLE_FILES $GENERIC_PORT_FILES $PORT_FILES $PORTABLE_CPP
|
||||
do
|
||||
echo "#include <$SOURCE_FILE>" >> "$OUTPUT"
|
||||
done
|
||||
|
||||
echo "int main(int argc, char** argv){ return 0; }" >> "$OUTPUT"
|
||||
|
||||
@@ -113,6 +113,13 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
is_first_key = false;
|
||||
|
||||
if (this_ikey.type == kTypeMerge) {
|
||||
// TODO(tbd): Add a check here to prevent RocksDB from crash when
|
||||
// reopening a DB w/o properly specifying the merge operator. But
|
||||
// currently we observed a memory leak on failing in RocksDB
|
||||
// recovery, so we decide to let it crash instead of causing
|
||||
// memory leak for now before we have identified the real cause
|
||||
// of the memory leak.
|
||||
|
||||
// Handle merge-type keys using the MergeHelper
|
||||
// TODO: pass statistics to MergeUntil
|
||||
merge.MergeUntil(iter, 0 /* don't worry about snapshot */);
|
||||
|
||||
@@ -36,6 +36,9 @@ using rocksdb::ColumnFamilyHandle;
|
||||
using rocksdb::ColumnFamilyOptions;
|
||||
using rocksdb::CompactionFilter;
|
||||
using rocksdb::CompactionFilterFactory;
|
||||
using rocksdb::CompactionFilterV2;
|
||||
using rocksdb::CompactionFilterFactoryV2;
|
||||
using rocksdb::CompactionFilterContext;
|
||||
using rocksdb::CompactionOptionsFIFO;
|
||||
using rocksdb::Comparator;
|
||||
using rocksdb::CompressionType;
|
||||
@@ -52,6 +55,7 @@ using rocksdb::MergeOperator;
|
||||
using rocksdb::NewBloomFilterPolicy;
|
||||
using rocksdb::NewLRUCache;
|
||||
using rocksdb::Options;
|
||||
using rocksdb::BlockBasedTableOptions;
|
||||
using rocksdb::RandomAccessFile;
|
||||
using rocksdb::Range;
|
||||
using rocksdb::ReadOptions;
|
||||
@@ -78,6 +82,7 @@ struct rocksdb_fifo_compaction_options_t { CompactionOptionsFIFO rep; };
|
||||
struct rocksdb_readoptions_t { ReadOptions rep; };
|
||||
struct rocksdb_writeoptions_t { WriteOptions rep; };
|
||||
struct rocksdb_options_t { Options rep; };
|
||||
struct rocksdb_block_based_table_options_t { BlockBasedTableOptions rep; };
|
||||
struct rocksdb_seqfile_t { SequentialFile* rep; };
|
||||
struct rocksdb_randomfile_t { RandomAccessFile* rep; };
|
||||
struct rocksdb_writablefile_t { WritableFile* rep; };
|
||||
@@ -154,6 +159,104 @@ struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
virtual const char* Name() const { return (*name_)(state_); }
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterv2_t : public CompactionFilterV2 {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
void (*filter_)(void*, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list);
|
||||
|
||||
virtual ~rocksdb_compactionfilterv2_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
|
||||
virtual const char* Name() const {
|
||||
return (*name_)(state_);
|
||||
}
|
||||
|
||||
virtual std::vector<bool> Filter(int level,
|
||||
const SliceVector& keys,
|
||||
const SliceVector& existing_values,
|
||||
std::vector<std::string>* new_values,
|
||||
std::vector<bool>* values_changed) const {
|
||||
// Make a vector pointing to the underlying key data.
|
||||
size_t num_keys = keys.size();
|
||||
std::vector<const char*> keys_list(num_keys);
|
||||
std::vector<size_t> keys_list_sizes(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
keys_list[i] = keys[i].data();
|
||||
keys_list_sizes[i] = keys[i].size();
|
||||
}
|
||||
// Make a vector pointing to the underlying value data.
|
||||
std::vector<const char*> existing_values_list(num_keys);
|
||||
std::vector<size_t> existing_values_list_sizes(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
existing_values_list[i] = existing_values[i].data();
|
||||
existing_values_list_sizes[i] = existing_values[i].size();
|
||||
}
|
||||
// Make a vector which will accept newly-allocated char* arrays
|
||||
// which we will take ownership of and assign to strings in new_values.
|
||||
new_values->clear();
|
||||
std::vector<char*> new_values_list(num_keys);
|
||||
std::vector<size_t> new_values_list_sizes(num_keys);
|
||||
// Resize values_changed to hold all keys.
|
||||
values_changed->resize(num_keys);
|
||||
// Make a vector for bools indicating a value should be deleted
|
||||
// on compaction (true) or maintained (false).
|
||||
std::vector<unsigned char> to_delete_list(num_keys);
|
||||
|
||||
(*filter_)(
|
||||
state_, level, num_keys, &keys_list[0], &keys_list_sizes[0],
|
||||
&existing_values_list[0], &existing_values_list_sizes[0],
|
||||
&new_values_list[0], &new_values_list_sizes[0], &to_delete_list[0]);
|
||||
|
||||
// Now, we transfer any changed values, setting values_changed and
|
||||
// initializing new_values in the event a value changed.
|
||||
std::vector<bool> to_delete(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
to_delete[i] = to_delete_list[i];
|
||||
(*values_changed)[i] = new_values_list[i] != nullptr;
|
||||
if ((*values_changed)[i]) {
|
||||
new_values->push_back(std::string(new_values_list[i], new_values_list_sizes[i]));
|
||||
free(new_values_list[i]);
|
||||
}
|
||||
}
|
||||
return to_delete;
|
||||
}
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterfactoryv2_t : public CompactionFilterFactoryV2 {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
const char* (*name_)(void*);
|
||||
rocksdb_compactionfilterv2_t* (*create_compaction_filter_v2_)(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context);
|
||||
|
||||
rocksdb_compactionfilterfactoryv2_t(const SliceTransform* prefix_extractor)
|
||||
: CompactionFilterFactoryV2(prefix_extractor) {
|
||||
}
|
||||
|
||||
virtual ~rocksdb_compactionfilterfactoryv2_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
|
||||
virtual const char* Name() const {
|
||||
return (*name_)(state_);
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilterV2> CreateCompactionFilterV2(
|
||||
const CompactionFilterContext& context) {
|
||||
struct rocksdb_compactionfiltercontext_t c_context;
|
||||
c_context.rep.is_full_compaction = context.is_full_compaction;
|
||||
c_context.rep.is_manual_compaction = context.is_manual_compaction;
|
||||
return std::unique_ptr<CompactionFilterV2>(
|
||||
(*create_compaction_filter_v2_)(state_, &c_context));
|
||||
}
|
||||
};
|
||||
|
||||
struct rocksdb_comparator_t : public Comparator {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
@@ -952,6 +1055,74 @@ const char* rocksdb_writebatch_data(rocksdb_writebatch_t* b, size_t* size) {
|
||||
return b->rep.Data().c_str();
|
||||
}
|
||||
|
||||
rocksdb_block_based_table_options_t*
|
||||
rocksdb_block_based_options_create() {
|
||||
return new rocksdb_block_based_table_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_destroy(
|
||||
rocksdb_block_based_table_options_t* options) {
|
||||
delete options;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_block_size(
|
||||
rocksdb_block_based_table_options_t* options, size_t block_size) {
|
||||
options->rep.block_size = block_size;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_block_size_deviation(
|
||||
rocksdb_block_based_table_options_t* options, int block_size_deviation) {
|
||||
options->rep.block_size_deviation = block_size_deviation;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_block_restart_interval(
|
||||
rocksdb_block_based_table_options_t* options, int block_restart_interval) {
|
||||
options->rep.block_restart_interval = block_restart_interval;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_filter_policy(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_filterpolicy_t* filter_policy) {
|
||||
options->rep.filter_policy.reset(filter_policy);
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_no_block_cache(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
unsigned char no_block_cache) {
|
||||
options->rep.no_block_cache = no_block_cache;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_block_cache(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_cache_t* block_cache) {
|
||||
if (block_cache) {
|
||||
options->rep.block_cache = block_cache->rep;
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_block_cache_compressed(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_cache_t* block_cache_compressed) {
|
||||
if (block_cache_compressed) {
|
||||
options->rep.block_cache_compressed = block_cache_compressed->rep;
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_whole_key_filtering(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.whole_key_filtering = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t *opt,
|
||||
rocksdb_block_based_table_options_t* table_options) {
|
||||
if (table_options) {
|
||||
opt->rep.table_factory.reset(
|
||||
rocksdb::NewBlockBasedTableFactory(table_options->rep));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rocksdb_options_t* rocksdb_options_create() {
|
||||
return new rocksdb_options_t;
|
||||
}
|
||||
@@ -966,8 +1137,8 @@ void rocksdb_options_increase_parallelism(
|
||||
}
|
||||
|
||||
void rocksdb_options_optimize_for_point_lookup(
|
||||
rocksdb_options_t* opt) {
|
||||
opt->rep.OptimizeForPointLookup();
|
||||
rocksdb_options_t* opt, uint64_t block_cache_size_mb) {
|
||||
opt->rep.OptimizeForPointLookup(block_cache_size_mb);
|
||||
}
|
||||
|
||||
void rocksdb_options_optimize_level_style_compaction(
|
||||
@@ -1004,10 +1175,10 @@ void rocksdb_options_set_merge_operator(
|
||||
opt->rep.merge_operator = std::shared_ptr<MergeOperator>(merge_operator);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_filter_policy(
|
||||
void rocksdb_options_set_compaction_filter_factory_v2(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_filterpolicy_t* policy) {
|
||||
opt->rep.filter_policy = policy;
|
||||
rocksdb_compactionfilterfactoryv2_t* compaction_filter_factory_v2) {
|
||||
opt->rep.compaction_filter_factory_v2 = std::shared_ptr<CompactionFilterFactoryV2>(compaction_filter_factory_v2);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_create_if_missing(
|
||||
@@ -1053,26 +1224,6 @@ void rocksdb_options_set_max_open_files(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_open_files = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_cache(rocksdb_options_t* opt, rocksdb_cache_t* c) {
|
||||
if (c) {
|
||||
opt->rep.block_cache = c->rep;
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_cache_compressed(rocksdb_options_t* opt, rocksdb_cache_t* c) {
|
||||
if (c) {
|
||||
opt->rep.block_cache_compressed = c->rep;
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_size(rocksdb_options_t* opt, size_t s) {
|
||||
opt->rep.block_size = s;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_restart_interval(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.block_restart_interval = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_target_file_size_base(
|
||||
rocksdb_options_t* opt, uint64_t n) {
|
||||
opt->rep.target_file_size_base = n;
|
||||
@@ -1165,11 +1316,6 @@ void rocksdb_options_set_prefix_extractor(
|
||||
opt->rep.prefix_extractor.reset(prefix_extractor);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_whole_key_filtering(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.whole_key_filtering = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_disable_data_sync(
|
||||
rocksdb_options_t* opt, int disable_data_sync) {
|
||||
opt->rep.disableDataSync = disable_data_sync;
|
||||
@@ -1180,11 +1326,6 @@ void rocksdb_options_set_use_fsync(
|
||||
opt->rep.use_fsync = use_fsync;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_db_stats_log_interval(
|
||||
rocksdb_options_t* opt, int db_stats_log_interval) {
|
||||
opt->rep.db_stats_log_interval = db_stats_log_interval;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_db_log_dir(
|
||||
rocksdb_options_t* opt, const char* db_log_dir) {
|
||||
opt->rep.db_log_dir = db_log_dir;
|
||||
@@ -1244,11 +1385,6 @@ void rocksdb_options_set_stats_dump_period_sec(
|
||||
opt->rep.stats_dump_period_sec = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_size_deviation(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
opt->rep.block_size_deviation = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_advise_random_on_open(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.advise_random_on_open = v;
|
||||
@@ -1343,11 +1479,6 @@ void rocksdb_options_set_max_manifest_file_size(
|
||||
opt->rep.max_manifest_file_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_no_block_cache(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.no_block_cache = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_table_cache_numshardbits(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
opt->rep.table_cache_numshardbits = v;
|
||||
@@ -1367,10 +1498,6 @@ void rocksdb_options_set_disable_auto_compactions(rocksdb_options_t* opt, int di
|
||||
opt->rep.disable_auto_compactions = disable;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_disable_seek_compaction(rocksdb_options_t* opt, int disable) {
|
||||
opt->rep.disable_seek_compaction = disable;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_delete_obsolete_files_period_micros(
|
||||
rocksdb_options_t* opt, uint64_t v) {
|
||||
opt->rep.delete_obsolete_files_period_micros = v;
|
||||
@@ -1550,6 +1677,46 @@ void rocksdb_compactionfilterfactory_destroy(
|
||||
delete factory;
|
||||
}
|
||||
|
||||
rocksdb_compactionfilterv2_t* rocksdb_compactionfilterv2_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
void (*filter)(void*, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list),
|
||||
const char* (*name)(void*)) {
|
||||
rocksdb_compactionfilterv2_t* result = new rocksdb_compactionfilterv2_t;
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->filter_ = filter;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilterv2_destroy(rocksdb_compactionfilterv2_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
rocksdb_compactionfilterfactoryv2_t* rocksdb_compactionfilterfactoryv2_create(
|
||||
void* state,
|
||||
rocksdb_slicetransform_t* prefix_extractor,
|
||||
void (*destructor)(void*),
|
||||
rocksdb_compactionfilterv2_t* (*create_compaction_filter_v2)(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context),
|
||||
const char* (*name)(void*)) {
|
||||
rocksdb_compactionfilterfactoryv2_t* result = new rocksdb_compactionfilterfactoryv2_t(prefix_extractor);
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->create_compaction_filter_v2_ = create_compaction_filter_v2;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilterfactoryv2_destroy(rocksdb_compactionfilterfactoryv2_t* factory) {
|
||||
delete factory;
|
||||
}
|
||||
|
||||
rocksdb_comparator_t* rocksdb_comparator_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
|
||||
+130
-9
@@ -225,6 +225,79 @@ static rocksdb_t* CheckCompaction(rocksdb_t* db, rocksdb_options_t* options,
|
||||
return db;
|
||||
}
|
||||
|
||||
// Custom compaction filter V2.
|
||||
static void CompactionFilterV2Destroy(void* arg) { }
|
||||
static const char* CompactionFilterV2Name(void* arg) {
|
||||
return "TestCompactionFilterV2";
|
||||
}
|
||||
static void CompactionFilterV2Filter(
|
||||
void* arg, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list) {
|
||||
size_t i;
|
||||
for (i = 0; i < num_keys; i++) {
|
||||
// If any value is "gc", it's removed.
|
||||
if (existing_values_list_sizes[i] == 2 && memcmp(existing_values_list[i], "gc", 2) == 0) {
|
||||
to_delete_list[i] = 1;
|
||||
} else if (existing_values_list_sizes[i] == 6 && memcmp(existing_values_list[i], "gc all", 6) == 0) {
|
||||
// If any value is "gc all", all keys are removed.
|
||||
size_t j;
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
to_delete_list[j] = 1;
|
||||
}
|
||||
return;
|
||||
} else if (existing_values_list_sizes[i] == 6 && memcmp(existing_values_list[i], "change", 6) == 0) {
|
||||
// If value is "change", set changed value to "changed".
|
||||
size_t len;
|
||||
len = strlen("changed");
|
||||
new_values_list[i] = malloc(len);
|
||||
memcpy(new_values_list[i], "changed", len);
|
||||
new_values_list_sizes[i] = len;
|
||||
} else {
|
||||
// Otherwise, no keys are removed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom prefix extractor for compaction filter V2 which extracts first 3 characters.
|
||||
static void CFV2PrefixExtractorDestroy(void* arg) { }
|
||||
static char* CFV2PrefixExtractorTransform(void* arg, const char* key, size_t length, size_t* dst_length) {
|
||||
// Verify keys are maximum length 4; this verifies fix for a
|
||||
// prior bug which was passing the RocksDB-encoded key with
|
||||
// logical timestamp suffix instead of parsed user key.
|
||||
if (length > 4) {
|
||||
fprintf(stderr, "%s:%d: %s: key %s is not user key\n", __FILE__, __LINE__, phase, key);
|
||||
abort();
|
||||
}
|
||||
*dst_length = length < 3 ? length : 3;
|
||||
return (char*)key;
|
||||
}
|
||||
static unsigned char CFV2PrefixExtractorInDomain(void* state, const char* key, size_t length) {
|
||||
return 1;
|
||||
}
|
||||
static unsigned char CFV2PrefixExtractorInRange(void* state, const char* key, size_t length) {
|
||||
return 1;
|
||||
}
|
||||
static const char* CFV2PrefixExtractorName(void* state) {
|
||||
return "TestCFV2PrefixExtractor";
|
||||
}
|
||||
|
||||
// Custom compaction filter factory V2.
|
||||
static void CompactionFilterFactoryV2Destroy(void* arg) {
|
||||
rocksdb_slicetransform_destroy((rocksdb_slicetransform_t*)arg);
|
||||
}
|
||||
static const char* CompactionFilterFactoryV2Name(void* arg) {
|
||||
return "TestCompactionFilterFactoryV2";
|
||||
}
|
||||
static rocksdb_compactionfilterv2_t* CompactionFilterFactoryV2Create(
|
||||
void* state, const rocksdb_compactionfiltercontext_t* context) {
|
||||
return rocksdb_compactionfilterv2_create(state, CompactionFilterV2Destroy,
|
||||
CompactionFilterV2Filter,
|
||||
CompactionFilterV2Name);
|
||||
}
|
||||
|
||||
// Custom merge operator
|
||||
static void MergeOperatorDestroy(void* arg) { }
|
||||
static const char* MergeOperatorName(void* arg) {
|
||||
@@ -262,6 +335,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_t* cache;
|
||||
rocksdb_env_t* env;
|
||||
rocksdb_options_t* options;
|
||||
rocksdb_block_based_table_options_t* table_options;
|
||||
rocksdb_readoptions_t* roptions;
|
||||
rocksdb_writeoptions_t* woptions;
|
||||
char* err = NULL;
|
||||
@@ -280,14 +354,15 @@ int main(int argc, char** argv) {
|
||||
options = rocksdb_options_create();
|
||||
rocksdb_options_set_comparator(options, cmp);
|
||||
rocksdb_options_set_error_if_exists(options, 1);
|
||||
rocksdb_options_set_cache(options, cache);
|
||||
rocksdb_options_set_env(options, env);
|
||||
rocksdb_options_set_info_log(options, NULL);
|
||||
rocksdb_options_set_write_buffer_size(options, 100000);
|
||||
rocksdb_options_set_paranoid_checks(options, 1);
|
||||
rocksdb_options_set_max_open_files(options, 10);
|
||||
rocksdb_options_set_block_size(options, 1024);
|
||||
rocksdb_options_set_block_restart_interval(options, 8);
|
||||
table_options = rocksdb_block_based_options_create();
|
||||
rocksdb_block_based_options_set_block_cache(table_options, cache);
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
|
||||
rocksdb_options_set_compression(options, rocksdb_no_compression);
|
||||
rocksdb_options_set_compression_options(options, -14, -1, 0);
|
||||
int compression_levels[] = {rocksdb_no_compression, rocksdb_no_compression,
|
||||
@@ -467,10 +542,12 @@ int main(int argc, char** argv) {
|
||||
policy = rocksdb_filterpolicy_create_bloom(10);
|
||||
}
|
||||
|
||||
rocksdb_block_based_options_set_filter_policy(table_options, policy);
|
||||
|
||||
// Create new database
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
rocksdb_options_set_filter_policy(options, policy);
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo", 3, "foovalue", 8, &err);
|
||||
@@ -492,8 +569,9 @@ int main(int argc, char** argv) {
|
||||
CheckGet(db, roptions, "foo", "foovalue");
|
||||
CheckGet(db, roptions, "bar", "barvalue");
|
||||
}
|
||||
rocksdb_options_set_filter_policy(options, NULL);
|
||||
rocksdb_filterpolicy_destroy(policy);
|
||||
// Reset the policy
|
||||
rocksdb_block_based_options_set_filter_policy(table_options, NULL);
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
}
|
||||
|
||||
StartPhase("compaction_filter");
|
||||
@@ -531,6 +609,50 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_destroy(options);
|
||||
}
|
||||
|
||||
StartPhase("compaction_filter_v2");
|
||||
{
|
||||
rocksdb_compactionfilterfactoryv2_t* factory;
|
||||
rocksdb_slicetransform_t* prefix_extractor;
|
||||
prefix_extractor = rocksdb_slicetransform_create(
|
||||
NULL, CFV2PrefixExtractorDestroy, CFV2PrefixExtractorTransform,
|
||||
CFV2PrefixExtractorInDomain, CFV2PrefixExtractorInRange,
|
||||
CFV2PrefixExtractorName);
|
||||
factory = rocksdb_compactionfilterfactoryv2_create(
|
||||
prefix_extractor, prefix_extractor, CompactionFilterFactoryV2Destroy,
|
||||
CompactionFilterFactoryV2Create, CompactionFilterFactoryV2Name);
|
||||
// Create new database
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
rocksdb_options_set_compaction_filter_factory_v2(options, factory);
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
// Only foo2 is GC'd, foo3 is changed.
|
||||
rocksdb_put(db, woptions, "foo1", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo2", 4, "gc", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo3", 4, "change", 6, &err);
|
||||
CheckNoError(err);
|
||||
// All bars are GC'd.
|
||||
rocksdb_put(db, woptions, "bar1", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "bar2", 4, "gc all", 6, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "bar3", 4, "no gc", 5, &err);
|
||||
CheckNoError(err);
|
||||
// Compact the DB to garbage collect.
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
|
||||
// Verify foo entries.
|
||||
CheckGet(db, roptions, "foo1", "no gc");
|
||||
CheckGet(db, roptions, "foo2", NULL);
|
||||
CheckGet(db, roptions, "foo3", "changed");
|
||||
// Verify bar entries were all deleted.
|
||||
CheckGet(db, roptions, "bar1", NULL);
|
||||
CheckGet(db, roptions, "bar2", NULL);
|
||||
CheckGet(db, roptions, "bar3", NULL);
|
||||
}
|
||||
|
||||
StartPhase("merge_operator");
|
||||
{
|
||||
rocksdb_mergeoperator_t* merge_operator;
|
||||
@@ -640,8 +762,7 @@ int main(int argc, char** argv) {
|
||||
StartPhase("prefix");
|
||||
{
|
||||
// Create new database
|
||||
rocksdb_filterpolicy_t* policy = rocksdb_filterpolicy_create_bloom(10);
|
||||
rocksdb_options_set_filter_policy(options, policy);
|
||||
rocksdb_options_set_allow_mmap_reads(options, 1);
|
||||
rocksdb_options_set_prefix_extractor(options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
@@ -678,13 +799,13 @@ int main(int argc, char** argv) {
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_filterpolicy_destroy(policy);
|
||||
}
|
||||
|
||||
|
||||
StartPhase("cleanup");
|
||||
rocksdb_close(db);
|
||||
rocksdb_options_destroy(options);
|
||||
rocksdb_block_based_options_destroy(table_options);
|
||||
rocksdb_readoptions_destroy(roptions);
|
||||
rocksdb_writeoptions_destroy(woptions);
|
||||
rocksdb_cache_destroy(cache);
|
||||
|
||||
+14
-25
@@ -49,21 +49,10 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
|
||||
uint32_t ColumnFamilyHandleImpl::GetID() const { return cfd()->GetID(); }
|
||||
|
||||
namespace {
|
||||
// Fix user-supplied options to be reasonable
|
||||
template <class T, class V>
|
||||
static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
|
||||
if (static_cast<V>(*ptr) > maxvalue) *ptr = maxvalue;
|
||||
if (static_cast<V>(*ptr) < minvalue) *ptr = minvalue;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const InternalKeyComparator* icmp,
|
||||
const InternalFilterPolicy* ipolicy,
|
||||
const ColumnFamilyOptions& src) {
|
||||
ColumnFamilyOptions result = src;
|
||||
result.comparator = icmp;
|
||||
result.filter_policy = (src.filter_policy != nullptr) ? ipolicy : nullptr;
|
||||
#ifdef OS_MACOSX
|
||||
// TODO(icanadi) make write_buffer_size uint64_t instead of size_t
|
||||
ClipToRange(&result.write_buffer_size, ((size_t)64) << 10, ((size_t)1) << 30);
|
||||
@@ -79,13 +68,7 @@ ColumnFamilyOptions SanitizeOptions(const InternalKeyComparator* icmp,
|
||||
result.min_write_buffer_number_to_merge =
|
||||
std::min(result.min_write_buffer_number_to_merge,
|
||||
result.max_write_buffer_number - 1);
|
||||
if (result.block_cache == nullptr && !result.no_block_cache) {
|
||||
result.block_cache = NewLRUCache(8 << 20);
|
||||
}
|
||||
result.compression_per_level = src.compression_per_level;
|
||||
if (result.block_size_deviation < 0 || result.block_size_deviation > 100) {
|
||||
result.block_size_deviation = 0;
|
||||
}
|
||||
if (result.max_mem_compaction_level >= result.num_levels) {
|
||||
result.max_mem_compaction_level = result.num_levels - 1;
|
||||
}
|
||||
@@ -191,8 +174,7 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
ColumnFamilyData::ColumnFamilyData(const std::string& dbname, uint32_t id,
|
||||
const std::string& name,
|
||||
ColumnFamilyData::ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
const ColumnFamilyOptions& options,
|
||||
const DBOptions* db_options,
|
||||
@@ -205,9 +187,7 @@ ColumnFamilyData::ColumnFamilyData(const std::string& dbname, uint32_t id,
|
||||
refs_(0),
|
||||
dropped_(false),
|
||||
internal_comparator_(options.comparator),
|
||||
internal_filter_policy_(options.filter_policy),
|
||||
options_(*db_options, SanitizeOptions(&internal_comparator_,
|
||||
&internal_filter_policy_, options)),
|
||||
options_(*db_options, SanitizeOptions(&internal_comparator_, options)),
|
||||
mem_(nullptr),
|
||||
imm_(options_.min_write_buffer_number_to_merge),
|
||||
super_version_(nullptr),
|
||||
@@ -485,7 +465,7 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const EnvOptions& storage_options,
|
||||
Cache* table_cache)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(dbname, 0, "", nullptr, nullptr,
|
||||
dummy_cfd_(new ColumnFamilyData(0, "", nullptr, nullptr,
|
||||
ColumnFamilyOptions(), db_options,
|
||||
storage_options_, nullptr)),
|
||||
default_cfd_cache_(nullptr),
|
||||
@@ -556,8 +536,8 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const ColumnFamilyOptions& options) {
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd =
|
||||
new ColumnFamilyData(db_name_, id, name, dummy_versions, table_cache_,
|
||||
options, db_options_, storage_options_, this);
|
||||
new ColumnFamilyData(id, name, dummy_versions, table_cache_, options,
|
||||
db_options_, storage_options_, this);
|
||||
Lock();
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
@@ -641,4 +621,13 @@ ColumnFamilyHandle* ColumnFamilyMemTablesImpl::GetColumnFamilyHandle() {
|
||||
return &handle_;
|
||||
}
|
||||
|
||||
uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family) {
|
||||
uint32_t column_family_id = 0;
|
||||
if (column_family != nullptr) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
column_family_id = cfh->GetID();
|
||||
}
|
||||
return column_family_id;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+5
-5
@@ -113,7 +113,6 @@ struct SuperVersion {
|
||||
};
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const InternalKeyComparator* icmp,
|
||||
const InternalFilterPolicy* ipolicy,
|
||||
const ColumnFamilyOptions& src);
|
||||
|
||||
class ColumnFamilySet;
|
||||
@@ -248,9 +247,9 @@ class ColumnFamilyData {
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(const std::string& dbname, uint32_t id,
|
||||
const std::string& name, Version* dummy_versions,
|
||||
Cache* table_cache, const ColumnFamilyOptions& options,
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
const ColumnFamilyOptions& options,
|
||||
const DBOptions* db_options,
|
||||
const EnvOptions& storage_options,
|
||||
ColumnFamilySet* column_family_set);
|
||||
@@ -272,7 +271,6 @@ class ColumnFamilyData {
|
||||
bool dropped_; // true if client dropped it
|
||||
|
||||
const InternalKeyComparator internal_comparator_;
|
||||
const InternalFilterPolicy internal_filter_policy_;
|
||||
|
||||
Options const options_;
|
||||
|
||||
@@ -457,4 +455,6 @@ class ColumnFamilyMemTablesImpl : public ColumnFamilyMemTables {
|
||||
ColumnFamilyHandleInternal handle_;
|
||||
};
|
||||
|
||||
extern uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -408,9 +408,15 @@ TEST(ColumnFamilyTest, WriteBatchFailure) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
WriteBatch batch;
|
||||
batch.Put(handles_[0], Slice("existing"), Slice("column-family"));
|
||||
batch.Put(handles_[1], Slice("non-existing"), Slice("column-family"));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
DropColumnFamilies({1});
|
||||
WriteOptions woptions_ignore_missing_cf;
|
||||
woptions_ignore_missing_cf.ignore_missing_column_families = true;
|
||||
batch.Put(handles_[0], Slice("still here"), Slice("column-family"));
|
||||
ASSERT_OK(db_->Write(woptions_ignore_missing_cf, &batch));
|
||||
ASSERT_EQ("column-family", Get(0, "still here"));
|
||||
Status s = db_->Write(WriteOptions(), &batch);
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
Close();
|
||||
@@ -746,9 +752,10 @@ TEST(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.filter_policy = nullptr;
|
||||
default_cf.no_block_cache = true;
|
||||
default_cf.source_compaction_factor = 100;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
one.compaction_style = kCompactionStyleUniversal;
|
||||
// trigger compaction if there are >= 4 files
|
||||
@@ -913,7 +920,9 @@ TEST(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
}
|
||||
int num_writable_file_start = env_->GetNumberOfNewWritableFileCalls();
|
||||
// this will trigger the flushes
|
||||
ASSERT_OK(db_->Write(WriteOptions(), nullptr));
|
||||
for (size_t i = 0; i <= 4; ++i) {
|
||||
ASSERT_OK(Flush(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[i]);
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->fd.GetFileSize();
|
||||
|
||||
@@ -231,4 +231,7 @@ class Compaction {
|
||||
void ResetNextCompactionIndex();
|
||||
};
|
||||
|
||||
// Utility function
|
||||
extern uint64_t TotalFileSize(const std::vector<FileMetaData*>& files);
|
||||
|
||||
} // namespace rocksdb
|
||||
+38
-26
@@ -18,6 +18,14 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
uint64_t TotalCompensatedFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->compensated_file_size;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Determine compression type, based on user options, level of the output
|
||||
// file and whether compression is disabled.
|
||||
@@ -45,14 +53,6 @@ CompressionType GetCompressionType(const Options& options, int level,
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t TotalCompensatedFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->compensated_file_size;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Multiple two operands. If they overflow, return op1.
|
||||
uint64_t MultiplyCheckOverflow(uint64_t op1, int op2) {
|
||||
if (op1 == 0) {
|
||||
@@ -173,9 +173,12 @@ void CompactionPicker::GetRange(const std::vector<FileMetaData*>& inputs1,
|
||||
}
|
||||
|
||||
bool CompactionPicker::ExpandWhileOverlapping(Compaction* c) {
|
||||
assert(c != nullptr);
|
||||
// If inputs are empty then there is nothing to expand.
|
||||
if (!c || c->inputs_[0].empty()) {
|
||||
return true;
|
||||
if (c->inputs_[0].empty()) {
|
||||
assert(c->inputs_[1].empty());
|
||||
// This isn't good compaction
|
||||
return false;
|
||||
}
|
||||
|
||||
// GetOverlappingInputs will always do the right thing for level-0.
|
||||
@@ -427,7 +430,7 @@ Compaction* LevelCompactionPicker::PickCompaction(Version* version,
|
||||
level = version->compaction_level_[i];
|
||||
if ((version->compaction_score_[i] >= 1)) {
|
||||
c = PickCompactionBySize(version, level, version->compaction_score_[i]);
|
||||
if (ExpandWhileOverlapping(c) == false) {
|
||||
if (c == nullptr || ExpandWhileOverlapping(c) == false) {
|
||||
delete c;
|
||||
c = nullptr;
|
||||
} else {
|
||||
@@ -720,10 +723,11 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
// first candidate to be compacted.
|
||||
uint64_t candidate_size = f != nullptr? f->compensated_file_size : 0;
|
||||
if (f != nullptr) {
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: Possible candidate file %s[%d].",
|
||||
version->cfd_->GetName().c_str(),
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId()).c_str(), loop);
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId(), file_num_buf,
|
||||
sizeof(file_num_buf));
|
||||
LogToBuffer(log_buffer, "[%s] Universal: Possible candidate file %s[%d].",
|
||||
version->cfd_->GetName().c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
// Check if the suceeding files need compaction.
|
||||
@@ -815,12 +819,14 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
for (unsigned int i = start_index; i < first_index_after; i++) {
|
||||
FileMetaData* f = c->input_version_->files_[level][i];
|
||||
c->inputs_[0].files.push_back(f);
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId(), file_num_buf,
|
||||
sizeof(file_num_buf));
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: Picking file %s[%d] "
|
||||
"with size %" PRIu64 " (compensated size %" PRIu64 ")\n",
|
||||
version->cfd_->GetName().c_str(),
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId()).c_str(),
|
||||
i, f->fd.GetFileSize(), f->compensated_file_size);
|
||||
version->cfd_->GetName().c_str(), file_num_buf, i,
|
||||
f->fd.GetFileSize(), f->compensated_file_size);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -854,29 +860,35 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
break;
|
||||
}
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId(), file_num_buf,
|
||||
sizeof(file_num_buf));
|
||||
LogToBuffer(log_buffer, "[%s] Universal: skipping file %s[%d] compacted %s",
|
||||
version->cfd_->GetName().c_str(),
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId()).c_str(),
|
||||
loop, " cannot be a candidate to reduce size amp.\n");
|
||||
version->cfd_->GetName().c_str(), file_num_buf, loop,
|
||||
" cannot be a candidate to reduce size amp.\n");
|
||||
f = nullptr;
|
||||
}
|
||||
if (f == nullptr) {
|
||||
return nullptr; // no candidate files
|
||||
}
|
||||
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId(), file_num_buf,
|
||||
sizeof(file_num_buf));
|
||||
LogToBuffer(log_buffer, "[%s] Universal: First candidate file %s[%d] %s",
|
||||
version->cfd_->GetName().c_str(),
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId()).c_str(),
|
||||
start_index, " to reduce size amp.\n");
|
||||
version->cfd_->GetName().c_str(), file_num_buf, start_index,
|
||||
" to reduce size amp.\n");
|
||||
|
||||
// keep adding up all the remaining files
|
||||
for (unsigned int loop = start_index; loop < files.size() - 1; loop++) {
|
||||
f = files[loop];
|
||||
if (f->being_compacted) {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId(), file_num_buf,
|
||||
sizeof(file_num_buf));
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: Possible candidate file %s[%d] %s.",
|
||||
version->cfd_->GetName().c_str(),
|
||||
FormatFileNumber(f->fd.GetNumber(), f->fd.GetPathId()).c_str(), loop,
|
||||
version->cfd_->GetName().c_str(), file_num_buf, loop,
|
||||
" is already being compacted. No size amp reduction possible.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -204,4 +204,7 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
}
|
||||
};
|
||||
|
||||
// Utility function
|
||||
extern uint64_t TotalCompensatedFileSize(const std::vector<FileMetaData*>& files);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -45,7 +45,9 @@ class CorruptionTest {
|
||||
|
||||
db_ = nullptr;
|
||||
options_.create_if_missing = true;
|
||||
options_.block_size_deviation = 0; // make unit test pass for now
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size_deviation = 0; // make unit test pass for now
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen();
|
||||
options_.create_if_missing = false;
|
||||
}
|
||||
@@ -60,9 +62,11 @@ class CorruptionTest {
|
||||
db_ = nullptr;
|
||||
Options opt = (options ? *options : options_);
|
||||
opt.env = &env_;
|
||||
opt.block_cache = tiny_cache_;
|
||||
opt.block_size_deviation = 0;
|
||||
opt.arena_block_size = 4096;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = tiny_cache_;
|
||||
table_options.block_size_deviation = 0;
|
||||
opt.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
return DB::Open(opt, dbname_, &db_);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class CuckooTableDBTest {
|
||||
private:
|
||||
std::string dbname_;
|
||||
Env* env_;
|
||||
DB* db_;
|
||||
|
||||
public:
|
||||
CuckooTableDBTest() : env_(Env::Default()) {
|
||||
dbname_ = test::TmpDir() + "/cuckoo_table_db_test";
|
||||
ASSERT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
Reopen();
|
||||
}
|
||||
|
||||
~CuckooTableDBTest() {
|
||||
delete db_;
|
||||
ASSERT_OK(DestroyDB(dbname_, Options()));
|
||||
}
|
||||
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
options.table_factory.reset(NewCuckooTableFactory());
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
options.allow_mmap_reads = true;
|
||||
options.create_if_missing = true;
|
||||
options.max_mem_compaction_level = 0;
|
||||
return options;
|
||||
}
|
||||
|
||||
DBImpl* dbfull() {
|
||||
return reinterpret_cast<DBImpl*>(db_);
|
||||
}
|
||||
|
||||
// The following util methods are copied from plain_table_db_test.
|
||||
void Reopen(Options* options = nullptr) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
Options opts;
|
||||
if (options != nullptr) {
|
||||
opts = *options;
|
||||
} else {
|
||||
opts = CurrentOptions();
|
||||
opts.create_if_missing = true;
|
||||
}
|
||||
ASSERT_OK(DB::Open(opts, dbname_, &db_));
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
}
|
||||
|
||||
Status Delete(const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), k);
|
||||
}
|
||||
|
||||
std::string Get(const std::string& k) {
|
||||
ReadOptions options;
|
||||
std::string result;
|
||||
Status s = db_->Get(options, k, &result);
|
||||
if (s.IsNotFound()) {
|
||||
result = "NOT_FOUND";
|
||||
} else if (!s.ok()) {
|
||||
result = s.ToString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int NumTableFilesAtLevel(int level) {
|
||||
std::string property;
|
||||
ASSERT_TRUE(
|
||||
db_->GetProperty("rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&property));
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel() {
|
||||
std::string result;
|
||||
int last_non_zero_offset = 0;
|
||||
for (int level = 0; level < db_->NumberLevels(); level++) {
|
||||
int f = NumTableFilesAtLevel(level);
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
|
||||
result += buf;
|
||||
if (f > 0) {
|
||||
last_non_zero_offset = result.size();
|
||||
}
|
||||
}
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CuckooTableDBTest, Flush) {
|
||||
// Try with empty DB first.
|
||||
ASSERT_TRUE(dbfull() != nullptr);
|
||||
ASSERT_EQ("NOT_FOUND", Get("key2"));
|
||||
|
||||
// Add some values to db.
|
||||
Options options = CurrentOptions();
|
||||
Reopen(&options);
|
||||
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key3", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(3U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
ASSERT_EQ("v1", Get("key1"));
|
||||
ASSERT_EQ("v2", Get("key2"));
|
||||
ASSERT_EQ("v3", Get("key3"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
|
||||
// Now add more keys and flush.
|
||||
ASSERT_OK(Put("key4", "v4"));
|
||||
ASSERT_OK(Put("key5", "v5"));
|
||||
ASSERT_OK(Put("key6", "v6"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(2U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
ASSERT_EQ(3U, (++row)->second->num_entries);
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_EQ("v1", Get("key1"));
|
||||
ASSERT_EQ("v2", Get("key2"));
|
||||
ASSERT_EQ("v3", Get("key3"));
|
||||
ASSERT_EQ("v4", Get("key4"));
|
||||
ASSERT_EQ("v5", Get("key5"));
|
||||
ASSERT_EQ("v6", Get("key6"));
|
||||
|
||||
ASSERT_OK(Delete("key6"));
|
||||
ASSERT_OK(Delete("key5"));
|
||||
ASSERT_OK(Delete("key4"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(3U, ptc.size());
|
||||
row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
ASSERT_EQ(3U, (++row)->second->num_entries);
|
||||
ASSERT_EQ(3U, (++row)->second->num_entries);
|
||||
ASSERT_EQ("3", FilesPerLevel());
|
||||
ASSERT_EQ("v1", Get("key1"));
|
||||
ASSERT_EQ("v2", Get("key2"));
|
||||
ASSERT_EQ("v3", Get("key3"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key5"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key6"));
|
||||
}
|
||||
|
||||
TEST(CuckooTableDBTest, FlushWithDuplicateKeys) {
|
||||
Options options = CurrentOptions();
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key1", "v3")); // Duplicate
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(2U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
ASSERT_EQ("v3", Get("key1"));
|
||||
ASSERT_EQ("v2", Get("key2"));
|
||||
}
|
||||
|
||||
namespace {
|
||||
static std::string Key(int i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "key_______%06d", i);
|
||||
return std::string(buf);
|
||||
}
|
||||
static std::string Uint64Key(uint64_t i) {
|
||||
std::string str;
|
||||
str.resize(8);
|
||||
memcpy(&str[0], static_cast<void*>(&i), 8);
|
||||
return str;
|
||||
}
|
||||
} // namespace.
|
||||
|
||||
TEST(CuckooTableDBTest, Uint64Comparator) {
|
||||
Options options = CurrentOptions();
|
||||
options.comparator = test::Uint64Comparator();
|
||||
Reopen(&options);
|
||||
|
||||
ASSERT_OK(Put(Uint64Key(1), "v1"));
|
||||
ASSERT_OK(Put(Uint64Key(2), "v2"));
|
||||
ASSERT_OK(Put(Uint64Key(3), "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
ASSERT_EQ("v1", Get(Uint64Key(1)));
|
||||
ASSERT_EQ("v2", Get(Uint64Key(2)));
|
||||
ASSERT_EQ("v3", Get(Uint64Key(3)));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Uint64Key(4)));
|
||||
|
||||
// Add more keys.
|
||||
ASSERT_OK(Delete(Uint64Key(2))); // Delete.
|
||||
ASSERT_OK(Put(Uint64Key(3), "v0")); // Update.
|
||||
ASSERT_OK(Put(Uint64Key(4), "v4"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v1", Get(Uint64Key(1)));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Uint64Key(2)));
|
||||
ASSERT_EQ("v0", Get(Uint64Key(3)));
|
||||
ASSERT_EQ("v4", Get(Uint64Key(4)));
|
||||
}
|
||||
|
||||
TEST(CuckooTableDBTest, CompactionTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
Reopen(&options);
|
||||
|
||||
// Write 11 values, each 10016 B
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx)));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||
for (int idx = 11; idx < 22; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx)));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ("0,2", FilesPerLevel());
|
||||
for (int idx = 0; idx < 22; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) {
|
||||
// Insert same key twice so that they go to different SST files. Then wait for
|
||||
// compaction and check if the latest value is stored and old value removed.
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
Reopen(&options);
|
||||
|
||||
// Write 11 values, each 10016 B
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a')));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx)));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx)));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CuckooTableDBTest, AdaptiveTable) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
// Write some keys using cuckoo table.
|
||||
options.table_factory.reset(NewCuckooTableFactory());
|
||||
Reopen(&options);
|
||||
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key3", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
// Write some keys using plain table.
|
||||
options.create_if_missing = false;
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("key4", "v4"));
|
||||
ASSERT_OK(Put("key1", "v5"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
// Write some keys using block based table.
|
||||
std::shared_ptr<TableFactory> block_based_factory(
|
||||
NewBlockBasedTableFactory());
|
||||
options.table_factory.reset(NewAdaptiveTableFactory(block_based_factory));
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("key5", "v6"));
|
||||
ASSERT_OK(Put("key2", "v7"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
ASSERT_EQ("v5", Get("key1"));
|
||||
ASSERT_EQ("v7", Get("key2"));
|
||||
ASSERT_EQ("v3", Get("key3"));
|
||||
ASSERT_EQ("v4", Get("key4"));
|
||||
ASSERT_EQ("v6", Get("key5"));
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); }
|
||||
+164
-79
@@ -30,7 +30,6 @@ int main() {
|
||||
#include <gflags/gflags.h>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -147,6 +146,7 @@ DEFINE_int64(merge_keys, -1,
|
||||
"Number of distinct keys to use for MergeRandom and "
|
||||
"ReadRandomMergeRandom. "
|
||||
"If negative, there will be FLAGS_num keys.");
|
||||
DEFINE_int32(num_column_families, 1, "Number of Column Families to use.");
|
||||
|
||||
DEFINE_int64(reads, -1, "Number of read operations to do. "
|
||||
"If negative, do FLAGS_num reads.");
|
||||
@@ -163,6 +163,7 @@ DEFINE_int32(duration, 0, "Time in seconds for the random-ops tests to run."
|
||||
|
||||
DEFINE_int32(value_size, 100, "Size of each value");
|
||||
|
||||
DEFINE_bool(use_uint64_comparator, false, "use Uint64 user comparator");
|
||||
|
||||
static bool ValidateKeySize(const char* flagname, int32_t value) {
|
||||
return true;
|
||||
@@ -239,10 +240,11 @@ DEFINE_int32(universal_compression_size_percent, -1,
|
||||
DEFINE_int64(cache_size, -1, "Number of bytes to use as a cache of uncompressed"
|
||||
"data. Negative means use default settings.");
|
||||
|
||||
DEFINE_int32(block_size, rocksdb::Options().block_size,
|
||||
DEFINE_int32(block_size, rocksdb::BlockBasedTableOptions().block_size,
|
||||
"Number of bytes in a block.");
|
||||
|
||||
DEFINE_int32(block_restart_interval, rocksdb::Options().block_restart_interval,
|
||||
DEFINE_int32(block_restart_interval,
|
||||
rocksdb::BlockBasedTableOptions().block_restart_interval,
|
||||
"Number of keys between restart points "
|
||||
"for delta encoding of keys.");
|
||||
|
||||
@@ -508,6 +510,8 @@ DEFINE_int32(prefix_size, 0, "control the prefix size for HashSkipList and "
|
||||
DEFINE_int64(keys_per_prefix, 0, "control average number of keys generated "
|
||||
"per prefix, 0 means no special handling of the prefix, "
|
||||
"i.e. use the prefix comes with the generated random number.");
|
||||
DEFINE_bool(enable_io_prio, false, "Lower the background flush/compaction "
|
||||
"threads' IO priority");
|
||||
|
||||
enum RepFactory {
|
||||
kSkipList,
|
||||
@@ -542,6 +546,8 @@ DEFINE_string(memtablerep, "skip_list", "");
|
||||
DEFINE_int64(hash_bucket_count, 1024 * 1024, "hash bucket count");
|
||||
DEFINE_bool(use_plain_table, false, "if use plain table "
|
||||
"instead of block-based table format");
|
||||
DEFINE_bool(use_cuckoo_table, false, "if use cuckoo table format");
|
||||
DEFINE_double(cuckoo_hash_ratio, 0.9, "Hash ratio for Cuckoo SST table.");
|
||||
DEFINE_bool(use_hash_search, false, "if use kHashSearch "
|
||||
"instead of kBinarySearch. "
|
||||
"This is valid if only we use BlockTable");
|
||||
@@ -840,12 +846,19 @@ class Duration {
|
||||
|
||||
class Benchmark {
|
||||
private:
|
||||
shared_ptr<Cache> cache_;
|
||||
shared_ptr<Cache> compressed_cache_;
|
||||
const FilterPolicy* filter_policy_;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<Cache> compressed_cache_;
|
||||
std::shared_ptr<const FilterPolicy> filter_policy_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
DB* db_;
|
||||
std::vector<DB*> multi_dbs_;
|
||||
struct DBWithColumnFamilies {
|
||||
std::vector<ColumnFamilyHandle*> cfh;
|
||||
DB* db;
|
||||
DBWithColumnFamilies() : db(nullptr) {
|
||||
cfh.clear();
|
||||
}
|
||||
};
|
||||
DBWithColumnFamilies db_;
|
||||
std::vector<DBWithColumnFamilies> multi_dbs_;
|
||||
int64_t num_;
|
||||
int value_size_;
|
||||
int key_size_;
|
||||
@@ -1065,7 +1078,6 @@ class Benchmark {
|
||||
? NewBloomFilterPolicy(FLAGS_bloom_bits)
|
||||
: nullptr),
|
||||
prefix_extractor_(NewFixedPrefixTransform(FLAGS_prefix_size)),
|
||||
db_(nullptr),
|
||||
num_(FLAGS_num),
|
||||
value_size_(FLAGS_value_size),
|
||||
key_size_(FLAGS_key_size),
|
||||
@@ -1096,8 +1108,7 @@ class Benchmark {
|
||||
}
|
||||
|
||||
~Benchmark() {
|
||||
delete db_;
|
||||
delete filter_policy_;
|
||||
delete db_.db;
|
||||
delete prefix_extractor_;
|
||||
}
|
||||
|
||||
@@ -1156,6 +1167,16 @@ class Benchmark {
|
||||
return base_name + std::to_string(id);
|
||||
}
|
||||
|
||||
std::string ColumnFamilyName(int i) {
|
||||
if (i == 0) {
|
||||
return kDefaultColumnFamilyName;
|
||||
} else {
|
||||
char name[100];
|
||||
snprintf(name, sizeof(name), "column_family_name_%06d", i);
|
||||
return std::string(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Run() {
|
||||
if (!SanityCheck()) {
|
||||
exit(1);
|
||||
@@ -1310,13 +1331,14 @@ class Benchmark {
|
||||
name.ToString().c_str());
|
||||
method = nullptr;
|
||||
} else {
|
||||
if (db_ != nullptr) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
if (db_.db != nullptr) {
|
||||
delete db_.db;
|
||||
db_.db = nullptr;
|
||||
db_.cfh.clear();
|
||||
DestroyDB(FLAGS_db, Options());
|
||||
}
|
||||
for (size_t i = 0; i < multi_dbs_.size(); i++) {
|
||||
delete multi_dbs_[i];
|
||||
delete multi_dbs_[i].db;
|
||||
DestroyDB(GetDbNameForMultiple(FLAGS_db, i), Options());
|
||||
}
|
||||
multi_dbs_.clear();
|
||||
@@ -1488,7 +1510,7 @@ class Benchmark {
|
||||
|
||||
void Compress(ThreadState *thread) {
|
||||
RandomGenerator gen;
|
||||
Slice input = gen.Generate(Options().block_size);
|
||||
Slice input = gen.Generate(FLAGS_block_size);
|
||||
int64_t bytes = 0;
|
||||
int64_t produced = 0;
|
||||
bool ok = true;
|
||||
@@ -1538,7 +1560,7 @@ class Benchmark {
|
||||
|
||||
void Uncompress(ThreadState *thread) {
|
||||
RandomGenerator gen;
|
||||
Slice input = gen.Generate(Options().block_size);
|
||||
Slice input = gen.Generate(FLAGS_block_size);
|
||||
std::string compressed;
|
||||
|
||||
bool ok;
|
||||
@@ -1614,14 +1636,10 @@ class Benchmark {
|
||||
}
|
||||
|
||||
void Open() {
|
||||
assert(db_ == nullptr);
|
||||
assert(db_.db == nullptr);
|
||||
Options options;
|
||||
options.create_if_missing = !FLAGS_use_existing_db;
|
||||
options.block_cache = cache_;
|
||||
options.block_cache_compressed = compressed_cache_;
|
||||
if (cache_ == nullptr) {
|
||||
options.no_block_cache = true;
|
||||
}
|
||||
options.create_missing_column_families = FLAGS_num_column_families > 1;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
options.max_write_buffer_number = FLAGS_max_write_buffer_number;
|
||||
options.min_write_buffer_number_to_merge =
|
||||
@@ -1629,17 +1647,25 @@ class Benchmark {
|
||||
options.max_background_compactions = FLAGS_max_background_compactions;
|
||||
options.max_background_flushes = FLAGS_max_background_flushes;
|
||||
options.compaction_style = FLAGS_compaction_style_e;
|
||||
options.block_size = FLAGS_block_size;
|
||||
options.block_restart_interval = FLAGS_block_restart_interval;
|
||||
options.filter_policy = filter_policy_;
|
||||
if (FLAGS_prefix_size != 0) {
|
||||
options.prefix_extractor.reset(
|
||||
NewFixedPrefixTransform(FLAGS_prefix_size));
|
||||
}
|
||||
if (FLAGS_use_uint64_comparator) {
|
||||
options.comparator = test::Uint64Comparator();
|
||||
if (FLAGS_key_size != 8) {
|
||||
fprintf(stderr, "Using Uint64 comparator but key size is not 8.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
options.memtable_prefix_bloom_bits = FLAGS_memtable_bloom_bits;
|
||||
options.bloom_locality = FLAGS_bloom_locality;
|
||||
options.max_open_files = FLAGS_open_files;
|
||||
options.statistics = dbstats;
|
||||
if (FLAGS_enable_io_prio) {
|
||||
FLAGS_env->LowerThreadPoolIOPriority(Env::LOW);
|
||||
FLAGS_env->LowerThreadPoolIOPriority(Env::HIGH);
|
||||
}
|
||||
options.env = FLAGS_env;
|
||||
options.disableDataSync = FLAGS_disable_data_sync;
|
||||
options.use_fsync = FLAGS_use_fsync;
|
||||
@@ -1700,13 +1726,33 @@ class Benchmark {
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
options.table_factory = std::shared_ptr<TableFactory>(
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
} else if (FLAGS_use_cuckoo_table) {
|
||||
if (FLAGS_cuckoo_hash_ratio > 1 || FLAGS_cuckoo_hash_ratio < 0) {
|
||||
fprintf(stderr, "Invalid cuckoo_hash_ratio\n");
|
||||
exit(1);
|
||||
}
|
||||
options.table_factory = std::shared_ptr<TableFactory>(
|
||||
NewCuckooTableFactory(FLAGS_cuckoo_hash_ratio));
|
||||
} else {
|
||||
BlockBasedTableOptions block_based_options;
|
||||
if (FLAGS_use_hash_search) {
|
||||
if (FLAGS_prefix_size == 0) {
|
||||
fprintf(stderr,
|
||||
"prefix_size not assigned when enable use_hash_search \n");
|
||||
exit(1);
|
||||
}
|
||||
block_based_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
} else {
|
||||
block_based_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
||||
}
|
||||
if (cache_ == nullptr) {
|
||||
block_based_options.no_block_cache = true;
|
||||
}
|
||||
block_based_options.block_cache = cache_;
|
||||
block_based_options.block_cache_compressed = compressed_cache_;
|
||||
block_based_options.block_size = FLAGS_block_size;
|
||||
block_based_options.block_restart_interval = FLAGS_block_restart_interval;
|
||||
block_based_options.filter_policy = filter_policy_;
|
||||
options.table_factory.reset(
|
||||
NewBlockBasedTableFactory(block_based_options));
|
||||
}
|
||||
@@ -1797,10 +1843,9 @@ class Benchmark {
|
||||
OpenDb(options, FLAGS_db, &db_);
|
||||
} else {
|
||||
multi_dbs_.clear();
|
||||
multi_dbs_.resize(FLAGS_num_multi_db);
|
||||
for (int i = 0; i < FLAGS_num_multi_db; i++) {
|
||||
DB* db;
|
||||
OpenDb(options, GetDbNameForMultiple(FLAGS_db, i), &db);
|
||||
multi_dbs_.push_back(db);
|
||||
OpenDb(options, GetDbNameForMultiple(FLAGS_db, i), &multi_dbs_[i]);
|
||||
}
|
||||
}
|
||||
if (FLAGS_min_level_to_compress >= 0) {
|
||||
@@ -1808,12 +1853,27 @@ class Benchmark {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenDb(Options options, std::string db_name, DB** db) {
|
||||
void OpenDb(const Options& options, const std::string& db_name,
|
||||
DBWithColumnFamilies* db) {
|
||||
Status s;
|
||||
if(FLAGS_readonly) {
|
||||
s = DB::OpenForReadOnly(options, db_name, db);
|
||||
// Open with column families if necessary.
|
||||
if (FLAGS_num_column_families > 1) {
|
||||
db->cfh.resize(FLAGS_num_column_families);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (int i = 0; i < FLAGS_num_column_families; i++) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
ColumnFamilyName(i), ColumnFamilyOptions(options)));
|
||||
}
|
||||
if (FLAGS_readonly) {
|
||||
s = DB::OpenForReadOnly(options, db_name, column_families,
|
||||
&db->cfh, &db->db);
|
||||
} else {
|
||||
s = DB::Open(options, db_name, column_families, &db->cfh, &db->db);
|
||||
}
|
||||
} else if (FLAGS_readonly) {
|
||||
s = DB::OpenForReadOnly(options, db_name, &db->db);
|
||||
} else {
|
||||
s = DB::Open(options, db_name, db);
|
||||
s = DB::Open(options, db_name, &db->db);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "open error: %s\n", s.ToString().c_str());
|
||||
@@ -1881,10 +1941,18 @@ class Benchmark {
|
||||
};
|
||||
|
||||
DB* SelectDB(ThreadState* thread) {
|
||||
if (db_ != nullptr) {
|
||||
return db_;
|
||||
} else {
|
||||
return multi_dbs_[thread->rand.Next() % multi_dbs_.size()];
|
||||
return SelectDBWithCfh(thread)->db;
|
||||
}
|
||||
|
||||
DBWithColumnFamilies* SelectDBWithCfh(ThreadState* thread) {
|
||||
return SelectDBWithCfh(thread->rand.Next());
|
||||
}
|
||||
|
||||
DBWithColumnFamilies* SelectDBWithCfh(uint64_t rand_int) {
|
||||
if (db_.db != nullptr) {
|
||||
return &db_;
|
||||
} else {
|
||||
return &multi_dbs_[rand_int % multi_dbs_.size()];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1893,7 +1961,7 @@ class Benchmark {
|
||||
const int64_t num_ops = writes_ == 0 ? num_ : writes_;
|
||||
|
||||
size_t num_key_gens = 1;
|
||||
if (db_ == nullptr) {
|
||||
if (db_.db == nullptr) {
|
||||
num_key_gens = multi_dbs_.size();
|
||||
}
|
||||
std::vector<std::unique_ptr<KeyGenerator>> key_gens(num_key_gens);
|
||||
@@ -1916,20 +1984,25 @@ class Benchmark {
|
||||
Slice key = AllocateKey();
|
||||
std::unique_ptr<const char[]> key_guard(key.data());
|
||||
while (!duration.Done(entries_per_batch_)) {
|
||||
size_t id = 0;
|
||||
DB* db_to_write = db_;
|
||||
if (db_to_write == nullptr) {
|
||||
id = thread->rand.Next() % num_key_gens;
|
||||
db_to_write = multi_dbs_[id];
|
||||
}
|
||||
size_t id = thread->rand.Next() % num_key_gens;
|
||||
DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(id);
|
||||
batch.Clear();
|
||||
for (int64_t j = 0; j < entries_per_batch_; j++) {
|
||||
GenerateKeyFromInt(key_gens[id]->Next(), FLAGS_num, &key);
|
||||
batch.Put(key, gen.Generate(value_size_));
|
||||
int64_t rand_num = key_gens[id]->Next();
|
||||
GenerateKeyFromInt(rand_num, FLAGS_num, &key);
|
||||
if (FLAGS_num_column_families <= 1) {
|
||||
batch.Put(key, gen.Generate(value_size_));
|
||||
} else {
|
||||
// We use same rand_num as seed for key and column family so that we
|
||||
// can deterministically find the cfh corresponding to a particular
|
||||
// key while reading the key.
|
||||
batch.Put(db_with_cfh->cfh[rand_num % db_with_cfh->cfh.size()],
|
||||
key, gen.Generate(value_size_));
|
||||
}
|
||||
bytes += value_size_ + key_size_;
|
||||
}
|
||||
s = db_to_write->Write(write_options_, &batch);
|
||||
thread->stats.FinishedOps(db_to_write, entries_per_batch_);
|
||||
s = db_with_cfh->db->Write(write_options_, &batch);
|
||||
thread->stats.FinishedOps(db_with_cfh->db, entries_per_batch_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "put error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
@@ -1939,11 +2012,11 @@ class Benchmark {
|
||||
}
|
||||
|
||||
void ReadSequential(ThreadState* thread) {
|
||||
if (db_ != nullptr) {
|
||||
ReadSequential(thread, db_);
|
||||
if (db_.db != nullptr) {
|
||||
ReadSequential(thread, db_.db);
|
||||
} else {
|
||||
for (DB* db : multi_dbs_) {
|
||||
ReadSequential(thread, db);
|
||||
for (const auto& db_with_cfh : multi_dbs_) {
|
||||
ReadSequential(thread, db_with_cfh.db);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1962,11 +2035,11 @@ class Benchmark {
|
||||
}
|
||||
|
||||
void ReadReverse(ThreadState* thread) {
|
||||
if (db_ != nullptr) {
|
||||
ReadReverse(thread, db_);
|
||||
if (db_.db != nullptr) {
|
||||
ReadReverse(thread, db_.db);
|
||||
} else {
|
||||
for (DB* db : multi_dbs_) {
|
||||
ReadReverse(thread, db);
|
||||
for (const auto& db_with_cfh : multi_dbs_) {
|
||||
ReadReverse(thread, db_with_cfh.db);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1977,7 +2050,7 @@ class Benchmark {
|
||||
int64_t bytes = 0;
|
||||
for (iter->SeekToLast(); i < reads_ && iter->Valid(); iter->Prev()) {
|
||||
bytes += iter->key().size() + iter->value().size();
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db, 1);
|
||||
++i;
|
||||
}
|
||||
delete iter;
|
||||
@@ -1994,13 +2067,24 @@ class Benchmark {
|
||||
|
||||
Duration duration(FLAGS_duration, reads_);
|
||||
while (!duration.Done(1)) {
|
||||
DB* db = SelectDB(thread);
|
||||
GenerateKeyFromInt(thread->rand.Next() % FLAGS_num, FLAGS_num, &key);
|
||||
DBWithColumnFamilies* db_with_cfh = SelectDBWithCfh(thread);
|
||||
// We use same key_rand as seed for key and column family so that we can
|
||||
// deterministically find the cfh corresponding to a particular key, as it
|
||||
// is done in DoWrite method.
|
||||
int64_t key_rand = thread->rand.Next() % FLAGS_num;
|
||||
GenerateKeyFromInt(key_rand, FLAGS_num, &key);
|
||||
read++;
|
||||
if (db->Get(options, key, &value).ok()) {
|
||||
Status s;
|
||||
if (FLAGS_num_column_families > 1) {
|
||||
s = db_with_cfh->db->Get(options,
|
||||
db_with_cfh->cfh[key_rand % db_with_cfh->cfh.size()], key, &value);
|
||||
} else {
|
||||
s = db_with_cfh->db->Get(options, key, &value);
|
||||
}
|
||||
if (s.ok()) {
|
||||
found++;
|
||||
}
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db_with_cfh->db, 1);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -2042,6 +2126,7 @@ class Benchmark {
|
||||
++found;
|
||||
}
|
||||
}
|
||||
thread->stats.FinishedOps(db, entries_per_batch_);
|
||||
}
|
||||
for (auto& k : keys) {
|
||||
delete k.data();
|
||||
@@ -2080,11 +2165,11 @@ class Benchmark {
|
||||
|
||||
Iterator* single_iter = nullptr;
|
||||
std::vector<Iterator*> multi_iters;
|
||||
if (db_ != nullptr) {
|
||||
single_iter = db_->NewIterator(options);
|
||||
if (db_.db != nullptr) {
|
||||
single_iter = db_.db->NewIterator(options);
|
||||
} else {
|
||||
for (DB* db : multi_dbs_) {
|
||||
multi_iters.push_back(db->NewIterator(options));
|
||||
for (const auto& db_with_cfh : multi_dbs_) {
|
||||
multi_iters.push_back(db_with_cfh.db->NewIterator(options));
|
||||
}
|
||||
}
|
||||
uint64_t last_refresh = FLAGS_env->NowMicros();
|
||||
@@ -2097,16 +2182,16 @@ class Benchmark {
|
||||
if (!FLAGS_use_tailing_iterator && FLAGS_iter_refresh_interval_us >= 0) {
|
||||
uint64_t now = FLAGS_env->NowMicros();
|
||||
if (now - last_refresh > (uint64_t)FLAGS_iter_refresh_interval_us) {
|
||||
if (db_ != nullptr) {
|
||||
if (db_.db != nullptr) {
|
||||
delete single_iter;
|
||||
single_iter = db_->NewIterator(options);
|
||||
single_iter = db_.db->NewIterator(options);
|
||||
} else {
|
||||
for (auto iter : multi_iters) {
|
||||
delete iter;
|
||||
}
|
||||
multi_iters.clear();
|
||||
for (DB* db : multi_dbs_) {
|
||||
multi_iters.push_back(db->NewIterator(options));
|
||||
for (const auto& db_with_cfh : multi_dbs_) {
|
||||
multi_iters.push_back(db_with_cfh.db->NewIterator(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2124,7 +2209,7 @@ class Benchmark {
|
||||
if (iter_to_use->Valid() && iter_to_use->key().compare(key) == 0) {
|
||||
found++;
|
||||
}
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db_.db, 1);
|
||||
}
|
||||
delete single_iter;
|
||||
for (auto iter : multi_iters) {
|
||||
@@ -2224,7 +2309,7 @@ class Benchmark {
|
||||
fprintf(stderr, "put error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db_.db, 1);
|
||||
|
||||
++num_writes;
|
||||
if (writes_per_second_by_10 && num_writes >= writes_per_second_by_10) {
|
||||
@@ -2384,7 +2469,7 @@ class Benchmark {
|
||||
deletes_done++;
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db_.db, 1);
|
||||
}
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg),
|
||||
@@ -2523,7 +2608,7 @@ class Benchmark {
|
||||
fprintf(stderr, "put error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db, 1);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -2559,7 +2644,7 @@ class Benchmark {
|
||||
fprintf(stderr, "merge error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db, 1);
|
||||
}
|
||||
|
||||
// Print some statistics
|
||||
@@ -2620,7 +2705,7 @@ class Benchmark {
|
||||
|
||||
}
|
||||
|
||||
thread->stats.FinishedOps(db_, 1);
|
||||
thread->stats.FinishedOps(db, 1);
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
@@ -2637,11 +2722,11 @@ class Benchmark {
|
||||
}
|
||||
|
||||
void PrintStats(const char* key) {
|
||||
if (db_ != nullptr) {
|
||||
PrintStats(db_, key, false);
|
||||
if (db_.db != nullptr) {
|
||||
PrintStats(db_.db, key, false);
|
||||
}
|
||||
for (DB* db : multi_dbs_) {
|
||||
PrintStats(db, key, true);
|
||||
for (const auto& db_with_cfh : multi_dbs_) {
|
||||
PrintStats(db_with_cfh.db, key, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
int DBImpl::IsFileDeletionsEnabled() const {
|
||||
return disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable) {
|
||||
|
||||
+507
-333
File diff suppressed because it is too large.
Load diff
+56
-5
@@ -127,6 +127,7 @@ class DBImpl : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status DisableFileDeletions();
|
||||
virtual Status EnableFileDeletions(bool force);
|
||||
virtual int IsFileDeletionsEnabled() const;
|
||||
// All the returned filenames start with "/"
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size,
|
||||
@@ -309,6 +310,7 @@ class DBImpl : public DB {
|
||||
friend struct SuperVersion;
|
||||
struct CompactionState;
|
||||
struct Writer;
|
||||
struct WriteContext;
|
||||
|
||||
Status NewDB();
|
||||
|
||||
@@ -347,13 +349,38 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t SlowdownAmount(int n, double bottom, double top);
|
||||
|
||||
// TODO(icanadi) free superversion_to_free and old_log outside of mutex
|
||||
// Before applying write operation (such as DBImpl::Write, DBImpl::Flush)
|
||||
// thread should grab the mutex_ and be the first on writers queue.
|
||||
// BeginWrite is used for it.
|
||||
// Be aware! Writer's job can be done by other thread (see DBImpl::Write
|
||||
// for examples), so check it via w.done before applying changes.
|
||||
//
|
||||
// Writer* w: writer to be placed in the queue
|
||||
// uint64_t expiration_time: maximum time to be in the queue
|
||||
// See also: EndWrite
|
||||
Status BeginWrite(Writer* w, uint64_t expiration_time);
|
||||
|
||||
// After doing write job, we need to remove already used writers from
|
||||
// writers_ queue and notify head of the queue about it.
|
||||
// EndWrite is used for this.
|
||||
//
|
||||
// Writer* w: Writer, that was added by BeginWrite function
|
||||
// Writer* last_writer: Since we can join a few Writers (as DBImpl::Write
|
||||
// does)
|
||||
// we should pass last_writer as a parameter to
|
||||
// EndWrite
|
||||
// (if you don't touch other writers, just pass w)
|
||||
// Status status: Status of write operation
|
||||
// See also: BeginWrite
|
||||
void EndWrite(Writer* w, Writer* last_writer, Status status);
|
||||
|
||||
Status MakeRoomForWrite(ColumnFamilyData* cfd,
|
||||
bool force /* flush even if there is room? */,
|
||||
autovector<SuperVersion*>* superversions_to_free,
|
||||
autovector<log::Writer*>* logs_to_free,
|
||||
WriteContext* context,
|
||||
uint64_t expiration_time);
|
||||
|
||||
Status SetNewMemtableAndNewLogFile(ColumnFamilyData* cfd,
|
||||
WriteContext* context);
|
||||
|
||||
void BuildBatchGroup(Writer** last_writer,
|
||||
autovector<WriteBatch*>* write_batch_group);
|
||||
|
||||
@@ -611,6 +638,16 @@ class DBImpl : public DB {
|
||||
void InstallSuperVersion(ColumnFamilyData* cfd,
|
||||
DeletionState& deletion_state);
|
||||
|
||||
// Find Super version and reference it. Based on options, it might return
|
||||
// the thread local cached one.
|
||||
inline SuperVersion* GetAndRefSuperVersion(ColumnFamilyData* cfd);
|
||||
|
||||
// Un-reference the super version and return it to thread local cache if
|
||||
// needed. If it is the last reference of the super version. Clean it up
|
||||
// after un-referencing it.
|
||||
inline void ReturnAndCleanupSuperVersion(ColumnFamilyData* cfd,
|
||||
SuperVersion* sv);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
@@ -623,14 +660,28 @@ class DBImpl : public DB {
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, std::string* value,
|
||||
bool* value_found = nullptr);
|
||||
|
||||
bool GetIntPropertyInternal(ColumnFamilyHandle* column_family,
|
||||
DBPropertyType property_type,
|
||||
bool need_out_of_mutex, uint64_t* value);
|
||||
};
|
||||
|
||||
// Sanitize db options. The caller should delete result.info_log if
|
||||
// it is not equal to src.info_log.
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
const InternalKeyComparator* icmp,
|
||||
const InternalFilterPolicy* ipolicy,
|
||||
const Options& src);
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
|
||||
|
||||
// Fix user-supplied options to be reasonable
|
||||
template <class T, class V>
|
||||
static void ClipToRange(T* ptr, V minvalue, V maxvalue) {
|
||||
if (static_cast<V>(*ptr) > maxvalue) *ptr = maxvalue;
|
||||
if (static_cast<V>(*ptr) < minvalue) *ptr = minvalue;
|
||||
}
|
||||
|
||||
// Dump db file summary, implemented in util/
|
||||
extern void DumpDBFileSummary(const DBOptions& options,
|
||||
const std::string& dbname);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
|
||||
+19
-13
@@ -32,60 +32,66 @@ class DBImplReadOnly : public DBImpl {
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value);
|
||||
std::string* value) override;
|
||||
|
||||
// TODO: Implement ReadOnly MultiGet?
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family);
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators);
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) {
|
||||
const Slice& value) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::Merge;
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) {
|
||||
const Slice& value) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::Delete;
|
||||
virtual Status Delete(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key) {
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status Write(const WriteOptions& options, WriteBatch* updates) {
|
||||
virtual Status Write(const WriteOptions& options,
|
||||
WriteBatch* updates) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false,
|
||||
int target_level = -1) {
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status DisableFileDeletions() {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status DisableFileDeletions() override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status EnableFileDeletions(bool force) {
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable = true) {
|
||||
bool flush_memtable = true) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
using DBImpl::Flush;
|
||||
virtual Status Flush(const FlushOptions& options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
|
||||
+33
-11
@@ -11,6 +11,7 @@
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -71,6 +72,7 @@ class DBIter: public Iterator {
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(options.statistics.get()) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
has_prefix_extractor_ = (options.prefix_extractor.get() != nullptr);
|
||||
max_skip_ = options.max_sequential_skip_in_iterations;
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
@@ -130,6 +132,7 @@ class DBIter: public Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
bool has_prefix_extractor_;
|
||||
bool arena_mode_;
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
@@ -191,9 +194,8 @@ void DBIter::Next() {
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker
|
||||
inline void DBIter::FindNextUserEntry(bool skipping) {
|
||||
PERF_TIMER_AUTO(find_next_user_entry_time);
|
||||
PERF_TIMER_GUARD(find_next_user_entry_time);
|
||||
FindNextUserEntryInternal(skipping);
|
||||
PERF_TIMER_STOP(find_next_user_entry_time);
|
||||
}
|
||||
|
||||
// Actual implementation of DBIter::FindNextUserEntry()
|
||||
@@ -549,12 +551,17 @@ void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) {
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
|
||||
saved_key_.Clear();
|
||||
// now savved_key is used to store internal key.
|
||||
saved_key_.SetInternalKey(target, sequence_);
|
||||
PERF_TIMER_AUTO(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetKey());
|
||||
PERF_TIMER_STOP(seek_internal_seek_time);
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetKey());
|
||||
}
|
||||
|
||||
if (iter_->Valid()) {
|
||||
direction_ = kForward;
|
||||
ClearSavedValue();
|
||||
@@ -565,11 +572,19 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
void DBIter::SeekToFirst() {
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek wiil be used.
|
||||
if (has_prefix_extractor_) {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kForward;
|
||||
ClearSavedValue();
|
||||
PERF_TIMER_AUTO(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
PERF_TIMER_STOP(seek_internal_seek_time);
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
}
|
||||
|
||||
if (iter_->Valid()) {
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
} else {
|
||||
@@ -578,11 +593,18 @@ void DBIter::SeekToFirst() {
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek wiil be used.
|
||||
if (has_prefix_extractor_) {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kReverse;
|
||||
ClearSavedValue();
|
||||
PERF_TIMER_AUTO(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
PERF_TIMER_STOP(seek_internal_seek_time);
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
}
|
||||
|
||||
PrevInternal();
|
||||
}
|
||||
|
||||
+298
-91
@@ -30,6 +30,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "util/hash.h"
|
||||
@@ -101,6 +102,11 @@ class AtomicCounter {
|
||||
count_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct OptionsOverride {
|
||||
std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace anon
|
||||
|
||||
static std::string Key(int i) {
|
||||
@@ -303,37 +309,34 @@ class SpecialEnv : public EnvWrapper {
|
||||
};
|
||||
|
||||
class DBTest {
|
||||
private:
|
||||
const FilterPolicy* filter_policy_;
|
||||
|
||||
protected:
|
||||
// Sequence of option configurations to try
|
||||
enum OptionConfig {
|
||||
kBlockBasedTableWithWholeKeyHashIndex,
|
||||
kDefault,
|
||||
kBlockBasedTableWithPrefixHashIndex,
|
||||
kPlainTableFirstBytePrefix,
|
||||
kPlainTableAllBytesPrefix,
|
||||
kVectorRep,
|
||||
kHashLinkList,
|
||||
kHashCuckoo,
|
||||
kMergePut,
|
||||
kFilter,
|
||||
kUncompressed,
|
||||
kNumLevel_3,
|
||||
kDBLogDir,
|
||||
kWalDir,
|
||||
kManifestFileSize,
|
||||
kCompactOnFlush,
|
||||
kPerfOptions,
|
||||
kDeletesFilterFirst,
|
||||
kHashSkipList,
|
||||
kUniversalCompaction,
|
||||
kCompressedBlockCache,
|
||||
kInfiniteMaxOpenFiles,
|
||||
kxxHashChecksum,
|
||||
kFIFOCompaction,
|
||||
kEnd
|
||||
kDefault = 0,
|
||||
kBlockBasedTableWithPrefixHashIndex = 1,
|
||||
kBlockBasedTableWithWholeKeyHashIndex = 2,
|
||||
kPlainTableFirstBytePrefix = 3,
|
||||
kPlainTableAllBytesPrefix = 4,
|
||||
kVectorRep = 5,
|
||||
kHashLinkList = 6,
|
||||
kHashCuckoo = 7,
|
||||
kMergePut = 8,
|
||||
kFilter = 9,
|
||||
kUncompressed = 10,
|
||||
kNumLevel_3 = 11,
|
||||
kDBLogDir = 12,
|
||||
kWalDir = 13,
|
||||
kManifestFileSize = 14,
|
||||
kCompactOnFlush = 15,
|
||||
kPerfOptions = 16,
|
||||
kDeletesFilterFirst = 17,
|
||||
kHashSkipList = 18,
|
||||
kUniversalCompaction = 19,
|
||||
kCompressedBlockCache = 20,
|
||||
kInfiniteMaxOpenFiles = 21,
|
||||
kxxHashChecksum = 22,
|
||||
kFIFOCompaction = 23,
|
||||
kEnd = 24
|
||||
};
|
||||
int option_config_;
|
||||
|
||||
@@ -359,9 +362,9 @@ class DBTest {
|
||||
kSkipFIFOCompaction = 128,
|
||||
};
|
||||
|
||||
|
||||
DBTest() : option_config_(kDefault),
|
||||
env_(new SpecialEnv(Env::Default())) {
|
||||
filter_policy_ = NewBloomFilterPolicy(10);
|
||||
dbname_ = test::TmpDir() + "/db_test";
|
||||
ASSERT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
@@ -377,7 +380,6 @@ class DBTest {
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 0);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
delete env_;
|
||||
delete filter_policy_;
|
||||
}
|
||||
|
||||
// Switch to a fresh database with the next option configuration to
|
||||
@@ -405,7 +407,7 @@ class DBTest {
|
||||
|| option_config_ == kPlainTableFirstBytePrefix)) {
|
||||
continue;
|
||||
}
|
||||
if ((skip_mask & kSkipPlainTable) &&
|
||||
if ((skip_mask & kSkipHashIndex) &&
|
||||
(option_config_ == kBlockBasedTableWithPrefixHashIndex ||
|
||||
option_config_ == kBlockBasedTableWithWholeKeyHashIndex)) {
|
||||
continue;
|
||||
@@ -445,14 +447,19 @@ class DBTest {
|
||||
}
|
||||
|
||||
// Return the current option configuration.
|
||||
Options CurrentOptions() {
|
||||
Options CurrentOptions(
|
||||
const anon::OptionsOverride& options_override = anon::OptionsOverride()) {
|
||||
Options options;
|
||||
return CurrentOptions(options);
|
||||
return CurrentOptions(options, options_override);
|
||||
}
|
||||
|
||||
Options CurrentOptions(const Options& defaultOptions) {
|
||||
Options CurrentOptions(
|
||||
const Options& defaultOptions,
|
||||
const anon::OptionsOverride& options_override = anon::OptionsOverride()) {
|
||||
// this redudant copy is to minimize code change w/o having lint error.
|
||||
Options options = defaultOptions;
|
||||
BlockBasedTableOptions table_options;
|
||||
bool set_block_based_table_factory = true;
|
||||
switch (option_config_) {
|
||||
case kHashSkipList:
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
@@ -464,18 +471,20 @@ class DBTest {
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.allow_mmap_reads = true;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
set_block_based_table_factory = false;
|
||||
break;
|
||||
case kPlainTableAllBytesPrefix:
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
options.allow_mmap_reads = true;
|
||||
options.max_sequential_skip_in_iterations = 999999;
|
||||
set_block_based_table_factory = false;
|
||||
break;
|
||||
case kMergePut:
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
break;
|
||||
case kFilter:
|
||||
options.filter_policy = filter_policy_;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
break;
|
||||
case kUncompressed:
|
||||
options.compression = kNoCompression;
|
||||
@@ -520,15 +529,13 @@ class DBTest {
|
||||
break;
|
||||
case kCompressedBlockCache:
|
||||
options.allow_mmap_writes = true;
|
||||
options.block_cache_compressed = NewLRUCache(8*1024*1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8*1024*1024);
|
||||
break;
|
||||
case kInfiniteMaxOpenFiles:
|
||||
options.max_open_files = -1;
|
||||
break;
|
||||
case kxxHashChecksum: {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.checksum = kxxHash;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
}
|
||||
case kFIFOCompaction: {
|
||||
@@ -536,22 +543,25 @@ class DBTest {
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPrefixHashIndex: {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithWholeKeyHashIndex: {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (options_override.filter_policy) {
|
||||
table_options.filter_policy = options_override.filter_policy;
|
||||
}
|
||||
if (set_block_based_table_factory) {
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -651,7 +661,6 @@ class DBTest {
|
||||
opts.create_if_missing = true;
|
||||
}
|
||||
last_options_ = opts;
|
||||
|
||||
return DB::Open(opts, dbname_, &db_);
|
||||
}
|
||||
|
||||
@@ -1140,6 +1149,31 @@ TEST(DBTest, Empty) {
|
||||
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
env_->delay_sstable_sync_.Release_Store(nullptr); // Release sync calls
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("1", num);
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("2", num);
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("3", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions(false));
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("2", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("0", num);
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
@@ -1161,18 +1195,27 @@ TEST(DBTest, ReadOnlyDB) {
|
||||
}
|
||||
ASSERT_EQ(count, 2);
|
||||
delete iter;
|
||||
Close();
|
||||
|
||||
// Reopen and flush memtable.
|
||||
Reopen();
|
||||
Flush();
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(&options));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
}
|
||||
|
||||
// Make sure that when options.block_cache is set, after a new table is
|
||||
// created its index/filter blocks are added to block cache.
|
||||
TEST(DBTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
||||
Options options = CurrentOptions();
|
||||
std::unique_ptr<const FilterPolicy> filter_policy(NewBloomFilterPolicy(20));
|
||||
options.filter_policy = filter_policy.get();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
|
||||
@@ -1186,6 +1229,10 @@ TEST(DBTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
||||
ASSERT_EQ(2, /* only index/filter were added */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
uint64_t int_num;
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
// Make sure filter block is in cache.
|
||||
std::string value;
|
||||
@@ -1454,8 +1501,9 @@ TEST(DBTest, KeyMayExist) {
|
||||
do {
|
||||
ReadOptions ropts;
|
||||
std::string value;
|
||||
Options options = CurrentOptions();
|
||||
options.filter_policy = NewBloomFilterPolicy(20);
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
Options options = CurrentOptions(options_override);
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
|
||||
@@ -1506,8 +1554,6 @@ TEST(DBTest, KeyMayExist) {
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
delete options.filter_policy;
|
||||
|
||||
// KeyMayExist function only checks data in block caches, which is not used
|
||||
// by plain table format.
|
||||
} while (
|
||||
@@ -1582,8 +1628,9 @@ TEST(DBTest, NonBlockingIteration) {
|
||||
// Tests Writebatch consistency and proper delete behaviour
|
||||
TEST(DBTest, FilterDeletes) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.filter_policy = NewBloomFilterPolicy(20);
|
||||
anon::OptionsOverride options_override;
|
||||
options_override.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
Options options = CurrentOptions(options_override);
|
||||
options.filter_deletes = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
WriteBatch batch;
|
||||
@@ -1613,8 +1660,6 @@ TEST(DBTest, FilterDeletes) {
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
ASSERT_EQ(AllEntriesFor("c", 1), "[ DEL, d ]"); // Delete issued
|
||||
batch.Clear();
|
||||
|
||||
delete options.filter_policy;
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
@@ -2489,6 +2534,10 @@ TEST(DBTest, GetProperty) {
|
||||
uint64_t int_num;
|
||||
SetPerfLevel(kEnableTime);
|
||||
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, "k1", big_value));
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
||||
ASSERT_EQ(num, "0");
|
||||
@@ -2521,9 +2570,13 @@ TEST(DBTest, GetProperty) {
|
||||
dbfull()->GetIntProperty("rocksdb.mem-table-flush-pending", &int_num));
|
||||
ASSERT_EQ(int_num, 1U);
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.compaction-pending", &int_num));
|
||||
ASSERT_EQ(int_num, 0);
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num));
|
||||
ASSERT_EQ(int_num, 4);
|
||||
ASSERT_EQ(int_num, 4U);
|
||||
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
sleeping_task_high.WakeUp();
|
||||
sleeping_task_high.WaitUntilDone();
|
||||
@@ -2538,8 +2591,29 @@ TEST(DBTest, GetProperty) {
|
||||
ASSERT_EQ(num, "1");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ(num, "4");
|
||||
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_GT(int_num, 0U);
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
options.max_open_files = 10;
|
||||
Reopen(&options);
|
||||
// After reopening, no table reader is loaded, so no memory for table readers
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num));
|
||||
ASSERT_GT(int_num, 0U);
|
||||
|
||||
// After reading a key, at least one table reader is loaded.
|
||||
Get("k5");
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_GT(int_num, 0U);
|
||||
}
|
||||
|
||||
TEST(DBTest, FLUSH) {
|
||||
@@ -3299,32 +3373,37 @@ TEST(DBTest, CompressedCache) {
|
||||
// Iteration 4: both block cache and compressed cache, but DB is not
|
||||
// compressed
|
||||
for (int iter = 0; iter < 4; iter++) {
|
||||
Options options = CurrentOptions();
|
||||
Options options;
|
||||
options.write_buffer_size = 64*1024; // small write buffer
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
options.block_cache = NewLRUCache(8*1024);
|
||||
options.block_cache_compressed = nullptr;
|
||||
table_options.block_cache = NewLRUCache(8*1024);
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
options.no_block_cache = true;
|
||||
options.block_cache = nullptr;
|
||||
options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache = nullptr;
|
||||
table_options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
options.block_cache = NewLRUCache(1024);
|
||||
options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
table_options.block_cache = NewLRUCache(1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 3:
|
||||
// both block cache and compressed cache, but DB is not compressed
|
||||
// also, make block cache sizes bigger, to trigger block cache hits
|
||||
options.block_cache = NewLRUCache(1024 * 1024);
|
||||
options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
||||
table_options.block_cache = NewLRUCache(1024 * 1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = kNoCompression;
|
||||
break;
|
||||
default:
|
||||
@@ -3333,9 +3412,11 @@ TEST(DBTest, CompressedCache) {
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
// default column family doesn't have block cache
|
||||
Options no_block_cache_opts;
|
||||
no_block_cache_opts.no_block_cache = true;
|
||||
no_block_cache_opts.statistics = options.statistics;
|
||||
options = CurrentOptions(options);
|
||||
BlockBasedTableOptions table_options_no_bc;
|
||||
table_options_no_bc.no_block_cache = true;
|
||||
no_block_cache_opts.table_factory.reset(
|
||||
NewBlockBasedTableFactory(table_options_no_bc));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"},
|
||||
{&no_block_cache_opts, &options});
|
||||
|
||||
@@ -4776,7 +4857,7 @@ TEST(DBTest, ApproximateSizes) {
|
||||
}
|
||||
// ApproximateOffsetOf() is not yet implemented in plain table format.
|
||||
} while (ChangeOptions(kSkipUniversalCompaction | kSkipFIFOCompaction |
|
||||
kSkipPlainTable));
|
||||
kSkipPlainTable | kSkipHashIndex));
|
||||
}
|
||||
|
||||
TEST(DBTest, ApproximateSizes_MixOfSmallAndLarge) {
|
||||
@@ -5195,7 +5276,6 @@ TEST(DBTest, CustomComparator) {
|
||||
new_options = CurrentOptions();
|
||||
new_options.create_if_missing = true;
|
||||
new_options.comparator = &cmp;
|
||||
new_options.filter_policy = nullptr; // Cannot use bloom filters
|
||||
new_options.write_buffer_size = 1000; // Compact more often
|
||||
new_options = CurrentOptions(new_options);
|
||||
DestroyAndReopen(&new_options);
|
||||
@@ -5603,11 +5683,16 @@ TEST(DBTest, FilesDeletedAfterCompaction) {
|
||||
|
||||
TEST(DBTest, BloomFilter) {
|
||||
do {
|
||||
env_->count_random_reads_ = true;
|
||||
Options options = CurrentOptions();
|
||||
env_->count_random_reads_ = true;
|
||||
options.env = env_;
|
||||
options.no_block_cache = true;
|
||||
options.filter_policy = NewBloomFilterPolicy(10);
|
||||
// ChangeCompactOptions() only changes compaction style, which does not
|
||||
// trigger reset of table_factory
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
|
||||
// Populate multiple layers
|
||||
@@ -5645,7 +5730,6 @@ TEST(DBTest, BloomFilter) {
|
||||
|
||||
env_->delay_sstable_sync_.Release_Store(nullptr);
|
||||
Close();
|
||||
delete options.filter_policy;
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
@@ -5865,7 +5949,8 @@ TEST(DBTest, CompactOnFlush) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<std::uint64_t> ListLogFiles(Env* env, const std::string& path) {
|
||||
std::vector<std::uint64_t> ListSpecificFiles(
|
||||
Env* env, const std::string& path, const FileType expected_file_type) {
|
||||
std::vector<std::string> files;
|
||||
std::vector<uint64_t> log_files;
|
||||
env->GetChildren(path, &files);
|
||||
@@ -5873,15 +5958,45 @@ std::vector<std::uint64_t> ListLogFiles(Env* env, const std::string& path) {
|
||||
FileType type;
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
if (ParseFileName(files[i], &number, &type)) {
|
||||
if (type == kLogFile) {
|
||||
if (type == expected_file_type) {
|
||||
log_files.push_back(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::move(log_files);
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> ListLogFiles(Env* env, const std::string& path) {
|
||||
return ListSpecificFiles(env, path, kLogFile);
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path) {
|
||||
return ListSpecificFiles(env, path, kTableFile);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(DBTest, FlushOneColumnFamily) {
|
||||
Options options;
|
||||
CreateAndReopenWithCF({"pikachu", "ilya", "muromec", "dobrynia", "nikitich",
|
||||
"alyosha", "popovich"},
|
||||
&options);
|
||||
|
||||
ASSERT_OK(Put(0, "Default", "Default"));
|
||||
ASSERT_OK(Put(1, "pikachu", "pikachu"));
|
||||
ASSERT_OK(Put(2, "ilya", "ilya"));
|
||||
ASSERT_OK(Put(3, "muromec", "muromec"));
|
||||
ASSERT_OK(Put(4, "dobrynia", "dobrynia"));
|
||||
ASSERT_OK(Put(5, "nikitich", "nikitich"));
|
||||
ASSERT_OK(Put(6, "alyosha", "alyosha"));
|
||||
ASSERT_OK(Put(7, "popovich", "popovich"));
|
||||
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
Flush(i);
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), i + 1U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, WALArchivalTtl) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
@@ -5990,6 +6105,52 @@ TEST(DBTest, WALArchivalSizeLimit) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, PurgeInfoLogs) {
|
||||
Options options = CurrentOptions();
|
||||
options.keep_log_file_num = 5;
|
||||
options.create_if_missing = true;
|
||||
for (int mode = 0; mode <= 1; mode++) {
|
||||
if (mode == 1) {
|
||||
options.db_log_dir = dbname_ + "_logs";
|
||||
env_->CreateDirIfMissing(options.db_log_dir);
|
||||
} else {
|
||||
options.db_log_dir = "";
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Reopen(&options);
|
||||
}
|
||||
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(options.db_log_dir.empty() ? dbname_ : options.db_log_dir,
|
||||
&files);
|
||||
int info_log_count = 0;
|
||||
for (std::string file : files) {
|
||||
if (file.find("LOG") != std::string::npos) {
|
||||
info_log_count++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(5, info_log_count);
|
||||
|
||||
Destroy(&options);
|
||||
// For mode (1), test DestoryDB() to delete all the logs under DB dir.
|
||||
// For mode (2), no info log file should have been put under DB dir.
|
||||
std::vector<std::string> db_files;
|
||||
env_->GetChildren(dbname_, &db_files);
|
||||
for (std::string file : db_files) {
|
||||
ASSERT_TRUE(file.find("LOG") == std::string::npos);
|
||||
}
|
||||
|
||||
if (mode == 1) {
|
||||
// Cleaning up
|
||||
env_->GetChildren(options.db_log_dir, &files);
|
||||
for (std::string file : files) {
|
||||
env_->DeleteFile(options.db_log_dir + "/" + file);
|
||||
}
|
||||
env_->DeleteDir(options.db_log_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
SequenceNumber ReadRecords(
|
||||
std::unique_ptr<TransactionLogIterator>& iter,
|
||||
@@ -6334,13 +6495,26 @@ static void MTThreadBody(void* arg) {
|
||||
// into each of the CFs
|
||||
// We add some padding for force compactions.
|
||||
int unique_id = rnd.Uniform(1000000);
|
||||
WriteBatch batch;
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id,
|
||||
static_cast<int>(counter), cf, unique_id);
|
||||
batch.Put(t->state->test->handles_[cf], Slice(keybuf), Slice(valbuf));
|
||||
|
||||
// Half of the time directly use WriteBatch. Half of the time use
|
||||
// WriteBatchWithIndex.
|
||||
if (rnd.OneIn(2)) {
|
||||
WriteBatch batch;
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id,
|
||||
static_cast<int>(counter), cf, unique_id);
|
||||
batch.Put(t->state->test->handles_[cf], Slice(keybuf), Slice(valbuf));
|
||||
}
|
||||
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
||||
} else {
|
||||
WriteBatchWithIndex batch(db->GetOptions().comparator);
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
snprintf(valbuf, sizeof(valbuf), "%d.%d.%d.%d.%-1000d", key, id,
|
||||
static_cast<int>(counter), cf, unique_id);
|
||||
batch.Put(t->state->test->handles_[cf], Slice(keybuf), Slice(valbuf));
|
||||
}
|
||||
ASSERT_OK(db->Write(WriteOptions(), batch.GetWriteBatch()));
|
||||
}
|
||||
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
||||
} else {
|
||||
// Read a value and verify that it matches the pattern written above
|
||||
// and that writes to all column families were atomic (unique_id is the
|
||||
@@ -6726,7 +6900,14 @@ class ModelDB: public DB {
|
||||
iter_ = map_->lower_bound(k.ToString());
|
||||
}
|
||||
virtual void Next() { ++iter_; }
|
||||
virtual void Prev() { --iter_; }
|
||||
virtual void Prev() {
|
||||
if (iter_ == map_->begin()) {
|
||||
iter_ = map_->end();
|
||||
return;
|
||||
}
|
||||
--iter_;
|
||||
}
|
||||
|
||||
virtual Slice key() const { return iter_->first; }
|
||||
virtual Slice value() const { return iter_->second; }
|
||||
virtual Status status() const { return Status::OK(); }
|
||||
@@ -6854,8 +7035,14 @@ TEST(DBTest, Randomized) {
|
||||
}
|
||||
|
||||
if ((step % 100) == 0) {
|
||||
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
|
||||
ASSERT_TRUE(CompareIterators(step, &model, db_, model_snap, db_snap));
|
||||
// For DB instances that use the hash index + block-based table, the
|
||||
// iterator will be invalid right when seeking a non-existent key, right
|
||||
// than return a key that is close to it.
|
||||
if (option_config_ != kBlockBasedTableWithWholeKeyHashIndex &&
|
||||
option_config_ != kBlockBasedTableWithPrefixHashIndex) {
|
||||
ASSERT_TRUE(CompareIterators(step, &model, db_, nullptr, nullptr));
|
||||
ASSERT_TRUE(CompareIterators(step, &model, db_, model_snap, db_snap));
|
||||
}
|
||||
|
||||
// Save a snapshot from each DB this time that we'll use next
|
||||
// time we compare things, to make sure the current state is
|
||||
@@ -6869,12 +7056,18 @@ TEST(DBTest, Randomized) {
|
||||
model_snap = model.GetSnapshot();
|
||||
db_snap = db_->GetSnapshot();
|
||||
}
|
||||
|
||||
if ((step % 2000) == 0) {
|
||||
fprintf(stdout,
|
||||
"DBTest.Randomized, option ID: %d, step: %d out of %d\n",
|
||||
option_config_, step, N);
|
||||
}
|
||||
}
|
||||
if (model_snap != nullptr) model.ReleaseSnapshot(model_snap);
|
||||
if (db_snap != nullptr) db_->ReleaseSnapshot(db_snap);
|
||||
// skip cuckoo hash as it does not support snapshot.
|
||||
} while (ChangeOptions(kSkipDeletesFilterFirst |
|
||||
kSkipNoSeekToLast | kSkipHashCuckoo));
|
||||
} while (ChangeOptions(kSkipDeletesFilterFirst | kSkipNoSeekToLast |
|
||||
kSkipHashCuckoo));
|
||||
}
|
||||
|
||||
TEST(DBTest, MultiGetSimple) {
|
||||
@@ -7002,15 +7195,18 @@ TEST(DBTest, PrefixScan) {
|
||||
env_->count_random_reads_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.no_block_cache = true;
|
||||
options.filter_policy = NewBloomFilterPolicy(10);
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
options.whole_key_filtering = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_compactions = 2;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
table_options.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
// 11 RAND I/Os
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
@@ -7028,7 +7224,6 @@ TEST(DBTest, PrefixScan) {
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
||||
Close();
|
||||
delete options.filter_policy;
|
||||
}
|
||||
|
||||
TEST(DBTest, TailingIteratorSingle) {
|
||||
@@ -7359,7 +7554,7 @@ TEST(DBTest, SimpleWriteTimeoutTest) {
|
||||
ASSERT_OK(Put(Key(2), Key(2) + std::string(100000, 'v'), write_opt));
|
||||
// As the only two write buffers are full in this moment, the third
|
||||
// Put is expected to be timed-out.
|
||||
write_opt.timeout_hint_us = 300;
|
||||
write_opt.timeout_hint_us = 50;
|
||||
ASSERT_TRUE(
|
||||
Put(Key(3), Key(3) + std::string(100000, 'v'), write_opt).IsTimedOut());
|
||||
}
|
||||
@@ -7536,6 +7731,18 @@ TEST(DBTest, RateLimitingTest) {
|
||||
ASSERT_TRUE(ratio < 0.6);
|
||||
}
|
||||
|
||||
TEST(DBTest, TableOptionsSanitizeTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_EQ(db_->GetOptions().allow_mmap_reads, false);
|
||||
|
||||
options.table_factory.reset(new PlainTableFactory());
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
Destroy(&options);
|
||||
ASSERT_TRUE(TryReopen(&options).IsNotSupported());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -127,26 +127,6 @@ void InternalKeyComparator::FindShortSuccessor(std::string* key) const {
|
||||
}
|
||||
}
|
||||
|
||||
const char* InternalFilterPolicy::Name() const {
|
||||
return user_policy_->Name();
|
||||
}
|
||||
|
||||
void InternalFilterPolicy::CreateFilter(const Slice* keys, int n,
|
||||
std::string* dst) const {
|
||||
// We rely on the fact that the code in table.cc does not mind us
|
||||
// adjusting keys[].
|
||||
Slice* mkey = const_cast<Slice*>(keys);
|
||||
for (int i = 0; i < n; i++) {
|
||||
mkey[i] = ExtractUserKey(keys[i]);
|
||||
// TODO(sanjay): Suppress dups?
|
||||
}
|
||||
user_policy_->CreateFilter(keys, n, dst);
|
||||
}
|
||||
|
||||
bool InternalFilterPolicy::KeyMayMatch(const Slice& key, const Slice& f) const {
|
||||
return user_policy_->KeyMayMatch(ExtractUserKey(key), f);
|
||||
}
|
||||
|
||||
LookupKey::LookupKey(const Slice& user_key, SequenceNumber s) {
|
||||
size_t usize = user_key.size();
|
||||
size_t needed = usize + 13; // A conservative estimate
|
||||
|
||||
+8
-11
@@ -124,17 +124,6 @@ class InternalKeyComparator : public Comparator {
|
||||
int Compare(const ParsedInternalKey& a, const ParsedInternalKey& b) const;
|
||||
};
|
||||
|
||||
// Filter policy wrapper that converts from internal keys to user keys
|
||||
class InternalFilterPolicy : public FilterPolicy {
|
||||
private:
|
||||
const FilterPolicy* const user_policy_;
|
||||
public:
|
||||
explicit InternalFilterPolicy(const FilterPolicy* p) : user_policy_(p) { }
|
||||
virtual const char* Name() const;
|
||||
virtual void CreateFilter(const Slice* keys, int n, std::string* dst) const;
|
||||
virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const;
|
||||
};
|
||||
|
||||
// Modules in this directory should keep internal keys wrapped inside
|
||||
// the following class instead of plain strings so that we do not
|
||||
// incorrectly use string comparisons instead of an InternalKeyComparator.
|
||||
@@ -401,4 +390,12 @@ class InternalKeySliceTransform : public SliceTransform {
|
||||
const SliceTransform* const transform_;
|
||||
};
|
||||
|
||||
// Read record from a write batch piece from input.
|
||||
// tag, column_family, key, value and blob are return values. Callers own the
|
||||
// Slice they point to.
|
||||
// Tag is defined as ValueType.
|
||||
// input will be advanced to after the record.
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob);
|
||||
} // namespace rocksdb
|
||||
+63
-31
@@ -6,8 +6,9 @@
|
||||
// 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.
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include "db/filename.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
@@ -19,14 +20,16 @@
|
||||
namespace rocksdb {
|
||||
|
||||
// Given a path, flatten the path name by replacing all chars not in
|
||||
// {[0-9,a-z,A-Z,-,_,.]} with _. And append '\0' at the end.
|
||||
// {[0-9,a-z,A-Z,-,_,.]} with _. And append '_LOG\0' at the end.
|
||||
// Return the number of chars stored in dest not including the trailing '\0'.
|
||||
static int FlattenPath(const std::string& path, char* dest, int len) {
|
||||
int write_idx = 0;
|
||||
int i = 0;
|
||||
int src_len = path.size();
|
||||
static size_t GetInfoLogPrefix(const std::string& path, char* dest, int len) {
|
||||
const char suffix[] = "_LOG";
|
||||
|
||||
while (i < src_len && write_idx < len - 1) {
|
||||
size_t write_idx = 0;
|
||||
size_t i = 0;
|
||||
size_t src_len = path.size();
|
||||
|
||||
while (i < src_len && write_idx < len - sizeof(suffix)) {
|
||||
if ((path[i] >= 'a' && path[i] <= 'z') ||
|
||||
(path[i] >= '0' && path[i] <= '9') ||
|
||||
(path[i] >= 'A' && path[i] <= 'Z') ||
|
||||
@@ -40,8 +43,10 @@ static int FlattenPath(const std::string& path, char* dest, int len) {
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
dest[write_idx] = '\0';
|
||||
assert(sizeof(suffix) <= len - write_idx);
|
||||
// "\0" is automatically added by snprintf
|
||||
snprintf(dest + write_idx, len - write_idx, suffix);
|
||||
write_idx += sizeof(suffix) - 1;
|
||||
return write_idx;
|
||||
}
|
||||
|
||||
@@ -83,11 +88,17 @@ std::string TableFileName(const std::vector<DbPath>& db_paths, uint64_t number,
|
||||
return MakeTableFileName(path, number);
|
||||
}
|
||||
|
||||
std::string FormatFileNumber(uint64_t number, uint32_t path_id) {
|
||||
const size_t kFormatFileNumberBufSize = 38;
|
||||
|
||||
void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
|
||||
size_t out_buf_size) {
|
||||
if (path_id == 0) {
|
||||
return std::to_string(number);
|
||||
snprintf(out_buf, out_buf_size, "%" PRIu64, number);
|
||||
} else {
|
||||
return std::to_string(number) + "(path " + std::to_string(path_id) + ")";
|
||||
snprintf(out_buf, out_buf_size, "%" PRIu64
|
||||
"(path "
|
||||
"%" PRIu32 ")",
|
||||
number, path_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +122,26 @@ std::string TempFileName(const std::string& dbname, uint64_t number) {
|
||||
return MakeFileName(dbname, number, "dbtmp");
|
||||
}
|
||||
|
||||
InfoLogPrefix::InfoLogPrefix(bool has_log_dir,
|
||||
const std::string& db_absolute_path) {
|
||||
if (!has_log_dir) {
|
||||
const char kInfoLogPrefix[] = "LOG";
|
||||
// "\0" is automatically added to the end
|
||||
snprintf(buf, sizeof(buf), kInfoLogPrefix);
|
||||
prefix = Slice(buf, sizeof(kInfoLogPrefix) - 1);
|
||||
} else {
|
||||
size_t len = GetInfoLogPrefix(db_absolute_path, buf, sizeof(buf));
|
||||
prefix = Slice(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
std::string InfoLogFileName(const std::string& dbname,
|
||||
const std::string& db_path, const std::string& log_dir) {
|
||||
if (log_dir.empty())
|
||||
return dbname + "/LOG";
|
||||
|
||||
char flatten_db_path[256];
|
||||
FlattenPath(db_path, flatten_db_path, 256);
|
||||
return log_dir + "/" + flatten_db_path + "_LOG";
|
||||
InfoLogPrefix info_log_prefix(true, db_path);
|
||||
return log_dir + "/" + info_log_prefix.buf;
|
||||
}
|
||||
|
||||
// Return the name of the old info log file for "dbname".
|
||||
@@ -130,9 +153,8 @@ std::string OldInfoLogFileName(const std::string& dbname, uint64_t ts,
|
||||
if (log_dir.empty())
|
||||
return dbname + "/LOG.old." + buf;
|
||||
|
||||
char flatten_db_path[256];
|
||||
FlattenPath(db_path, flatten_db_path, 256);
|
||||
return log_dir + "/" + flatten_db_path + "_LOG.old." + buf;
|
||||
InfoLogPrefix info_log_prefix(true, db_path);
|
||||
return log_dir + "/" + info_log_prefix.buf + ".old." + buf;
|
||||
}
|
||||
|
||||
std::string MetaDatabaseName(const std::string& dbname, uint64_t number) {
|
||||
@@ -150,8 +172,8 @@ std::string IdentityFileName(const std::string& dbname) {
|
||||
// dbname/IDENTITY
|
||||
// dbname/CURRENT
|
||||
// dbname/LOCK
|
||||
// dbname/LOG
|
||||
// dbname/LOG.old.[0-9]+
|
||||
// dbname/<info_log_name_prefix>
|
||||
// dbname/<info_log_name_prefix>.old.[0-9]+
|
||||
// dbname/MANIFEST-[0-9]+
|
||||
// dbname/[0-9]+.(log|sst)
|
||||
// dbname/METADB-[0-9]+
|
||||
@@ -160,6 +182,12 @@ bool ParseFileName(const std::string& fname,
|
||||
uint64_t* number,
|
||||
FileType* type,
|
||||
WalFileType* log_type) {
|
||||
return ParseFileName(fname, number, "", type, log_type);
|
||||
}
|
||||
|
||||
bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
const Slice& info_log_name_prefix, FileType* type,
|
||||
WalFileType* log_type) {
|
||||
Slice rest(fname);
|
||||
if (fname.length() > 1 && fname[0] == '/') {
|
||||
rest.remove_prefix(1);
|
||||
@@ -173,18 +201,22 @@ bool ParseFileName(const std::string& fname,
|
||||
} else if (rest == "LOCK") {
|
||||
*number = 0;
|
||||
*type = kDBLockFile;
|
||||
} else if (rest == "LOG" || rest == "LOG.old") {
|
||||
*number = 0;
|
||||
*type = kInfoLogFile;
|
||||
} else if (rest.starts_with("LOG.old.")) {
|
||||
uint64_t ts_suffix;
|
||||
// sizeof also counts the trailing '\0'.
|
||||
rest.remove_prefix(sizeof("LOG.old.") - 1);
|
||||
if (!ConsumeDecimalNumber(&rest, &ts_suffix)) {
|
||||
return false;
|
||||
} else if (info_log_name_prefix.size() > 0 &&
|
||||
rest.starts_with(info_log_name_prefix)) {
|
||||
rest.remove_prefix(info_log_name_prefix.size());
|
||||
if (rest == "" || rest == ".old") {
|
||||
*number = 0;
|
||||
*type = kInfoLogFile;
|
||||
} else if (rest.starts_with(".old.")) {
|
||||
uint64_t ts_suffix;
|
||||
// sizeof also counts the trailing '\0'.
|
||||
rest.remove_prefix(sizeof(".old.") - 1);
|
||||
if (!ConsumeDecimalNumber(&rest, &ts_suffix)) {
|
||||
return false;
|
||||
}
|
||||
*number = ts_suffix;
|
||||
*type = kInfoLogFile;
|
||||
}
|
||||
*number = ts_suffix;
|
||||
*type = kInfoLogFile;
|
||||
} else if (rest.starts_with("MANIFEST-")) {
|
||||
rest.remove_prefix(strlen("MANIFEST-"));
|
||||
uint64_t num;
|
||||
|
||||
+21
-4
@@ -61,7 +61,11 @@ extern std::string MakeTableFileName(const std::string& name, uint64_t number);
|
||||
extern std::string TableFileName(const std::vector<DbPath>& db_paths,
|
||||
uint64_t number, uint32_t path_id);
|
||||
|
||||
extern std::string FormatFileNumber(uint64_t number, uint32_t path_id);
|
||||
// Sufficient buffer size for FormatFileNumber.
|
||||
extern const size_t kFormatFileNumberBufSize;
|
||||
|
||||
extern void FormatFileNumber(uint64_t number, uint32_t path_id, char* out_buf,
|
||||
size_t out_buf_size);
|
||||
|
||||
// Return the name of the descriptor file for the db named by
|
||||
// "dbname" and the specified incarnation number. The result will be
|
||||
@@ -82,6 +86,16 @@ extern std::string LockFileName(const std::string& dbname);
|
||||
// The result will be prefixed with "dbname".
|
||||
extern std::string TempFileName(const std::string& dbname, uint64_t number);
|
||||
|
||||
// A helper structure for prefix of info log names.
|
||||
struct InfoLogPrefix {
|
||||
char buf[260];
|
||||
Slice prefix;
|
||||
// Prefix with DB absolute path encoded
|
||||
explicit InfoLogPrefix(bool has_log_dir, const std::string& db_absolute_path);
|
||||
// Default Prefix
|
||||
explicit InfoLogPrefix();
|
||||
};
|
||||
|
||||
// Return the name of the info log file for "dbname".
|
||||
extern std::string InfoLogFileName(const std::string& dbname,
|
||||
const std::string& db_path="", const std::string& log_dir="");
|
||||
@@ -103,10 +117,13 @@ extern std::string IdentityFileName(const std::string& dbname);
|
||||
// If filename is a rocksdb file, store the type of the file in *type.
|
||||
// The number encoded in the filename is stored in *number. If the
|
||||
// filename was successfully parsed, returns true. Else return false.
|
||||
extern bool ParseFileName(const std::string& filename,
|
||||
uint64_t* number,
|
||||
FileType* type,
|
||||
// info_log_name_prefix is the path of info logs.
|
||||
extern bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
const Slice& info_log_name_prefix, FileType* type,
|
||||
WalFileType* log_type = nullptr);
|
||||
// Same as previous function, but skip info log files.
|
||||
extern bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
FileType* type, WalFileType* log_type = nullptr);
|
||||
|
||||
// Make the CURRENT file point to the descriptor file with the
|
||||
// specified number.
|
||||
|
||||
+54
-18
@@ -23,30 +23,50 @@ TEST(FileNameTest, Parse) {
|
||||
FileType type;
|
||||
uint64_t number;
|
||||
|
||||
char kDefautInfoLogDir = 1;
|
||||
char kDifferentInfoLogDir = 2;
|
||||
char kNoCheckLogDir = 4;
|
||||
char kAllMode = kDefautInfoLogDir | kDifferentInfoLogDir | kNoCheckLogDir;
|
||||
|
||||
// Successful parses
|
||||
static struct {
|
||||
const char* fname;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
char mode;
|
||||
} cases[] = {
|
||||
{ "100.log", 100, kLogFile },
|
||||
{ "0.log", 0, kLogFile },
|
||||
{ "0.sst", 0, kTableFile },
|
||||
{ "CURRENT", 0, kCurrentFile },
|
||||
{ "LOCK", 0, kDBLockFile },
|
||||
{ "MANIFEST-2", 2, kDescriptorFile },
|
||||
{ "MANIFEST-7", 7, kDescriptorFile },
|
||||
{ "METADB-2", 2, kMetaDatabase },
|
||||
{ "METADB-7", 7, kMetaDatabase },
|
||||
{ "LOG", 0, kInfoLogFile },
|
||||
{ "LOG.old", 0, kInfoLogFile },
|
||||
{ "18446744073709551615.log", 18446744073709551615ull, kLogFile },
|
||||
};
|
||||
for (unsigned int i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
std::string f = cases[i].fname;
|
||||
ASSERT_TRUE(ParseFileName(f, &number, &type)) << f;
|
||||
ASSERT_EQ(cases[i].type, type) << f;
|
||||
ASSERT_EQ(cases[i].number, number) << f;
|
||||
{"100.log", 100, kLogFile, kAllMode},
|
||||
{"0.log", 0, kLogFile, kAllMode},
|
||||
{"0.sst", 0, kTableFile, kAllMode},
|
||||
{"CURRENT", 0, kCurrentFile, kAllMode},
|
||||
{"LOCK", 0, kDBLockFile, kAllMode},
|
||||
{"MANIFEST-2", 2, kDescriptorFile, kAllMode},
|
||||
{"MANIFEST-7", 7, kDescriptorFile, kAllMode},
|
||||
{"METADB-2", 2, kMetaDatabase, kAllMode},
|
||||
{"METADB-7", 7, kMetaDatabase, kAllMode},
|
||||
{"LOG", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old", 0, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"LOG.old.6688", 6688, kInfoLogFile, kDefautInfoLogDir},
|
||||
{"rocksdb_dir_LOG", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old", 0, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"rocksdb_dir_LOG.old.6688", 6688, kInfoLogFile, kDifferentInfoLogDir},
|
||||
{"18446744073709551615.log", 18446744073709551615ull, kLogFile,
|
||||
kAllMode}, };
|
||||
for (char mode : {kDifferentInfoLogDir, kDefautInfoLogDir, kNoCheckLogDir}) {
|
||||
for (unsigned int i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
InfoLogPrefix info_log_prefix(mode != kDefautInfoLogDir, "/rocksdb/dir");
|
||||
if (cases[i].mode & mode) {
|
||||
std::string f = cases[i].fname;
|
||||
if (mode == kNoCheckLogDir) {
|
||||
ASSERT_TRUE(ParseFileName(f, &number, &type)) << f;
|
||||
} else {
|
||||
ASSERT_TRUE(ParseFileName(f, &number, info_log_prefix.prefix, &type))
|
||||
<< f;
|
||||
}
|
||||
ASSERT_EQ(cases[i].type, type) << f;
|
||||
ASSERT_EQ(cases[i].number, number) << f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Errors
|
||||
@@ -85,6 +105,22 @@ TEST(FileNameTest, Parse) {
|
||||
};
|
||||
}
|
||||
|
||||
TEST(FileNameTest, InfoLogFileName) {
|
||||
std::string dbname = ("/data/rocksdb");
|
||||
std::string db_absolute_path;
|
||||
Env::Default()->GetAbsolutePath(dbname, &db_absolute_path);
|
||||
|
||||
ASSERT_EQ("/data/rocksdb/LOG", InfoLogFileName(dbname, db_absolute_path, ""));
|
||||
ASSERT_EQ("/data/rocksdb/LOG.old.666",
|
||||
OldInfoLogFileName(dbname, 666u, db_absolute_path, ""));
|
||||
|
||||
ASSERT_EQ("/data/rocksdb_log/data_rocksdb_LOG",
|
||||
InfoLogFileName(dbname, db_absolute_path, "/data/rocksdb_log"));
|
||||
ASSERT_EQ(
|
||||
"/data/rocksdb_log/data_rocksdb_LOG.old.666",
|
||||
OldInfoLogFileName(dbname, 666u, db_absolute_path, "/data/rocksdb_log"));
|
||||
}
|
||||
|
||||
TEST(FileNameTest, Construction) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
|
||||
+70
-13
@@ -6,9 +6,10 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "db/forward_iterator.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/column_family.h"
|
||||
@@ -37,12 +38,16 @@ class LevelIterator : public Iterator {
|
||||
assert(file_index < files_.size());
|
||||
if (file_index != file_index_) {
|
||||
file_index_ = file_index;
|
||||
file_iter_.reset(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd, nullptr /* table_reader_ptr */, false));
|
||||
Reset();
|
||||
}
|
||||
valid_ = false;
|
||||
}
|
||||
void Reset() {
|
||||
assert(file_index_ < files_.size());
|
||||
file_iter_.reset(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd, nullptr /* table_reader_ptr */, false));
|
||||
}
|
||||
void SeekToLast() override {
|
||||
status_ = Status::NotSupported("LevelIterator::SeekToLast()");
|
||||
valid_ = false;
|
||||
@@ -63,12 +68,15 @@ class LevelIterator : public Iterator {
|
||||
assert(file_iter_ != nullptr);
|
||||
file_iter_->Seek(internal_key);
|
||||
valid_ = file_iter_->Valid();
|
||||
assert(valid_);
|
||||
}
|
||||
void Next() override {
|
||||
assert(valid_);
|
||||
file_iter_->Next();
|
||||
while (!file_iter_->Valid()) {
|
||||
for (;;) {
|
||||
if (file_iter_->status().IsIncomplete() || file_iter_->Valid()) {
|
||||
valid_ = !file_iter_->status().IsIncomplete();
|
||||
return;
|
||||
}
|
||||
if (file_index_ + 1 >= files_.size()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -76,7 +84,6 @@ class LevelIterator : public Iterator {
|
||||
SetFileIndex(file_index_ + 1);
|
||||
file_iter_->SeekToFirst();
|
||||
}
|
||||
valid_ = file_iter_->Valid();
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(valid_);
|
||||
@@ -160,6 +167,8 @@ void ForwardIterator::SeekToFirst() {
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RebuildIterators();
|
||||
} else if (status_.IsIncomplete()) {
|
||||
ResetIncompleteIterators();
|
||||
}
|
||||
SeekInternal(Slice(), true);
|
||||
}
|
||||
@@ -168,6 +177,8 @@ void ForwardIterator::Seek(const Slice& internal_key) {
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RebuildIterators();
|
||||
} else if (status_.IsIncomplete()) {
|
||||
ResetIncompleteIterators();
|
||||
}
|
||||
SeekInternal(internal_key, false);
|
||||
}
|
||||
@@ -211,7 +222,15 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
}
|
||||
l0_iters_[i]->Seek(internal_key);
|
||||
}
|
||||
if (l0_iters_[i]->Valid()) {
|
||||
|
||||
if (l0_iters_[i]->status().IsIncomplete()) {
|
||||
// if any of the immutable iterators is incomplete (no-io option was
|
||||
// used), we are unable to reliably find the smallest key
|
||||
assert(read_options_.read_tier == kBlockCacheTier);
|
||||
status_ = l0_iters_[i]->status();
|
||||
valid_ = false;
|
||||
return;
|
||||
} else if (l0_iters_[i]->Valid()) {
|
||||
immutable_min_heap_.push(l0_iters_[i]);
|
||||
}
|
||||
}
|
||||
@@ -280,7 +299,14 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
level_iters_[level - 1]->SetFileIndex(f_idx);
|
||||
seek_to_first ? level_iters_[level - 1]->SeekToFirst() :
|
||||
level_iters_[level - 1]->Seek(internal_key);
|
||||
if (level_iters_[level - 1]->Valid()) {
|
||||
|
||||
if (level_iters_[level - 1]->status().IsIncomplete()) {
|
||||
// see above
|
||||
assert(read_options_.read_tier == kBlockCacheTier);
|
||||
status_ = level_iters_[level - 1]->status();
|
||||
valid_ = false;
|
||||
return;
|
||||
} else if (level_iters_[level - 1]->Valid()) {
|
||||
immutable_min_heap_.push(level_iters_[level - 1]);
|
||||
}
|
||||
}
|
||||
@@ -304,7 +330,7 @@ void ForwardIterator::Next() {
|
||||
assert(valid_);
|
||||
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
sv_->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
std::string current_key = key().ToString();
|
||||
Slice old_key(current_key.data(), current_key.size());
|
||||
|
||||
@@ -320,9 +346,17 @@ void ForwardIterator::Next() {
|
||||
}
|
||||
|
||||
current_->Next();
|
||||
if (current_->Valid() && current_ != mutable_iter_) {
|
||||
immutable_min_heap_.push(current_);
|
||||
if (current_ != mutable_iter_) {
|
||||
if (current_->status().IsIncomplete()) {
|
||||
assert(read_options_.read_tier == kBlockCacheTier);
|
||||
status_ = current_->status();
|
||||
valid_ = false;
|
||||
return;
|
||||
} else if (current_->Valid()) {
|
||||
immutable_min_heap_.push(current_);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
@@ -389,6 +423,29 @@ void ForwardIterator::RebuildIterators() {
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void ForwardIterator::ResetIncompleteIterators() {
|
||||
const auto& l0_files = sv_->current->files_[0];
|
||||
for (uint32_t i = 0; i < l0_iters_.size(); ++i) {
|
||||
assert(i < l0_files.size());
|
||||
if (!l0_iters_[i]->status().IsIncomplete()) {
|
||||
continue;
|
||||
}
|
||||
delete l0_iters_[i];
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files[i]->fd);
|
||||
}
|
||||
|
||||
for (auto* level_iter : level_iters_) {
|
||||
if (level_iter && level_iter->status().IsIncomplete()) {
|
||||
level_iter->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void ForwardIterator::UpdateCurrent() {
|
||||
if (immutable_min_heap_.empty() && !mutable_iter_->Valid()) {
|
||||
current_ = nullptr;
|
||||
@@ -417,7 +474,7 @@ void ForwardIterator::UpdateCurrent() {
|
||||
}
|
||||
|
||||
bool ForwardIterator::NeedToSeekImmutable(const Slice& target) {
|
||||
if (!is_prev_set_) {
|
||||
if (!valid_ || !is_prev_set_) {
|
||||
return true;
|
||||
}
|
||||
Slice prev_key = prev_key_.GetKey();
|
||||
|
||||
@@ -73,6 +73,7 @@ class ForwardIterator : public Iterator {
|
||||
private:
|
||||
void Cleanup();
|
||||
void RebuildIterators();
|
||||
void ResetIncompleteIterators();
|
||||
void SeekInternal(const Slice& internal_key, bool seek_to_first);
|
||||
void UpdateCurrent();
|
||||
bool NeedToSeekImmutable(const Slice& internal_key);
|
||||
|
||||
+44
-16
@@ -11,6 +11,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -86,10 +87,17 @@ void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
|
||||
}
|
||||
|
||||
DBPropertyType GetPropertyType(const Slice& property) {
|
||||
DBPropertyType GetPropertyType(const Slice& property, bool* is_int_property,
|
||||
bool* need_out_of_mutex) {
|
||||
assert(is_int_property != nullptr);
|
||||
assert(need_out_of_mutex != nullptr);
|
||||
Slice in = property;
|
||||
Slice prefix("rocksdb.");
|
||||
if (!in.starts_with(prefix)) return kUnknown;
|
||||
*need_out_of_mutex = false;
|
||||
*is_int_property = false;
|
||||
if (!in.starts_with(prefix)) {
|
||||
return kUnknown;
|
||||
}
|
||||
in.remove_prefix(prefix.size());
|
||||
|
||||
if (in.starts_with("num-files-at-level")) {
|
||||
@@ -104,7 +112,10 @@ DBPropertyType GetPropertyType(const Slice& property) {
|
||||
return kDBStats;
|
||||
} else if (in == "sstables") {
|
||||
return kSsTables;
|
||||
} else if (in == "num-immutable-mem-table") {
|
||||
}
|
||||
|
||||
*is_int_property = true;
|
||||
if (in == "num-immutable-mem-table") {
|
||||
return kNumImmutableMemTable;
|
||||
} else if (in == "mem-table-flush-pending") {
|
||||
return kMemtableFlushPending;
|
||||
@@ -120,21 +131,34 @@ DBPropertyType GetPropertyType(const Slice& property) {
|
||||
return kNumEntriesInImmutableMemtable;
|
||||
} else if (in == "estimate-num-keys") {
|
||||
return kEstimatedNumKeys;
|
||||
} else if (in == "estimate-table-readers-mem") {
|
||||
*need_out_of_mutex = true;
|
||||
return kEstimatedUsageByTableReaders;
|
||||
} else if (in == "is-file-deletions-enabled") {
|
||||
return kIsFileDeletionEnabled;
|
||||
}
|
||||
return kUnknown;
|
||||
}
|
||||
|
||||
bool InternalStats::GetProperty(DBPropertyType property_type,
|
||||
const Slice& property, std::string* value) {
|
||||
if (property_type > kStartIntTypes) {
|
||||
uint64_t int_value;
|
||||
bool ret_value = GetIntProperty(property_type, property, &int_value);
|
||||
if (ret_value) {
|
||||
*value = std::to_string(int_value);
|
||||
}
|
||||
return ret_value;
|
||||
bool InternalStats::GetIntPropertyOutOfMutex(DBPropertyType property_type,
|
||||
Version* version,
|
||||
uint64_t* value) const {
|
||||
assert(value != nullptr);
|
||||
if (property_type != kEstimatedUsageByTableReaders) {
|
||||
return false;
|
||||
}
|
||||
if (version == nullptr) {
|
||||
*value = 0;
|
||||
} else {
|
||||
*value = version->GetMemoryUsageByTableReaders();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::GetStringProperty(DBPropertyType property_type,
|
||||
const Slice& property,
|
||||
std::string* value) {
|
||||
assert(value != nullptr);
|
||||
Version* current = cfd_->current();
|
||||
Slice in = property;
|
||||
|
||||
@@ -169,10 +193,10 @@ bool InternalStats::GetProperty(DBPropertyType property_type,
|
||||
return true;
|
||||
}
|
||||
case kStats: {
|
||||
if (!GetProperty(kCFStats, "rocksdb.cfstats", value)) {
|
||||
if (!GetStringProperty(kCFStats, "rocksdb.cfstats", value)) {
|
||||
return false;
|
||||
}
|
||||
if (!GetProperty(kDBStats, "rocksdb.dbstats", value)) {
|
||||
if (!GetStringProperty(kDBStats, "rocksdb.dbstats", value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -194,8 +218,7 @@ bool InternalStats::GetProperty(DBPropertyType property_type,
|
||||
}
|
||||
|
||||
bool InternalStats::GetIntProperty(DBPropertyType property_type,
|
||||
const Slice& property,
|
||||
uint64_t* value) const {
|
||||
uint64_t* value, DBImpl* db) const {
|
||||
Version* current = cfd_->current();
|
||||
|
||||
switch (property_type) {
|
||||
@@ -234,6 +257,11 @@ bool InternalStats::GetIntProperty(DBPropertyType property_type,
|
||||
cfd_->imm()->current()->GetTotalNumEntries() +
|
||||
current->GetEstimatedActiveKeys();
|
||||
return true;
|
||||
#ifndef ROCKSDB_LITE
|
||||
case kIsFileDeletionEnabled:
|
||||
*value = db->IsFileDeletionsEnabled();
|
||||
return true;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
+13
-5
@@ -41,9 +41,14 @@ enum DBPropertyType : uint32_t {
|
||||
kNumEntriesInImmutableMemtable, // Return sum of number of entries in all
|
||||
// the immutable mem tables.
|
||||
kEstimatedNumKeys, // Estimated total number of keys in the database.
|
||||
kEstimatedUsageByTableReaders, // Estimated memory by table readers.
|
||||
kIsFileDeletionEnabled, // Equals disable_delete_obsolete_files_,
|
||||
// 0 means file deletions enabled
|
||||
};
|
||||
|
||||
extern DBPropertyType GetPropertyType(const Slice& property);
|
||||
extern DBPropertyType GetPropertyType(const Slice& property,
|
||||
bool* is_int_property,
|
||||
bool* need_out_of_mutex);
|
||||
|
||||
class InternalStats {
|
||||
public:
|
||||
@@ -191,11 +196,14 @@ class InternalStats {
|
||||
|
||||
uint64_t BumpAndGetBackgroundErrorCount() { return ++bg_error_count_; }
|
||||
|
||||
bool GetProperty(DBPropertyType property_type, const Slice& property,
|
||||
std::string* value);
|
||||
bool GetStringProperty(DBPropertyType property_type, const Slice& property,
|
||||
std::string* value);
|
||||
|
||||
bool GetIntProperty(DBPropertyType property_type, const Slice& property,
|
||||
uint64_t* value) const;
|
||||
bool GetIntProperty(DBPropertyType property_type, uint64_t* value,
|
||||
DBImpl* db) const;
|
||||
|
||||
bool GetIntPropertyOutOfMutex(DBPropertyType property_type, Version* version,
|
||||
uint64_t* value) const;
|
||||
|
||||
private:
|
||||
void DumpDBStats(std::string* value);
|
||||
|
||||
@@ -42,6 +42,11 @@ void BM_LogAndApply(int iters, int num_base_files) {
|
||||
|
||||
Options options;
|
||||
EnvOptions sopt;
|
||||
// Notice we are using the default options not through SanitizeOptions().
|
||||
// We might want to initialize some options manually if needed.
|
||||
options.db_paths.emplace_back(dbname, 0);
|
||||
// The parameter of table cache is passed in as null, so any file I/O
|
||||
// operation is likely to fail.
|
||||
vset = new VersionSet(dbname, &options, sopt, nullptr);
|
||||
std::vector<ColumnFamilyDescriptor> dummy;
|
||||
dummy.push_back(ColumnFamilyDescriptor());
|
||||
|
||||
+22
-9
@@ -174,13 +174,13 @@ const char* EncodeKey(std::string* scratch, const Slice& target) {
|
||||
|
||||
class MemTableIterator: public Iterator {
|
||||
public:
|
||||
MemTableIterator(const MemTable& mem, const ReadOptions& options,
|
||||
bool enforce_total_order, Arena* arena)
|
||||
MemTableIterator(
|
||||
const MemTable& mem, const ReadOptions& options, Arena* arena)
|
||||
: bloom_(nullptr),
|
||||
prefix_extractor_(mem.prefix_extractor_),
|
||||
valid_(false),
|
||||
arena_mode_(arena != nullptr) {
|
||||
if (prefix_extractor_ != nullptr && !enforce_total_order) {
|
||||
if (prefix_extractor_ != nullptr && !options.total_order_seek) {
|
||||
bloom_ = mem.prefix_bloom_.get();
|
||||
iter_ = mem.table_->GetDynamicPrefixIterator(arena);
|
||||
} else {
|
||||
@@ -248,14 +248,13 @@ class MemTableIterator: public Iterator {
|
||||
void operator=(const MemTableIterator&);
|
||||
};
|
||||
|
||||
Iterator* MemTable::NewIterator(const ReadOptions& options,
|
||||
bool enforce_total_order, Arena* arena) {
|
||||
Iterator* MemTable::NewIterator(const ReadOptions& options, Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return new MemTableIterator(*this, options, enforce_total_order, nullptr);
|
||||
return new MemTableIterator(*this, options, nullptr);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
|
||||
return new (mem)
|
||||
MemTableIterator(*this, options, enforce_total_order, arena);
|
||||
MemTableIterator(*this, options, arena);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,6 +389,16 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
return false;
|
||||
}
|
||||
case kTypeMerge: {
|
||||
if (!merge_operator) {
|
||||
*(s->status) = Status::InvalidArgument(
|
||||
"merge_operator is not properly initialized.");
|
||||
// Normally we continue the loop (return true) when we see a merge
|
||||
// operand. But in case of an error, we should stop the loop
|
||||
// immediately and pretend we have found the value to stop further
|
||||
// seek. Otherwise, the later call will override this error status.
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
std::string merge_result; // temporary area for merge results later
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
*(s->merge_in_progress) = true;
|
||||
@@ -408,7 +417,12 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext& merge_context, const Options& options) {
|
||||
PERF_TIMER_AUTO(get_from_memtable_time);
|
||||
// The sequence number is updated synchronously in version_set.h
|
||||
if (first_seqno_ == 0) {
|
||||
// Avoiding recording stats for speed.
|
||||
return false;
|
||||
}
|
||||
PERF_TIMER_GUARD(get_from_memtable_time);
|
||||
|
||||
Slice user_key = key.user_key();
|
||||
bool found_final_value = false;
|
||||
@@ -438,7 +452,6 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
*s = Status::MergeInProgress("");
|
||||
}
|
||||
PERF_TIMER_STOP(get_from_memtable_time);
|
||||
PERF_COUNTER_ADD(get_from_memtable_count, 1);
|
||||
return found_final_value;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ class MemTable {
|
||||
// Calling ~Iterator of the iterator will destroy all the states but
|
||||
// those allocated in arena.
|
||||
Iterator* NewIterator(const ReadOptions& options,
|
||||
bool enforce_total_order = false,
|
||||
Arena* arena = nullptr);
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
|
||||
@@ -24,10 +24,12 @@ void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
bool at_bottom, Statistics* stats, int* steps) {
|
||||
// Get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
// Also maintain the list of merge operands seen.
|
||||
assert(HasOperator());
|
||||
keys_.clear();
|
||||
operands_.clear();
|
||||
keys_.push_front(iter->key().ToString());
|
||||
operands_.push_front(iter->value().ToString());
|
||||
assert(user_merge_operator_);
|
||||
|
||||
success_ = false; // Will become true if we hit Put/Delete or bottom
|
||||
|
||||
|
||||
+8
-5
@@ -78,13 +78,16 @@ class MergeHelper {
|
||||
// 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() {
|
||||
bool IsSuccess() const { return success_; }
|
||||
Slice key() const { assert(success_); return Slice(keys_.back()); }
|
||||
Slice value() const { assert(success_); return Slice(operands_.back()); }
|
||||
const std::deque<std::string>& keys() const {
|
||||
assert(!success_); return keys_;
|
||||
}
|
||||
const std::deque<std::string>& values() const {
|
||||
assert(!success_); return operands_;
|
||||
}
|
||||
bool HasOperator() const { return user_merge_operator_ != nullptr; }
|
||||
|
||||
private:
|
||||
const Comparator* user_comparator_;
|
||||
|
||||
+36
-1
@@ -212,7 +212,7 @@ class Counters {
|
||||
void assert_add(const string& key, uint64_t value) {
|
||||
int result = add(key, value);
|
||||
assert(result);
|
||||
if (result == 0) exit(1); // Disable unused variable warning.
|
||||
if (result == 0) exit(1); // Disable unused variable warning.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -460,6 +460,41 @@ void runTest(int argc, const string& dbname, const bool use_ttl = false) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cout << "Test merge-operator not set after reopen\n";
|
||||
{
|
||||
auto db = OpenDb(dbname);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
}
|
||||
|
||||
DB* reopen_db;
|
||||
ASSERT_OK(DB::Open(Options(), dbname, &reopen_db));
|
||||
std::string value;
|
||||
ASSERT_TRUE(!(reopen_db->Get(ReadOptions(), "test-key", &value).ok()));
|
||||
delete reopen_db;
|
||||
DestroyDB(dbname, Options());
|
||||
}
|
||||
|
||||
/* Temporary remove this test
|
||||
{
|
||||
cout << "Test merge-operator not set after reopen (recovery case)\n";
|
||||
{
|
||||
auto db = OpenDb(dbname);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
counters.add("test-key", 1);
|
||||
}
|
||||
|
||||
DB* reopen_db;
|
||||
ASSERT_TRUE(DB::Open(Options(), dbname, &reopen_db).IsInvalidArgument());
|
||||
}
|
||||
*/
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -347,11 +347,20 @@ TEST(PlainTableDBTest, Flush) {
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
uint64_t int_num;
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(
|
||||
"rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(
|
||||
"rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_GT(int_num, 0U);
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
|
||||
+7
-7
@@ -57,8 +57,7 @@ class Repairer {
|
||||
: dbname_(dbname),
|
||||
env_(options.env),
|
||||
icmp_(options.comparator),
|
||||
ipolicy_(options.filter_policy),
|
||||
options_(SanitizeOptions(dbname, &icmp_, &ipolicy_, options)),
|
||||
options_(SanitizeOptions(dbname, &icmp_, options)),
|
||||
raw_table_cache_(
|
||||
// TableCache can be small since we expect each table to be opened
|
||||
// once.
|
||||
@@ -109,7 +108,6 @@ class Repairer {
|
||||
std::string const dbname_;
|
||||
Env* const env_;
|
||||
InternalKeyComparator const icmp_;
|
||||
InternalFilterPolicy const ipolicy_;
|
||||
Options const options_;
|
||||
std::shared_ptr<Cache> raw_table_cache_;
|
||||
TableCache* table_cache_;
|
||||
@@ -239,7 +237,8 @@ class Repairer {
|
||||
FileMetaData meta;
|
||||
meta.fd = FileDescriptor(next_file_number_++, 0, 0);
|
||||
ReadOptions ro;
|
||||
Iterator* iter = mem->NewIterator(ro, true /* enforce_total_order */);
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = mem->NewIterator(ro);
|
||||
status = BuildTable(dbname_, env_, options_, storage_options_, table_cache_,
|
||||
iter, &meta, icmp_, 0, 0, kNoCompression);
|
||||
delete iter;
|
||||
@@ -265,9 +264,10 @@ class Repairer {
|
||||
if (!status.ok()) {
|
||||
std::string fname = TableFileName(
|
||||
options_.db_paths, t.meta.fd.GetNumber(), t.meta.fd.GetPathId());
|
||||
Log(options_.info_log, "Table #%s: ignoring %s",
|
||||
FormatFileNumber(t.meta.fd.GetNumber(), t.meta.fd.GetPathId())
|
||||
.c_str(),
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
FormatFileNumber(t.meta.fd.GetNumber(), t.meta.fd.GetPathId(),
|
||||
file_num_buf, sizeof(file_num_buf));
|
||||
Log(options_.info_log, "Table #%s: ignoring %s", file_num_buf,
|
||||
status.ToString().c_str());
|
||||
ArchiveFile(fname);
|
||||
} else {
|
||||
|
||||
@@ -92,6 +92,8 @@ public:
|
||||
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) override;
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() const override { return 0; }
|
||||
|
||||
void SetupForCompaction() override;
|
||||
|
||||
std::shared_ptr<const TableProperties> GetTableProperties() const override;
|
||||
@@ -553,6 +555,14 @@ public:
|
||||
const InternalKeyComparator& internal_key,
|
||||
WritableFile* file,
|
||||
CompressionType compression_type) const;
|
||||
|
||||
virtual Status SanitizeDBOptions(const DBOptions* db_opts) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual std::string GetPrintableTableOptions() const override {
|
||||
return std::string();
|
||||
}
|
||||
};
|
||||
|
||||
Status SimpleTableFactory::NewTableReader(
|
||||
|
||||
@@ -185,6 +185,29 @@ Status TableCache::GetTableProperties(
|
||||
return s;
|
||||
}
|
||||
|
||||
size_t TableCache::GetMemoryUsageByTableReader(
|
||||
const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd) {
|
||||
Status s;
|
||||
auto table_reader = fd.table_reader;
|
||||
// table already been pre-loaded?
|
||||
if (table_reader) {
|
||||
return table_reader->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
Cache::Handle* table_handle = nullptr;
|
||||
s = FindTable(toptions, internal_comparator, fd, &table_handle, true);
|
||||
if (!s.ok()) {
|
||||
return 0;
|
||||
}
|
||||
assert(table_handle);
|
||||
auto table = GetTableReaderFromHandle(table_handle);
|
||||
auto ret = table->ApproximateMemoryUsage();
|
||||
ReleaseHandle(table_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TableCache::Evict(Cache* cache, uint64_t file_number) {
|
||||
cache->Erase(GetSliceForFileNumber(&file_number));
|
||||
}
|
||||
|
||||
@@ -80,6 +80,13 @@ class TableCache {
|
||||
std::shared_ptr<const TableProperties>* properties,
|
||||
bool no_io = false);
|
||||
|
||||
// Return total memory usage of the table reader of the file.
|
||||
// 0 of table reader of the file is not loaded.
|
||||
size_t GetMemoryUsageByTableReader(
|
||||
const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd);
|
||||
|
||||
// Release the handle from a cache
|
||||
void ReleaseHandle(Cache::Handle* handle);
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@ void TestInternalKeyPropertiesCollector(
|
||||
// SanitizeOptions().
|
||||
options.info_log = std::make_shared<DumbLogger>();
|
||||
options = SanitizeOptions("db", // just a place holder
|
||||
&pikc, nullptr, // don't care filter policy
|
||||
&pikc,
|
||||
options);
|
||||
options.comparator = comparator;
|
||||
} else {
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "db/tailing_iter.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/merger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
TailingIterator::TailingIterator(Env* const env, DBImpl* db,
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd)
|
||||
: env_(env),
|
||||
db_(db),
|
||||
read_options_(read_options),
|
||||
cfd_(cfd),
|
||||
super_version_(nullptr),
|
||||
current_(nullptr),
|
||||
status_(Status::InvalidArgument("Seek() not called on this iterator")) {}
|
||||
|
||||
TailingIterator::~TailingIterator() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
bool TailingIterator::Valid() const {
|
||||
return current_ != nullptr;
|
||||
}
|
||||
|
||||
void TailingIterator::SeekToFirst() {
|
||||
if (!IsCurrentVersion()) {
|
||||
CreateIterators();
|
||||
}
|
||||
|
||||
mutable_->SeekToFirst();
|
||||
immutable_->SeekToFirst();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void TailingIterator::Seek(const Slice& target) {
|
||||
if (!IsCurrentVersion()) {
|
||||
CreateIterators();
|
||||
}
|
||||
|
||||
mutable_->Seek(target);
|
||||
|
||||
// We maintain the interval (prev_key_, immutable_->key()] such that there
|
||||
// are no records with keys within that range in immutable_ other than
|
||||
// immutable_->key(). Since immutable_ can't change in this version, we don't
|
||||
// need to do a seek if 'target' belongs to that interval (i.e. immutable_ is
|
||||
// already at the correct position)!
|
||||
//
|
||||
// If prefix seek is used and immutable_ is not valid, seek if target has a
|
||||
// different prefix than prev_key.
|
||||
//
|
||||
// prev_key_ is updated by Next(). SeekImmutable() sets prev_key_ to
|
||||
// 'target' -- in this case, prev_key_ is included in the interval, so
|
||||
// prev_inclusive_ has to be set.
|
||||
|
||||
const Comparator* cmp = cfd_->user_comparator();
|
||||
if (!is_prev_set_ || cmp->Compare(prev_key_, target) >= !is_prev_inclusive_ ||
|
||||
(immutable_->Valid() && cmp->Compare(target, immutable_->key()) > 0) ||
|
||||
(cfd_->options()->prefix_extractor != nullptr && !IsSamePrefix(target))) {
|
||||
SeekImmutable(target);
|
||||
}
|
||||
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void TailingIterator::Next() {
|
||||
assert(Valid());
|
||||
|
||||
if (!IsCurrentVersion()) {
|
||||
// save the current key, create new iterators and then seek
|
||||
std::string current_key = key().ToString();
|
||||
Slice key_slice(current_key.data(), current_key.size());
|
||||
|
||||
CreateIterators();
|
||||
Seek(key_slice);
|
||||
|
||||
if (!Valid() || key().compare(key_slice) != 0) {
|
||||
// record with current_key no longer exists
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (current_ == immutable_.get()) {
|
||||
// immutable iterator is advanced -- update prev_key_
|
||||
prev_key_ = key().ToString();
|
||||
is_prev_inclusive_ = false;
|
||||
is_prev_set_ = true;
|
||||
}
|
||||
|
||||
current_->Next();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
Slice TailingIterator::key() const {
|
||||
assert(Valid());
|
||||
return current_->key();
|
||||
}
|
||||
|
||||
Slice TailingIterator::value() const {
|
||||
assert(Valid());
|
||||
return current_->value();
|
||||
}
|
||||
|
||||
Status TailingIterator::status() const {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
} else if (!mutable_->status().ok()) {
|
||||
return mutable_->status();
|
||||
} else {
|
||||
return immutable_->status();
|
||||
}
|
||||
}
|
||||
|
||||
void TailingIterator::Prev() {
|
||||
status_ = Status::NotSupported("This iterator doesn't support Prev()");
|
||||
}
|
||||
|
||||
void TailingIterator::SeekToLast() {
|
||||
status_ = Status::NotSupported("This iterator doesn't support SeekToLast()");
|
||||
}
|
||||
|
||||
void TailingIterator::Cleanup() {
|
||||
// Release old super version if necessary
|
||||
mutable_.reset();
|
||||
immutable_.reset();
|
||||
if (super_version_ != nullptr && super_version_->Unref()) {
|
||||
DBImpl::DeletionState deletion_state;
|
||||
db_->mutex_.Lock();
|
||||
super_version_->Cleanup();
|
||||
db_->FindObsoleteFiles(deletion_state, false, true);
|
||||
db_->mutex_.Unlock();
|
||||
delete super_version_;
|
||||
if (deletion_state.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(deletion_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TailingIterator::CreateIterators() {
|
||||
Cleanup();
|
||||
super_version_= cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
|
||||
Iterator* mutable_iter = super_version_->mem->NewIterator(read_options_);
|
||||
// create a DBIter that only uses memtable content; see NewIterator()
|
||||
mutable_.reset(
|
||||
NewDBIterator(env_, *cfd_->options(), cfd_->user_comparator(),
|
||||
mutable_iter, kMaxSequenceNumber));
|
||||
|
||||
std::vector<Iterator*> list;
|
||||
super_version_->imm->AddIterators(read_options_, &list);
|
||||
super_version_->current->AddIterators(
|
||||
read_options_, *cfd_->soptions(), &list);
|
||||
Iterator* immutable_iter =
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &list[0], list.size());
|
||||
|
||||
// create a DBIter that only uses memtable content; see NewIterator()
|
||||
immutable_.reset(
|
||||
NewDBIterator(env_, *cfd_->options(), cfd_->user_comparator(),
|
||||
immutable_iter, kMaxSequenceNumber));
|
||||
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void TailingIterator::UpdateCurrent() {
|
||||
current_ = nullptr;
|
||||
|
||||
if (mutable_->Valid()) {
|
||||
current_ = mutable_.get();
|
||||
}
|
||||
const Comparator* cmp = cfd_->user_comparator();
|
||||
if (immutable_->Valid() &&
|
||||
(current_ == nullptr ||
|
||||
cmp->Compare(immutable_->key(), current_->key()) < 0)) {
|
||||
current_ = immutable_.get();
|
||||
}
|
||||
|
||||
if (!status_.ok()) {
|
||||
// reset status that was set by Prev() or SeekToLast()
|
||||
status_ = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
bool TailingIterator::IsCurrentVersion() const {
|
||||
return super_version_ != nullptr &&
|
||||
super_version_->version_number == cfd_->GetSuperVersionNumber();
|
||||
}
|
||||
|
||||
bool TailingIterator::IsSamePrefix(const Slice& target) const {
|
||||
const SliceTransform* extractor = cfd_->options()->prefix_extractor.get();
|
||||
|
||||
assert(extractor);
|
||||
assert(is_prev_set_);
|
||||
|
||||
return extractor->Transform(target)
|
||||
.compare(extractor->Transform(prev_key_)) == 0;
|
||||
}
|
||||
|
||||
void TailingIterator::SeekImmutable(const Slice& target) {
|
||||
prev_key_ = target.ToString();
|
||||
is_prev_inclusive_ = true;
|
||||
is_prev_set_ = true;
|
||||
|
||||
immutable_->Seek(target);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBImpl;
|
||||
class Env;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
|
||||
/**
|
||||
* TailingIterator is a special type of iterator that doesn't use an (implicit)
|
||||
* snapshot. In other words, it can be used to read data that was added to the
|
||||
* db after the iterator had been created.
|
||||
*
|
||||
* TailingIterator is optimized for sequential reading. It doesn't support
|
||||
* Prev() and SeekToLast() operations.
|
||||
*/
|
||||
class TailingIterator : public Iterator {
|
||||
public:
|
||||
TailingIterator(Env* const env, DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd);
|
||||
virtual ~TailingIterator();
|
||||
|
||||
virtual bool Valid() const override;
|
||||
virtual void SeekToFirst() override;
|
||||
virtual void SeekToLast() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void Next() override;
|
||||
virtual void Prev() override;
|
||||
virtual Slice key() const override;
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
|
||||
private:
|
||||
void Cleanup();
|
||||
|
||||
Env* const env_;
|
||||
DBImpl* const db_;
|
||||
const ReadOptions read_options_;
|
||||
ColumnFamilyData* const cfd_;
|
||||
SuperVersion* super_version_;
|
||||
|
||||
// TailingIterator merges the contents of the two iterators below (one using
|
||||
// mutable memtable contents only, other over SSTs and immutable memtables).
|
||||
// See DBIter::GetTailingIteratorPair().
|
||||
std::unique_ptr<Iterator> mutable_;
|
||||
std::unique_ptr<Iterator> immutable_;
|
||||
|
||||
// points to either mutable_ or immutable_
|
||||
Iterator* current_;
|
||||
|
||||
// key that precedes immutable iterator's current key
|
||||
std::string prev_key_;
|
||||
|
||||
// unless prev_set is true, prev_key/prev_head is not valid and shouldn't be
|
||||
// used; reset by createIterators()
|
||||
bool is_prev_set_;
|
||||
|
||||
// prev_key_ was set by SeekImmutable(), which means that the interval of
|
||||
// keys covered by immutable_ is [prev_key_, current], i.e. it includes the
|
||||
// left endpoint
|
||||
bool is_prev_inclusive_;
|
||||
|
||||
// internal iterator status
|
||||
Status status_;
|
||||
|
||||
// check if this iterator's version matches DB's version
|
||||
bool IsCurrentVersion() const;
|
||||
|
||||
// check if SeekImmutable() is needed due to target having a different prefix
|
||||
// than prev_key_ (used when in prefix seek mode)
|
||||
bool IsSamePrefix(const Slice& target) const;
|
||||
|
||||
// creates mutable_ and immutable_ iterators and updates version_number_
|
||||
void CreateIterators();
|
||||
|
||||
// set current_ to be one of the iterators with the smallest key
|
||||
void UpdateCurrent();
|
||||
|
||||
// seek on immutable_ and update prev_key
|
||||
void SeekImmutable(const Slice& target);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
+4
-1
@@ -81,6 +81,8 @@ struct FileMetaData {
|
||||
uint64_t num_deletions; // the number of deletion entries.
|
||||
uint64_t raw_key_size; // total uncompressed key size.
|
||||
uint64_t raw_value_size; // total uncompressed value size.
|
||||
bool init_stats_from_file; // true if the data-entry stats of this file
|
||||
// has initialized from file.
|
||||
|
||||
FileMetaData()
|
||||
: refs(0),
|
||||
@@ -90,7 +92,8 @@ struct FileMetaData {
|
||||
num_entries(0),
|
||||
num_deletions(0),
|
||||
raw_key_size(0),
|
||||
raw_value_size(0) {}
|
||||
raw_value_size(0),
|
||||
init_stats_from_file(false) {}
|
||||
};
|
||||
|
||||
// A compressed copy of file meta data that just contain
|
||||
|
||||
+35
-24
@@ -301,23 +301,6 @@ class FilePicker {
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
static uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->fd.GetFileSize();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static uint64_t TotalCompensatedFileSize(
|
||||
const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->compensated_file_size;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
Version::~Version() {
|
||||
assert(refs_ == 0);
|
||||
|
||||
@@ -593,6 +576,18 @@ Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
size_t Version::GetMemoryUsageByTableReaders() {
|
||||
size_t total_usage = 0;
|
||||
for (auto& file_level : file_levels_) {
|
||||
for (size_t i = 0; i < file_level.num_files; i++) {
|
||||
total_usage += cfd_->table_cache()->GetMemoryUsageByTableReader(
|
||||
vset_->storage_options_, cfd_->internal_comparator(),
|
||||
file_level.files[i].fd);
|
||||
}
|
||||
}
|
||||
return total_usage;
|
||||
}
|
||||
|
||||
uint64_t Version::GetEstimatedActiveKeys() {
|
||||
// Estimation will be not accurate when:
|
||||
// (1) there is merge keys
|
||||
@@ -654,7 +649,6 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
// Callback from TableCache::Get()
|
||||
namespace {
|
||||
enum SaverState {
|
||||
kNotFound,
|
||||
kFound,
|
||||
@@ -662,6 +656,8 @@ enum SaverState {
|
||||
kCorrupt,
|
||||
kMerge // saver contains the current merge result (the operands)
|
||||
};
|
||||
|
||||
namespace version_set {
|
||||
struct Saver {
|
||||
SaverState state;
|
||||
const Comparator* ucmp;
|
||||
@@ -674,7 +670,7 @@ struct Saver {
|
||||
Logger* logger;
|
||||
Statistics* statistics;
|
||||
};
|
||||
}
|
||||
} // namespace version_set
|
||||
|
||||
// Called from TableCache::Get and Table::Get when file/block in which
|
||||
// key may exist are not there in TableCache/BlockCache respectively. In this
|
||||
@@ -682,7 +678,7 @@ struct Saver {
|
||||
// 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);
|
||||
version_set::Saver* s = reinterpret_cast<version_set::Saver*>(arg);
|
||||
s->state = kFound;
|
||||
if (s->value_found != nullptr) {
|
||||
*(s->value_found) = false;
|
||||
@@ -691,7 +687,7 @@ static void MarkKeyMayExist(void* arg) {
|
||||
|
||||
static bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
|
||||
const Slice& v) {
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
version_set::Saver* s = reinterpret_cast<version_set::Saver*>(arg);
|
||||
MergeContext* merge_contex = s->merge_context;
|
||||
std::string merge_result; // temporary area for merge results later
|
||||
|
||||
@@ -805,7 +801,7 @@ void Version::Get(const ReadOptions& options,
|
||||
Slice user_key = k.user_key();
|
||||
|
||||
assert(status->ok() || status->IsMergeInProgress());
|
||||
Saver saver;
|
||||
version_set::Saver saver;
|
||||
saver.state = status->ok()? kNotFound : kMerge;
|
||||
saver.ucmp = user_comparator_;
|
||||
saver.user_key = user_key;
|
||||
@@ -845,6 +841,11 @@ void Version::Get(const ReadOptions& options,
|
||||
}
|
||||
|
||||
if (kMerge == saver.state) {
|
||||
if (!merge_operator_) {
|
||||
*status = Status::InvalidArgument(
|
||||
"merge_operator is not properly initialized.");
|
||||
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_->FullMerge(user_key, nullptr,
|
||||
@@ -878,12 +879,16 @@ void Version::PrepareApply(std::vector<uint64_t>& size_being_compacted) {
|
||||
}
|
||||
|
||||
bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) {
|
||||
if (file_meta->num_entries > 0) {
|
||||
if (file_meta->init_stats_from_file) {
|
||||
return false;
|
||||
}
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
Status s = GetTableProperties(&tp, file_meta);
|
||||
file_meta->init_stats_from_file = true;
|
||||
if (!s.ok()) {
|
||||
Log(vset_->options_->info_log,
|
||||
"Unable to load table properties for file %" PRIu64 " --- %s\n",
|
||||
file_meta->fd.GetNumber(), s.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (tp.get() == nullptr) return false;
|
||||
@@ -1392,7 +1397,7 @@ const char* Version::LevelFileSummary(FileSummaryStorage* scratch,
|
||||
for (const auto& f : files_[level]) {
|
||||
int sz = sizeof(scratch->buffer) - len;
|
||||
char sztxt[16];
|
||||
AppendHumanBytes(f->fd.GetFileSize(), sztxt, 16);
|
||||
AppendHumanBytes(f->fd.GetFileSize(), sztxt, sizeof(sztxt));
|
||||
int ret = snprintf(scratch->buffer + len, sz,
|
||||
"#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ",
|
||||
f->fd.GetNumber(), f->smallest_seqno, sztxt,
|
||||
@@ -1876,6 +1881,9 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
if (new_descriptor_log) {
|
||||
// create manifest file
|
||||
Log(options_->info_log,
|
||||
"Creating manifest %" PRIu64 "\n", pending_manifest_file_number_);
|
||||
unique_ptr<WritableFile> descriptor_file;
|
||||
s = env_->NewWritableFile(
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_),
|
||||
@@ -1997,6 +2005,9 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
column_family_data->GetName().c_str());
|
||||
delete v;
|
||||
if (new_descriptor_log) {
|
||||
Log(options_->info_log,
|
||||
"Deleting manifest %" PRIu64 " current manifest %" PRIu64 "\n",
|
||||
manifest_file_number_, pending_manifest_file_number_);
|
||||
descriptor_log_.reset();
|
||||
env_->DeleteFile(
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_));
|
||||
|
||||
@@ -234,6 +234,8 @@ class Version {
|
||||
|
||||
uint64_t GetEstimatedActiveKeys();
|
||||
|
||||
size_t GetMemoryUsageByTableReaders();
|
||||
|
||||
// used to sort files by size
|
||||
struct Fsize {
|
||||
int index;
|
||||
|
||||
+89
-73
@@ -27,6 +27,7 @@
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/snapshot.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -80,6 +81,58 @@ int WriteBatch::Count() const {
|
||||
return WriteBatchInternal::Count(this);
|
||||
}
|
||||
|
||||
Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob) {
|
||||
assert(key != nullptr && value != nullptr);
|
||||
*tag = (*input)[0];
|
||||
input->remove_prefix(1);
|
||||
*column_family = 0; // default
|
||||
switch (*tag) {
|
||||
case kTypeColumnFamilyValue:
|
||||
if (!GetVarint32(input, column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Put");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeValue:
|
||||
if (!GetLengthPrefixedSlice(input, key) ||
|
||||
!GetLengthPrefixedSlice(input, value)) {
|
||||
return Status::Corruption("bad WriteBatch Put");
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyDeletion:
|
||||
if (!GetVarint32(input, column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Delete");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeDeletion:
|
||||
if (!GetLengthPrefixedSlice(input, key)) {
|
||||
return Status::Corruption("bad WriteBatch Delete");
|
||||
}
|
||||
break;
|
||||
case kTypeColumnFamilyMerge:
|
||||
if (!GetVarint32(input, column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Merge");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeMerge:
|
||||
if (!GetLengthPrefixedSlice(input, key) ||
|
||||
!GetLengthPrefixedSlice(input, value)) {
|
||||
return Status::Corruption("bad WriteBatch Merge");
|
||||
}
|
||||
break;
|
||||
case kTypeLogData:
|
||||
assert(blob != nullptr);
|
||||
if (!GetLengthPrefixedSlice(input, blob)) {
|
||||
return Status::Corruption("bad WriteBatch Blob");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status WriteBatch::Iterate(Handler* handler) const {
|
||||
Slice input(rep_);
|
||||
if (input.size() < kHeader) {
|
||||
@@ -91,57 +144,33 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
int found = 0;
|
||||
Status s;
|
||||
while (s.ok() && !input.empty() && handler->Continue()) {
|
||||
char tag = input[0];
|
||||
input.remove_prefix(1);
|
||||
char tag = 0;
|
||||
uint32_t column_family = 0; // default
|
||||
|
||||
s = ReadRecordFromWriteBatch(&input, &tag, &column_family, &key, &value,
|
||||
&blob);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case kTypeColumnFamilyValue:
|
||||
if (!GetVarint32(&input, &column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Put");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeValue:
|
||||
if (GetLengthPrefixedSlice(&input, &key) &&
|
||||
GetLengthPrefixedSlice(&input, &value)) {
|
||||
s = handler->PutCF(column_family, key, value);
|
||||
found++;
|
||||
} else {
|
||||
return Status::Corruption("bad WriteBatch Put");
|
||||
}
|
||||
s = handler->PutCF(column_family, key, value);
|
||||
found++;
|
||||
break;
|
||||
case kTypeColumnFamilyDeletion:
|
||||
if (!GetVarint32(&input, &column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Delete");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeDeletion:
|
||||
if (GetLengthPrefixedSlice(&input, &key)) {
|
||||
s = handler->DeleteCF(column_family, key);
|
||||
found++;
|
||||
} else {
|
||||
return Status::Corruption("bad WriteBatch Delete");
|
||||
}
|
||||
s = handler->DeleteCF(column_family, key);
|
||||
found++;
|
||||
break;
|
||||
case kTypeColumnFamilyMerge:
|
||||
if (!GetVarint32(&input, &column_family)) {
|
||||
return Status::Corruption("bad WriteBatch Merge");
|
||||
}
|
||||
// intentional fallthrough
|
||||
case kTypeMerge:
|
||||
if (GetLengthPrefixedSlice(&input, &key) &&
|
||||
GetLengthPrefixedSlice(&input, &value)) {
|
||||
s = handler->MergeCF(column_family, key, value);
|
||||
found++;
|
||||
} else {
|
||||
return Status::Corruption("bad WriteBatch Merge");
|
||||
}
|
||||
s = handler->MergeCF(column_family, key, value);
|
||||
found++;
|
||||
break;
|
||||
case kTypeLogData:
|
||||
if (GetLengthPrefixedSlice(&input, &blob)) {
|
||||
handler->LogData(blob);
|
||||
} else {
|
||||
return Status::Corruption("bad WriteBatch Blob");
|
||||
}
|
||||
handler->LogData(blob);
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
@@ -186,17 +215,6 @@ void WriteBatchInternal::Put(WriteBatch* b, uint32_t column_family_id,
|
||||
PutLengthPrefixedSlice(&b->rep_, value);
|
||||
}
|
||||
|
||||
namespace {
|
||||
inline uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family) {
|
||||
uint32_t column_family_id = 0;
|
||||
if (column_family != nullptr) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
column_family_id = cfh->GetID();
|
||||
}
|
||||
return column_family_id;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void WriteBatch::Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) {
|
||||
WriteBatchInternal::Put(this, GetColumnFamilyID(column_family), key, value);
|
||||
@@ -281,17 +299,17 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
public:
|
||||
SequenceNumber sequence_;
|
||||
ColumnFamilyMemTables* cf_mems_;
|
||||
bool recovery_;
|
||||
bool ignore_missing_column_families_;
|
||||
uint64_t log_number_;
|
||||
DBImpl* db_;
|
||||
const bool dont_filter_deletes_;
|
||||
|
||||
MemTableInserter(SequenceNumber sequence, ColumnFamilyMemTables* cf_mems,
|
||||
bool recovery, uint64_t log_number, DB* db,
|
||||
const bool dont_filter_deletes)
|
||||
bool ignore_missing_column_families, uint64_t log_number,
|
||||
DB* db, const bool dont_filter_deletes)
|
||||
: sequence_(sequence),
|
||||
cf_mems_(cf_mems),
|
||||
recovery_(recovery),
|
||||
ignore_missing_column_families_(ignore_missing_column_families),
|
||||
log_number_(log_number),
|
||||
db_(reinterpret_cast<DBImpl*>(db)),
|
||||
dont_filter_deletes_(dont_filter_deletes) {
|
||||
@@ -303,12 +321,18 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
bool SeekToColumnFamily(uint32_t column_family_id, Status* s) {
|
||||
bool found = cf_mems_->Seek(column_family_id);
|
||||
if (recovery_ && (!found || log_number_ < cf_mems_->GetLogNumber())) {
|
||||
// if in recovery envoronment:
|
||||
// * If column family was not found, it might mean that the WAL write
|
||||
// batch references to the column family that was dropped after the
|
||||
// insert. We don't want to fail the whole write batch in that case -- we
|
||||
// just ignore the update.
|
||||
if (!found) {
|
||||
if (ignore_missing_column_families_) {
|
||||
*s = Status::OK();
|
||||
} else {
|
||||
*s = Status::InvalidArgument(
|
||||
"Invalid column family specified in write batch");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (log_number_ != 0 && log_number_ < cf_mems_->GetLogNumber()) {
|
||||
// This is true only in recovery environment (log_number_ is always 0 in
|
||||
// non-recovery, regular write code-path)
|
||||
// * If log_number_ < cf_mems_->GetLogNumber(), this means that column
|
||||
// family already contains updates from this log. We can't apply updates
|
||||
// twice because of update-in-place or merge workloads -- ignore the
|
||||
@@ -316,18 +340,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
*s = Status::OK();
|
||||
return false;
|
||||
}
|
||||
if (!found) {
|
||||
assert(!recovery_);
|
||||
// If the column family was not found in non-recovery enviornment
|
||||
// (client's write code-path), we have to fail the write and return
|
||||
// the failure status to the client.
|
||||
*s = Status::InvalidArgument(
|
||||
"Invalid column family specified in write batch");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Status PutCF(uint32_t column_family_id, const Slice& key,
|
||||
const Slice& value) {
|
||||
Status seek_status;
|
||||
@@ -485,10 +499,12 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
|
||||
Status WriteBatchInternal::InsertInto(const WriteBatch* b,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
bool recovery, uint64_t log_number,
|
||||
DB* db, const bool dont_filter_deletes) {
|
||||
bool ignore_missing_column_families,
|
||||
uint64_t log_number, DB* db,
|
||||
const bool dont_filter_deletes) {
|
||||
MemTableInserter inserter(WriteBatchInternal::Sequence(b), memtables,
|
||||
recovery, log_number, db, dont_filter_deletes);
|
||||
ignore_missing_column_families, log_number, db,
|
||||
dont_filter_deletes);
|
||||
return b->Iterate(&inserter);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,18 +106,18 @@ class WriteBatchInternal {
|
||||
// Inserts batch entries into memtable
|
||||
// If dont_filter_deletes is false AND options.filter_deletes is true,
|
||||
// then --> Drops deletes in batch if db->KeyMayExist returns false
|
||||
// If recovery == true, this means InsertInto is executed on a recovery
|
||||
// code-path. WriteBatch referencing a dropped column family can be
|
||||
// found on a recovery code-path and should be ignored (recovery should not
|
||||
// fail). Additionally, the memtable will be updated only if
|
||||
// If ignore_missing_column_families == true. WriteBatch referencing
|
||||
// non-existing column family should be ignored.
|
||||
// However, if ignore_missing_column_families == false, any WriteBatch
|
||||
// referencing non-existing column family will return a InvalidArgument()
|
||||
// failure.
|
||||
//
|
||||
// If log_number is non-zero, the memtable will be updated only if
|
||||
// memtables->GetLogNumber() >= log_number
|
||||
// However, if recovery == false, any WriteBatch referencing
|
||||
// non-existing column family will return a failure. Also, log_number is
|
||||
// ignored in that case
|
||||
static Status InsertInto(const WriteBatch* batch,
|
||||
ColumnFamilyMemTables* memtables,
|
||||
bool recovery = false, uint64_t log_number = 0,
|
||||
DB* db = nullptr,
|
||||
bool ignore_missing_column_families = false,
|
||||
uint64_t log_number = 0, DB* db = nullptr,
|
||||
const bool dont_filter_deletes = true);
|
||||
|
||||
static void Append(WriteBatch* dst, const WriteBatch* src);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
@@ -316,6 +317,88 @@ TEST(WriteBatchTest, ColumnFamiliesBatchTest) {
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
TEST(WriteBatchTest, ColumnFamiliesBatchWithIndexTest) {
|
||||
WriteBatchWithIndex batch(BytewiseComparator(), 20);
|
||||
ColumnFamilyHandleImplDummy zero(0), two(2), three(3), eight(8);
|
||||
batch.Put(&zero, Slice("foo"), Slice("bar"));
|
||||
batch.Put(&two, Slice("twofoo"), Slice("bar2"));
|
||||
batch.Put(&eight, Slice("eightfoo"), Slice("bar8"));
|
||||
batch.Delete(&eight, Slice("eightfoo"));
|
||||
batch.Merge(&three, Slice("threethree"), Slice("3three"));
|
||||
batch.Put(&zero, Slice("foo"), Slice("bar"));
|
||||
batch.Merge(Slice("omom"), Slice("nom"));
|
||||
|
||||
std::unique_ptr<WBWIIterator> iter;
|
||||
|
||||
iter.reset(batch.NewIterator(&eight));
|
||||
iter->Seek("eightfoo");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type);
|
||||
ASSERT_EQ("eightfoo", iter->Entry().key.ToString());
|
||||
ASSERT_EQ("bar8", iter->Entry().value.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kDeleteRecord, iter->Entry().type);
|
||||
ASSERT_EQ("eightfoo", iter->Entry().key.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter.reset(batch.NewIterator());
|
||||
iter->Seek("gggg");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kMergeRecord, iter->Entry().type);
|
||||
ASSERT_EQ("omom", iter->Entry().key.ToString());
|
||||
ASSERT_EQ("nom", iter->Entry().value.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter.reset(batch.NewIterator(&zero));
|
||||
iter->Seek("foo");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type);
|
||||
ASSERT_EQ("foo", iter->Entry().key.ToString());
|
||||
ASSERT_EQ("bar", iter->Entry().value.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kPutRecord, iter->Entry().type);
|
||||
ASSERT_EQ("foo", iter->Entry().key.ToString());
|
||||
ASSERT_EQ("bar", iter->Entry().value.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(WriteType::kMergeRecord, iter->Entry().type);
|
||||
ASSERT_EQ("omom", iter->Entry().key.ToString());
|
||||
ASSERT_EQ("nom", iter->Entry().value.ToString());
|
||||
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
TestHandler handler;
|
||||
batch.GetWriteBatch()->Iterate(&handler);
|
||||
ASSERT_EQ(
|
||||
"Put(foo, bar)"
|
||||
"PutCF(2, twofoo, bar2)"
|
||||
"PutCF(8, eightfoo, bar8)"
|
||||
"DeleteCF(8, eightfoo)"
|
||||
"MergeCF(3, threethree, 3three)"
|
||||
"Put(foo, bar)"
|
||||
"Merge(omom, nom)",
|
||||
handler.seen);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+72
-16
@@ -6,8 +6,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.
|
||||
|
||||
C bindings for leveldb. May be useful as a stable ABI that can be
|
||||
used by programs that keep leveldb in a shared library, or for
|
||||
C bindings for rocksdb. May be useful as a stable ABI that can be
|
||||
used by programs that keep rocksdb in a shared library, or for
|
||||
a JNI api.
|
||||
|
||||
Does not support:
|
||||
@@ -61,6 +61,10 @@ typedef struct rocksdb_compactionfiltercontext_t
|
||||
rocksdb_compactionfiltercontext_t;
|
||||
typedef struct rocksdb_compactionfilterfactory_t
|
||||
rocksdb_compactionfilterfactory_t;
|
||||
typedef struct rocksdb_compactionfilterv2_t
|
||||
rocksdb_compactionfilterv2_t;
|
||||
typedef struct rocksdb_compactionfilterfactoryv2_t
|
||||
rocksdb_compactionfilterfactoryv2_t;
|
||||
typedef struct rocksdb_comparator_t rocksdb_comparator_t;
|
||||
typedef struct rocksdb_env_t rocksdb_env_t;
|
||||
typedef struct rocksdb_fifo_compaction_options_t rocksdb_fifo_compaction_options_t;
|
||||
@@ -71,6 +75,8 @@ typedef struct rocksdb_iterator_t rocksdb_iterator_t;
|
||||
typedef struct rocksdb_logger_t rocksdb_logger_t;
|
||||
typedef struct rocksdb_mergeoperator_t rocksdb_mergeoperator_t;
|
||||
typedef struct rocksdb_options_t rocksdb_options_t;
|
||||
typedef struct rocksdb_block_based_table_options_t
|
||||
rocksdb_block_based_table_options_t;
|
||||
typedef struct rocksdb_randomfile_t rocksdb_randomfile_t;
|
||||
typedef struct rocksdb_readoptions_t rocksdb_readoptions_t;
|
||||
typedef struct rocksdb_seqfile_t rocksdb_seqfile_t;
|
||||
@@ -342,6 +348,34 @@ extern void rocksdb_writebatch_iterate(
|
||||
void (*deleted)(void*, const char* k, size_t klen));
|
||||
extern const char* rocksdb_writebatch_data(rocksdb_writebatch_t*, size_t *size);
|
||||
|
||||
/* Block based table options */
|
||||
|
||||
extern rocksdb_block_based_table_options_t*
|
||||
rocksdb_block_based_options_create();
|
||||
extern void rocksdb_block_based_options_destroy(
|
||||
rocksdb_block_based_table_options_t* options);
|
||||
extern void rocksdb_block_based_options_set_block_size(
|
||||
rocksdb_block_based_table_options_t* options, size_t block_size);
|
||||
extern void rocksdb_block_based_options_set_block_size_deviation(
|
||||
rocksdb_block_based_table_options_t* options, int block_size_deviation);
|
||||
extern void rocksdb_block_based_options_set_block_restart_interval(
|
||||
rocksdb_block_based_table_options_t* options, int block_restart_interval);
|
||||
extern void rocksdb_block_based_options_set_filter_policy(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_filterpolicy_t* filter_policy);
|
||||
extern void rocksdb_block_based_options_set_no_block_cache(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
unsigned char no_block_cache);
|
||||
extern void rocksdb_block_based_options_set_block_cache(
|
||||
rocksdb_block_based_table_options_t* options, rocksdb_cache_t* block_cache);
|
||||
extern void rocksdb_block_based_options_set_block_cache_compressed(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_cache_t* block_cache_compressed);
|
||||
extern void rocksdb_block_based_options_set_whole_key_filtering(
|
||||
rocksdb_block_based_table_options_t*, unsigned char);
|
||||
extern void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t *opt, rocksdb_block_based_table_options_t* table_options);
|
||||
|
||||
/* Options */
|
||||
|
||||
extern rocksdb_options_t* rocksdb_options_create();
|
||||
@@ -349,7 +383,7 @@ extern void rocksdb_options_destroy(rocksdb_options_t*);
|
||||
extern void rocksdb_options_increase_parallelism(
|
||||
rocksdb_options_t* opt, int total_threads);
|
||||
extern void rocksdb_options_optimize_for_point_lookup(
|
||||
rocksdb_options_t* opt);
|
||||
rocksdb_options_t* opt, uint64_t block_cache_size_mb);
|
||||
extern void rocksdb_options_optimize_level_style_compaction(
|
||||
rocksdb_options_t* opt, uint64_t memtable_memory_budget);
|
||||
extern void rocksdb_options_optimize_universal_style_compaction(
|
||||
@@ -359,18 +393,19 @@ extern void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_compactionfilter_t*);
|
||||
extern void rocksdb_options_set_compaction_filter_factory(
|
||||
rocksdb_options_t*, rocksdb_compactionfilterfactory_t*);
|
||||
extern void rocksdb_options_set_compaction_filter_factory_v2(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_compactionfilterfactoryv2_t*);
|
||||
extern void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_comparator_t*);
|
||||
extern void rocksdb_options_set_merge_operator(rocksdb_options_t*,
|
||||
rocksdb_mergeoperator_t*);
|
||||
extern void rocksdb_options_set_merge_operator(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_mergeoperator_t*);
|
||||
extern void rocksdb_options_set_compression_per_level(
|
||||
rocksdb_options_t* opt,
|
||||
int* level_values,
|
||||
size_t num_levels);
|
||||
extern void rocksdb_options_set_filter_policy(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_filterpolicy_t*);
|
||||
extern void rocksdb_options_set_create_if_missing(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern void rocksdb_options_set_create_missing_column_families(
|
||||
@@ -384,13 +419,8 @@ extern void rocksdb_options_set_info_log(rocksdb_options_t*, rocksdb_logger_t*);
|
||||
extern void rocksdb_options_set_info_log_level(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_write_buffer_size(rocksdb_options_t*, size_t);
|
||||
extern void rocksdb_options_set_max_open_files(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_cache(rocksdb_options_t*, rocksdb_cache_t*);
|
||||
extern void rocksdb_options_set_cache_compressed(rocksdb_options_t*, rocksdb_cache_t*);
|
||||
extern void rocksdb_options_set_block_size(rocksdb_options_t*, size_t);
|
||||
extern void rocksdb_options_set_block_restart_interval(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_compression_options(
|
||||
rocksdb_options_t*, int, int, int);
|
||||
extern void rocksdb_options_set_whole_key_filtering(rocksdb_options_t*, unsigned char);
|
||||
extern void rocksdb_options_set_prefix_extractor(
|
||||
rocksdb_options_t*, rocksdb_slicetransform_t*);
|
||||
extern void rocksdb_options_set_num_levels(rocksdb_options_t*, int);
|
||||
@@ -441,8 +471,6 @@ extern void rocksdb_options_set_arena_block_size(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern void rocksdb_options_set_use_fsync(
|
||||
rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_db_stats_log_interval(
|
||||
rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_db_log_dir(
|
||||
rocksdb_options_t*, const char*);
|
||||
extern void rocksdb_options_set_wal_dir(
|
||||
@@ -485,7 +513,6 @@ extern void rocksdb_options_set_max_sequential_skip_in_iterations(
|
||||
rocksdb_options_t*, uint64_t);
|
||||
extern void rocksdb_options_set_disable_data_sync(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_disable_auto_compactions(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_disable_seek_compaction(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_delete_obsolete_files_period_micros(
|
||||
rocksdb_options_t*, uint64_t);
|
||||
extern void rocksdb_options_set_source_compaction_factor(rocksdb_options_t*, int);
|
||||
@@ -571,6 +598,35 @@ extern rocksdb_compactionfilterfactory_t*
|
||||
extern void rocksdb_compactionfilterfactory_destroy(
|
||||
rocksdb_compactionfilterfactory_t*);
|
||||
|
||||
/* Compaction Filter V2 */
|
||||
|
||||
extern rocksdb_compactionfilterv2_t* rocksdb_compactionfilterv2_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
// num_keys specifies the number of array entries in every *list parameter.
|
||||
// New values added to the new_values_list should be malloc'd and will be
|
||||
// freed by the caller. Specify true in the to_delete_list to remove an
|
||||
// entry during compaction; false to keep it.
|
||||
void (*filter)(
|
||||
void*, int level, size_t num_keys,
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
const char* const* existing_values_list, const size_t* existing_values_list_sizes,
|
||||
char** new_values_list, size_t* new_values_list_sizes,
|
||||
unsigned char* to_delete_list),
|
||||
const char* (*name)(void*));
|
||||
extern void rocksdb_compactionfilterv2_destroy(rocksdb_compactionfilterv2_t*);
|
||||
|
||||
/* Compaction Filter Factory V2 */
|
||||
|
||||
extern rocksdb_compactionfilterfactoryv2_t* rocksdb_compactionfilterfactoryv2_create(
|
||||
void* state,
|
||||
rocksdb_slicetransform_t* prefix_extractor,
|
||||
void (*destructor)(void*),
|
||||
rocksdb_compactionfilterv2_t* (*create_compaction_filter_v2)(
|
||||
void*, const rocksdb_compactionfiltercontext_t* context),
|
||||
const char* (*name)(void*));
|
||||
extern void rocksdb_compactionfilterfactoryv2_destroy(rocksdb_compactionfilterfactoryv2_t*);
|
||||
|
||||
/* Comparator */
|
||||
|
||||
extern rocksdb_comparator_t* rocksdb_comparator_create(
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef STORAGE_ROCKSDB_INCLUDE_COMPACTION_FILTER_H_
|
||||
#define STORAGE_ROCKSDB_INCLUDE_COMPACTION_FILTER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ class Env {
|
||||
// Priority for scheduling job in thread pool
|
||||
enum Priority { LOW, HIGH, TOTAL };
|
||||
|
||||
// Priority for scheduling job in thread pool
|
||||
// Priority for requesting bytes in rate limiter scheduler
|
||||
enum IOPriority {
|
||||
IO_LOW = 0,
|
||||
IO_HIGH = 1,
|
||||
@@ -272,6 +272,9 @@ class Env {
|
||||
// default number: 1
|
||||
virtual void SetBackgroundThreads(int number, Priority pri = LOW) = 0;
|
||||
|
||||
// Lower IO priority for threads from the specified pool.
|
||||
virtual void LowerThreadPoolIOPriority(Priority pool = LOW) {}
|
||||
|
||||
// Converts seconds-since-Jan-01-1970 to a printable string
|
||||
virtual std::string TimeToString(uint64_t time) = 0;
|
||||
|
||||
@@ -779,6 +782,9 @@ class EnvWrapper : public Env {
|
||||
void SetBackgroundThreads(int num, Priority pri) {
|
||||
return target_->SetBackgroundThreads(num, pri);
|
||||
}
|
||||
void LowerThreadPoolIOPriority(Priority pool = LOW) override {
|
||||
target_->LowerThreadPoolIOPriority(pool);
|
||||
}
|
||||
std::string TimeToString(uint64_t time) {
|
||||
return target_->TimeToString(time);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -37,7 +38,8 @@ class FlushBlockPolicyFactory {
|
||||
// Callers must delete the result after any database that is using the
|
||||
// result has been closed.
|
||||
virtual FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
const Options& options, const BlockBuilder& data_block_builder) const = 0;
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const BlockBuilder& data_block_builder) const = 0;
|
||||
|
||||
virtual ~FlushBlockPolicyFactory() { }
|
||||
};
|
||||
@@ -51,7 +53,7 @@ class FlushBlockBySizePolicyFactory : public FlushBlockPolicyFactory {
|
||||
}
|
||||
|
||||
virtual FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
const Options& options,
|
||||
const BlockBasedTableOptions& table_options,
|
||||
const BlockBuilder& data_block_builder) const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ struct IOStatsContext {
|
||||
uint64_t bytes_read;
|
||||
};
|
||||
|
||||
#ifndef IOS_CROSS_COMPILE
|
||||
extern __thread IOStatsContext iostats_context;
|
||||
#endif // IOS_CROSS_COMPILE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+36
-81
@@ -97,7 +97,8 @@ struct ColumnFamilyOptions {
|
||||
|
||||
// Use this if you don't need to keep the data sorted, i.e. you'll never use
|
||||
// an iterator, only Put() and Get() API calls
|
||||
ColumnFamilyOptions* OptimizeForPointLookup();
|
||||
ColumnFamilyOptions* OptimizeForPointLookup(
|
||||
uint64_t block_cache_size_mb);
|
||||
|
||||
// Default values for some parameters in ColumnFamilyOptions are not
|
||||
// optimized for heavy workloads and big datasets, which means you might
|
||||
@@ -206,34 +207,6 @@ struct ColumnFamilyOptions {
|
||||
// individual write buffers. Default: 1
|
||||
int min_write_buffer_number_to_merge;
|
||||
|
||||
// Control over blocks (user data is stored in a set of blocks, and
|
||||
// a block is the unit of reading from disk).
|
||||
|
||||
// If non-NULL use the specified cache for blocks.
|
||||
// If NULL, rocksdb will automatically create and use an 8MB internal cache.
|
||||
// Default: nullptr
|
||||
std::shared_ptr<Cache> block_cache;
|
||||
|
||||
// If non-NULL use the specified cache for compressed blocks.
|
||||
// If NULL, rocksdb will not use a compressed block cache.
|
||||
// Default: nullptr
|
||||
std::shared_ptr<Cache> block_cache_compressed;
|
||||
|
||||
// Approximate size of user data packed per block. Note that the
|
||||
// block size specified here corresponds to uncompressed data. The
|
||||
// actual size of the unit read from disk may be smaller if
|
||||
// compression is enabled. This parameter can be changed dynamically.
|
||||
//
|
||||
// Default: 4K
|
||||
size_t block_size;
|
||||
|
||||
// Number of keys between restart points for delta encoding of keys.
|
||||
// This parameter can be changed dynamically. Most clients should
|
||||
// leave this parameter alone.
|
||||
//
|
||||
// Default: 16
|
||||
int block_restart_interval;
|
||||
|
||||
// Compress blocks using the specified compression algorithm. This
|
||||
// parameter can be changed dynamically.
|
||||
//
|
||||
@@ -267,13 +240,6 @@ struct ColumnFamilyOptions {
|
||||
// different options for compression algorithms
|
||||
CompressionOptions compression_opts;
|
||||
|
||||
// If non-nullptr, use the specified filter policy to reduce disk reads.
|
||||
// Many applications will benefit from passing the result of
|
||||
// NewBloomFilterPolicy() here.
|
||||
//
|
||||
// Default: nullptr
|
||||
const FilterPolicy* filter_policy;
|
||||
|
||||
// If non-nullptr, use the specified function to determine the
|
||||
// prefixes for keys. These prefixes will be placed in the filter.
|
||||
// Depending on the workload, this can reduce the number of read-IOP
|
||||
@@ -290,12 +256,6 @@ struct ColumnFamilyOptions {
|
||||
// Default: nullptr
|
||||
std::shared_ptr<const SliceTransform> prefix_extractor;
|
||||
|
||||
// If true, place whole keys in the filter (not just prefixes).
|
||||
// This must generally be true for gets to be efficient.
|
||||
//
|
||||
// Default: true
|
||||
bool whole_key_filtering;
|
||||
|
||||
// Number of levels for this database
|
||||
int num_levels;
|
||||
|
||||
@@ -375,18 +335,6 @@ struct ColumnFamilyOptions {
|
||||
// stop building a single file in a level->level+1 compaction.
|
||||
int max_grandparent_overlap_factor;
|
||||
|
||||
// We decided to remove seek compaction from RocksDB because:
|
||||
// 1) It makes more sense for spinning disk workloads, while RocksDB is
|
||||
// primarily designed for flash and memory,
|
||||
// 2) It added some complexity to the important code-paths,
|
||||
// 3) None of our internal customers were really using it.
|
||||
//
|
||||
// Since we removed seek compaction, this option is now obsolete.
|
||||
// We left it here for backwards compatiblity (otherwise it would break the
|
||||
// build), but we'll remove it at some point.
|
||||
// Default: true
|
||||
bool disable_seek_compaction;
|
||||
|
||||
// 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
|
||||
@@ -404,12 +352,6 @@ struct ColumnFamilyOptions {
|
||||
// Default: 1000
|
||||
unsigned int rate_limit_delay_max_milliseconds;
|
||||
|
||||
// Disable block cache. If this is set to true,
|
||||
// then no block cache should be used, and the block_cache should
|
||||
// point to a nullptr object.
|
||||
// Default: false
|
||||
bool no_block_cache;
|
||||
|
||||
// size of one block in arena memory allocation.
|
||||
// If <= 0, a proper value is automatically calculated (usually 1/10 of
|
||||
// writer_buffer_size).
|
||||
@@ -433,14 +375,6 @@ struct ColumnFamilyOptions {
|
||||
// Default: true
|
||||
bool purge_redundant_kvs_while_flush;
|
||||
|
||||
// This is used to close a block before it reaches the configured
|
||||
// 'block_size'. If the percentage of free space in the current block is less
|
||||
// than this specified number and adding a new record to the block will
|
||||
// exceed the configured block size, then this block will be closed and the
|
||||
// new record will be written to the next block.
|
||||
// Default is 10.
|
||||
int block_size_deviation;
|
||||
|
||||
// The compaction style. Default: kCompactionStyleLevel
|
||||
CompactionStyle compaction_style;
|
||||
|
||||
@@ -487,7 +421,9 @@ struct ColumnFamilyOptions {
|
||||
TablePropertiesCollectorFactories;
|
||||
TablePropertiesCollectorFactories table_properties_collector_factories;
|
||||
|
||||
// Allows thread-safe inplace updates.
|
||||
// Allows thread-safe inplace updates. If this is true, there is no way to
|
||||
// achieve point-in-time consistency using snapshot or iterator (assuming
|
||||
// concurrent updates).
|
||||
// If inplace_callback function is not set,
|
||||
// Put(key, new_value) will update inplace the existing_value iff
|
||||
// * key exists in current memtable
|
||||
@@ -634,6 +570,7 @@ struct DBOptions {
|
||||
|
||||
// Use to control write rate of flush and compaction. Flush has higher
|
||||
// priority than compaction. Rate limiting is disabled if nullptr.
|
||||
// If rate limiter is enabled, bytes_per_sync is set to 1MB by default.
|
||||
// Default: nullptr
|
||||
std::shared_ptr<RateLimiter> rate_limiter;
|
||||
|
||||
@@ -681,9 +618,6 @@ struct DBOptions {
|
||||
// Default: false
|
||||
bool use_fsync;
|
||||
|
||||
// This options is not used!!
|
||||
int db_stats_log_interval;
|
||||
|
||||
// A list of paths where SST files can be put into, with its target size.
|
||||
// Newer data is placed into paths specified earlier in the vector while
|
||||
// older data gradually moves to paths specified later in the vector.
|
||||
@@ -855,12 +789,6 @@ struct DBOptions {
|
||||
// Default: false
|
||||
bool use_adaptive_mutex;
|
||||
|
||||
// Allows OS to incrementally sync files to disk while they are being
|
||||
// written, asynchronously, in the background.
|
||||
// Issue one request for every bytes_per_sync written. 0 turns it off.
|
||||
// Default: 0
|
||||
uint64_t bytes_per_sync;
|
||||
|
||||
// Allow RocksDB to use thread local storage to optimize performance.
|
||||
// Default: true
|
||||
bool allow_thread_local;
|
||||
@@ -871,6 +799,16 @@ struct DBOptions {
|
||||
explicit DBOptions(const Options& options);
|
||||
|
||||
void Dump(Logger* log) const;
|
||||
|
||||
// Allows OS to incrementally sync files to disk while they are being
|
||||
// written, asynchronously, in the background.
|
||||
// Issue one request for every bytes_per_sync written. 0 turns it off.
|
||||
// Default: 0
|
||||
//
|
||||
// You may consider using rate_limiter to regulate write rate to device.
|
||||
// When rate limiter is enabled, it automatically enables bytes_per_sync
|
||||
// to 1MB.
|
||||
uint64_t bytes_per_sync;
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
@@ -965,18 +903,25 @@ struct ReadOptions {
|
||||
// Not supported in ROCKSDB_LITE mode!
|
||||
bool tailing;
|
||||
|
||||
// Enable a total order seek regardless of index format (e.g. hash index)
|
||||
// used in the table. Some table format (e.g. plain table) may not support
|
||||
// this option.
|
||||
bool total_order_seek;
|
||||
|
||||
ReadOptions()
|
||||
: verify_checksums(true),
|
||||
fill_cache(true),
|
||||
snapshot(nullptr),
|
||||
read_tier(kReadAllTier),
|
||||
tailing(false) {}
|
||||
tailing(false),
|
||||
total_order_seek(false) {}
|
||||
ReadOptions(bool cksum, bool cache)
|
||||
: verify_checksums(cksum),
|
||||
fill_cache(cache),
|
||||
snapshot(nullptr),
|
||||
read_tier(kReadAllTier),
|
||||
tailing(false) {}
|
||||
tailing(false),
|
||||
total_order_seek(false) {}
|
||||
};
|
||||
|
||||
// Options that control write operations
|
||||
@@ -1014,7 +959,17 @@ struct WriteOptions {
|
||||
// Default: 0
|
||||
uint64_t timeout_hint_us;
|
||||
|
||||
WriteOptions() : sync(false), disableWAL(false), timeout_hint_us(0) {}
|
||||
// If true and if user is trying to write to column families that don't exist
|
||||
// (they were dropped), ignore the write (don't return an error). If there
|
||||
// are multiple writes in a WriteBatch, other writes will succeed.
|
||||
// Default: false
|
||||
bool ignore_missing_column_families;
|
||||
|
||||
WriteOptions()
|
||||
: sync(false),
|
||||
disableWAL(false),
|
||||
timeout_hint_us(0),
|
||||
ignore_missing_column_families(false) {}
|
||||
};
|
||||
|
||||
// Options that control flush operations
|
||||
|
||||
@@ -219,6 +219,7 @@ enum Histograms : uint32_t {
|
||||
HARD_RATE_LIMIT_DELAY_COUNT,
|
||||
SOFT_RATE_LIMIT_DELAY_COUNT,
|
||||
NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
DB_SEEK,
|
||||
HISTOGRAM_ENUM_MAX,
|
||||
};
|
||||
|
||||
@@ -241,6 +242,7 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{ HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"},
|
||||
{ SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"},
|
||||
{ NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction" },
|
||||
{ DB_SEEK, "rocksdb.db.seek.micros" },
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
@@ -265,7 +267,10 @@ class Statistics {
|
||||
virtual void measureTime(uint32_t histogramType, uint64_t time) = 0;
|
||||
|
||||
// String representation of the statistic object.
|
||||
virtual std::string ToString() const = 0;
|
||||
virtual std::string ToString() const {
|
||||
// Do nothing by default
|
||||
return std::string("ToString(): not implemented");
|
||||
}
|
||||
|
||||
// Override this function to disable particular histogram collection
|
||||
virtual bool HistEnabledForType(uint32_t type) const {
|
||||
|
||||
@@ -96,7 +96,7 @@ class Status {
|
||||
// Returns true iff the status indicates Incomplete
|
||||
bool IsIncomplete() const { return code() == kIncomplete; }
|
||||
|
||||
// Returns true iff the status indicates Incomplete
|
||||
// Returns true iff the status indicates Shutdown In progress
|
||||
bool IsShutdownInProgress() const { return code() == kShutdownInProgress; }
|
||||
|
||||
bool IsTimedOut() const { return code() == kTimedOut; }
|
||||
|
||||
+125
-38
@@ -84,6 +84,46 @@ struct BlockBasedTableOptions {
|
||||
// protected with this checksum type. Old table files will still be readable,
|
||||
// even though they have different checksum type.
|
||||
ChecksumType checksum = kCRC32c;
|
||||
|
||||
// Disable block cache. If this is set to true,
|
||||
// then no block cache should be used, and the block_cache should
|
||||
// point to a nullptr object.
|
||||
bool no_block_cache = false;
|
||||
|
||||
// If non-NULL use the specified cache for blocks.
|
||||
// If NULL, rocksdb will automatically create and use an 8MB internal cache.
|
||||
std::shared_ptr<Cache> block_cache = nullptr;
|
||||
|
||||
// If non-NULL use the specified cache for compressed blocks.
|
||||
// If NULL, rocksdb will not use a compressed block cache.
|
||||
std::shared_ptr<Cache> block_cache_compressed = nullptr;
|
||||
|
||||
// Approximate size of user data packed per block. Note that the
|
||||
// block size specified here corresponds to uncompressed data. The
|
||||
// actual size of the unit read from disk may be smaller if
|
||||
// compression is enabled. This parameter can be changed dynamically.
|
||||
size_t block_size = 4 * 1024;
|
||||
|
||||
// This is used to close a block before it reaches the configured
|
||||
// 'block_size'. If the percentage of free space in the current block is less
|
||||
// than this specified number and adding a new record to the block will
|
||||
// exceed the configured block size, then this block will be closed and the
|
||||
// new record will be written to the next block.
|
||||
int block_size_deviation = 10;
|
||||
|
||||
// Number of keys between restart points for delta encoding of keys.
|
||||
// This parameter can be changed dynamically. Most clients should
|
||||
// leave this parameter alone.
|
||||
int block_restart_interval = 16;
|
||||
|
||||
// If non-nullptr, use the specified filter policy to reduce disk reads.
|
||||
// Many applications will benefit from passing the result of
|
||||
// NewBloomFilterPolicy() here.
|
||||
std::shared_ptr<const FilterPolicy> filter_policy = nullptr;
|
||||
|
||||
// If true, place whole keys in the filter (not just prefixes).
|
||||
// This must generally be true for gets to be efficient.
|
||||
bool whole_key_filtering = true;
|
||||
};
|
||||
|
||||
// Table Properties that are specific to block-based table properties.
|
||||
@@ -126,47 +166,49 @@ struct PlainTablePropertyNames {
|
||||
const uint32_t kPlainTableVariableLength = 0;
|
||||
|
||||
struct PlainTableOptions {
|
||||
// @user_key_len: plain table has optimization for fix-sized keys, which can be
|
||||
// specified via user_key_len. Alternatively, you can pass
|
||||
// `kPlainTableVariableLength` if your keys have variable
|
||||
// lengths.
|
||||
uint32_t user_key_len = kPlainTableVariableLength;
|
||||
// @user_key_len: plain table has optimization for fix-sized keys, which can
|
||||
// be specified via user_key_len. Alternatively, you can pass
|
||||
// `kPlainTableVariableLength` if your keys have variable
|
||||
// lengths.
|
||||
uint32_t user_key_len = kPlainTableVariableLength;
|
||||
|
||||
// @bloom_bits_per_key: the number of bits used for bloom filer per prefix. You
|
||||
// may disable it by passing a zero.
|
||||
int bloom_bits_per_key = 10;
|
||||
// @bloom_bits_per_key: the number of bits used for bloom filer per prefix.
|
||||
// You may disable it by passing a zero.
|
||||
int bloom_bits_per_key = 10;
|
||||
|
||||
// @hash_table_ratio: the desired utilization of the hash table used for prefix
|
||||
// hashing. hash_table_ratio = number of prefixes / #buckets
|
||||
// in the hash table
|
||||
double hash_table_ratio = 0.75;
|
||||
// @hash_table_ratio: the desired utilization of the hash table used for
|
||||
// prefix hashing.
|
||||
// hash_table_ratio = number of prefixes / #buckets in the
|
||||
// hash table
|
||||
double hash_table_ratio = 0.75;
|
||||
|
||||
// @index_sparseness: inside each prefix, need to build one index record for how
|
||||
// many keys for binary search inside each hash bucket.
|
||||
// For encoding type kPrefix, the value will be used when
|
||||
// writing to determine an interval to rewrite the full key.
|
||||
// It will also be used as a suggestion and satisfied when
|
||||
// possible.
|
||||
size_t index_sparseness = 16;
|
||||
// @index_sparseness: inside each prefix, need to build one index record for
|
||||
// how many keys for binary search inside each hash bucket.
|
||||
// For encoding type kPrefix, the value will be used when
|
||||
// writing to determine an interval to rewrite the full
|
||||
// key. It will also be used as a suggestion and satisfied
|
||||
// when possible.
|
||||
size_t index_sparseness = 16;
|
||||
|
||||
// @huge_page_tlb_size: if <=0, allocate hash indexes and blooms from malloc.
|
||||
// Otherwise from huge page TLB. The user needs to reserve
|
||||
// huge pages for it to be allocated, like:
|
||||
// sysctl -w vm.nr_hugepages=20
|
||||
// See linux doc Documentation/vm/hugetlbpage.txt
|
||||
size_t huge_page_tlb_size = 0;
|
||||
// @huge_page_tlb_size: if <=0, allocate hash indexes and blooms from malloc.
|
||||
// Otherwise from huge page TLB. The user needs to
|
||||
// reserve huge pages for it to be allocated, like:
|
||||
// sysctl -w vm.nr_hugepages=20
|
||||
// See linux doc Documentation/vm/hugetlbpage.txt
|
||||
size_t huge_page_tlb_size = 0;
|
||||
|
||||
// @encoding_type: how to encode the keys. See enum EncodingType above for
|
||||
// the choices. The value will determine how to encode keys
|
||||
// when writing to a new SST file. This value will be stored
|
||||
// inside the SST file which will be used when reading from the
|
||||
// file, which makes it possible for users to choose different
|
||||
// encoding type when reopening a DB. Files with different
|
||||
// encoding types can co-exist in the same DB and can be read.
|
||||
EncodingType encoding_type = kPlain;
|
||||
// @encoding_type: how to encode the keys. See enum EncodingType above for
|
||||
// the choices. The value will determine how to encode keys
|
||||
// when writing to a new SST file. This value will be stored
|
||||
// inside the SST file which will be used when reading from
|
||||
// the file, which makes it possible for users to choose
|
||||
// different encoding type when reopening a DB. Files with
|
||||
// different encoding types can co-exist in the same DB and
|
||||
// can be read.
|
||||
EncodingType encoding_type = kPlain;
|
||||
|
||||
// @full_scan_mode: mode for reading the whole file one record by one without
|
||||
// using the index.
|
||||
// @full_scan_mode: mode for reading the whole file one record by one without
|
||||
// using the index.
|
||||
bool full_scan_mode = false;
|
||||
|
||||
// @store_index_in_file: compute plain table index and bloom filter during
|
||||
@@ -185,13 +227,47 @@ extern TableFactory* NewPlainTableFactory(const PlainTableOptions& options =
|
||||
PlainTableOptions());
|
||||
|
||||
struct CuckooTablePropertyNames {
|
||||
// The key that is used to fill empty buckets.
|
||||
static const std::string kEmptyKey;
|
||||
// Fixed length of value.
|
||||
static const std::string kValueLength;
|
||||
static const std::string kNumHashTable;
|
||||
static const std::string kMaxNumBuckets;
|
||||
// Number of hash functions used in Cuckoo Hash.
|
||||
static const std::string kNumHashFunc;
|
||||
// It denotes the number of buckets in a Cuckoo Block. Given a key and a
|
||||
// particular hash function, a Cuckoo Block is a set of consecutive buckets,
|
||||
// where starting bucket id is given by the hash function on the key. In case
|
||||
// of a collision during inserting the key, the builder tries to insert the
|
||||
// key in other locations of the cuckoo block before using the next hash
|
||||
// function. This reduces cache miss during read operation in case of
|
||||
// collision.
|
||||
static const std::string kCuckooBlockSize;
|
||||
// Size of the hash table. Use this number to compute the modulo of hash
|
||||
// function. The actual number of buckets will be kMaxHashTableSize +
|
||||
// kCuckooBlockSize - 1. The last kCuckooBlockSize-1 buckets are used to
|
||||
// accommodate the Cuckoo Block from end of hash table, due to cache friendly
|
||||
// implementation.
|
||||
static const std::string kHashTableSize;
|
||||
// Denotes if the key sorted in the file is Internal Key (if false)
|
||||
// or User Key only (if true).
|
||||
static const std::string kIsLastLevel;
|
||||
};
|
||||
|
||||
// Cuckoo Table Factory for SST table format using Cache Friendly Cuckoo Hashing
|
||||
// @hash_table_ratio: Determines the utilization of hash tables. Smaller values
|
||||
// result in larger hash tables with fewer collisions.
|
||||
// @max_search_depth: A property used by builder to determine the depth to go to
|
||||
// to search for a path to displace elements in case of
|
||||
// collision. See Builder.MakeSpaceForKey method. Higher
|
||||
// values result in more efficient hash tables with fewer
|
||||
// lookups but take more time to build.
|
||||
// @cuckoo_block_size: In case of collision while inserting, the builder
|
||||
// attempts to insert in the next cuckoo_block_size
|
||||
// locations before skipping over to the next Cuckoo hash
|
||||
// function. This makes lookups more cache friendly in case
|
||||
// of collisions.
|
||||
extern TableFactory* NewCuckooTableFactory(double hash_table_ratio = 0.9,
|
||||
uint32_t max_search_depth = 100, uint32_t cuckoo_block_size = 5);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// A base class for table factories.
|
||||
@@ -250,6 +326,16 @@ class TableFactory {
|
||||
virtual TableBuilder* NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const = 0;
|
||||
|
||||
// Sanitizes the specified DB Options.
|
||||
//
|
||||
// If the function cannot find a way to sanitize the input DB Options,
|
||||
// a non-ok Status will be returned.
|
||||
virtual Status SanitizeDBOptions(const DBOptions* db_opts) const = 0;
|
||||
|
||||
// Return a string that contains printable format of table configurations.
|
||||
// RocksDB prints configurations at DB Open().
|
||||
virtual std::string GetPrintableTableOptions() const = 0;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -263,7 +349,8 @@ class TableFactory {
|
||||
extern TableFactory* NewAdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write = nullptr,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory = nullptr,
|
||||
std::shared_ptr<TableFactory> plain_table_factory = nullptr);
|
||||
std::shared_ptr<TableFactory> plain_table_factory = nullptr,
|
||||
std::shared_ptr<TableFactory> cuckoo_table_factory = nullptr);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
@@ -120,11 +120,14 @@ class FeatureSet {
|
||||
iterator end() const { return map_.end(); }
|
||||
|
||||
void Clear();
|
||||
size_t Size() const { return map_.size(); }
|
||||
|
||||
void Serialize(std::string* output) const;
|
||||
// REQUIRED: empty FeatureSet
|
||||
bool Deserialize(const Slice& input);
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
map map_;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// 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.
|
||||
//
|
||||
// A WriteBatchWithIndex with a binary searchable index built for all the keys
|
||||
// inserted.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class ColumnFamilyHandle;
|
||||
struct SliceParts;
|
||||
class Comparator;
|
||||
|
||||
enum WriteType { kPutRecord, kMergeRecord, kDeleteRecord, kLogDataRecord };
|
||||
|
||||
// an entry for Put, Merge or Delete entry for write batches. Used in
|
||||
// WBWIIterator.
|
||||
struct WriteEntry {
|
||||
WriteType type;
|
||||
Slice key;
|
||||
Slice value;
|
||||
};
|
||||
|
||||
// Iterator of one column family out of a WriteBatchWithIndex.
|
||||
class WBWIIterator {
|
||||
public:
|
||||
virtual ~WBWIIterator() {}
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
|
||||
virtual void Seek(const Slice& key) = 0;
|
||||
|
||||
virtual void Next() = 0;
|
||||
|
||||
virtual const WriteEntry& Entry() const = 0;
|
||||
|
||||
virtual Status status() const = 0;
|
||||
};
|
||||
|
||||
// A WriteBatchWithIndex with a binary searchable index built for all the keys
|
||||
// inserted.
|
||||
// In Put(), Merge() or Delete(), the same function of the wrapped will be
|
||||
// called. At the same time, indexes will be built.
|
||||
// By calling GetWriteBatch(), a user will get the WriteBatch for the data
|
||||
// they inserted, which can be used for DB::Write().
|
||||
// A user can call NewIterator() to create an iterator.
|
||||
class WriteBatchWithIndex {
|
||||
public:
|
||||
// index_comparator indicates the order when iterating data in the write
|
||||
// batch. Technically, it doesn't have to be the same as the one used in
|
||||
// the DB.
|
||||
// reserved_bytes: reserved bytes in underlying WriteBatch
|
||||
explicit WriteBatchWithIndex(const Comparator* index_comparator,
|
||||
size_t reserved_bytes = 0);
|
||||
virtual ~WriteBatchWithIndex();
|
||||
|
||||
WriteBatch* GetWriteBatch();
|
||||
|
||||
virtual void Put(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value);
|
||||
|
||||
virtual void Put(const Slice& key, const Slice& value);
|
||||
|
||||
virtual void Merge(ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value);
|
||||
|
||||
virtual void Merge(const Slice& key, const Slice& value);
|
||||
|
||||
virtual void PutLogData(const Slice& blob);
|
||||
|
||||
virtual void Delete(ColumnFamilyHandle* column_family, const Slice& key);
|
||||
virtual void Delete(const Slice& key);
|
||||
|
||||
virtual void Delete(ColumnFamilyHandle* column_family, const SliceParts& key);
|
||||
|
||||
virtual void Delete(const SliceParts& key);
|
||||
|
||||
// Create an iterator of a column family. User can call iterator.Seek() to
|
||||
// search to the next entry of or after a key. Keys will be iterated in the
|
||||
// order given by index_comparator. For multiple updates on the same key,
|
||||
// each update will be returned as a separate entry, in the order of update
|
||||
// time.
|
||||
virtual WBWIIterator* NewIterator(ColumnFamilyHandle* column_family);
|
||||
// Create an iterator of the default column family.
|
||||
virtual WBWIIterator* NewIterator();
|
||||
|
||||
private:
|
||||
struct Rep;
|
||||
Rep* rep;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Also update Makefile if you change these
|
||||
#define ROCKSDB_MAJOR 3
|
||||
#define ROCKSDB_MINOR 4
|
||||
#define ROCKSDB_MINOR 5
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -152,6 +152,7 @@ class WriteBatch {
|
||||
private:
|
||||
friend class WriteBatchInternal;
|
||||
|
||||
protected:
|
||||
std::string rep_; // See comment in write_batch.cc for the format of rep_
|
||||
|
||||
// Intentionally copyable
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB org.rocksdb.Options org.rocksdb.WriteBatch org.rocksdb.WriteBatchInternal org.rocksdb.WriteBatchTest org.rocksdb.WriteOptions org.rocksdb.BackupableDB org.rocksdb.BackupableDBOptions org.rocksdb.Statistics org.rocksdb.RocksIterator org.rocksdb.VectorMemTableConfig org.rocksdb.SkipListMemTableConfig org.rocksdb.HashLinkedListMemTableConfig org.rocksdb.HashSkipListMemTableConfig org.rocksdb.PlainTableConfig org.rocksdb.ReadOptions org.rocksdb.Filter org.rocksdb.BloomFilter org.rocksdb.RestoreOptions org.rocksdb.RestoreBackupableDB org.rocksdb.RocksEnv
|
||||
NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB org.rocksdb.Options org.rocksdb.WriteBatch org.rocksdb.WriteBatchInternal org.rocksdb.WriteBatchTest org.rocksdb.WriteOptions org.rocksdb.BackupableDB org.rocksdb.BackupableDBOptions org.rocksdb.Statistics org.rocksdb.RocksIterator org.rocksdb.VectorMemTableConfig org.rocksdb.SkipListMemTableConfig org.rocksdb.HashLinkedListMemTableConfig org.rocksdb.HashSkipListMemTableConfig org.rocksdb.PlainTableConfig org.rocksdb.BlockBasedTableConfig org.rocksdb.ReadOptions org.rocksdb.Filter org.rocksdb.BloomFilter org.rocksdb.RestoreOptions org.rocksdb.RestoreBackupableDB org.rocksdb.RocksEnv
|
||||
|
||||
NATIVE_INCLUDE = ./include
|
||||
ROCKSDB_JAR = rocksdbjni.jar
|
||||
|
||||
@@ -35,16 +35,11 @@ public class RocksDBSample {
|
||||
assert(db == null);
|
||||
}
|
||||
|
||||
Filter filter = new BloomFilter(10);
|
||||
options.setCreateIfMissing(true)
|
||||
.createStatistics()
|
||||
.setWriteBufferSize(8 * SizeUnit.KB)
|
||||
.setMaxWriteBufferNumber(3)
|
||||
.setDisableSeekCompaction(true)
|
||||
.setBlockSize(64 * SizeUnit.KB)
|
||||
.setMaxBackgroundCompactions(10)
|
||||
.setFilter(filter)
|
||||
.setCacheNumShardBits(6)
|
||||
.setCompressionType(CompressionType.SNAPPY_COMPRESSION)
|
||||
.setCompactionStyle(CompactionStyle.UNIVERSAL);
|
||||
Statistics stats = options.statisticsPtr();
|
||||
@@ -52,10 +47,7 @@ public class RocksDBSample {
|
||||
assert(options.createIfMissing() == true);
|
||||
assert(options.writeBufferSize() == 8 * SizeUnit.KB);
|
||||
assert(options.maxWriteBufferNumber() == 3);
|
||||
assert(options.disableSeekCompaction() == true);
|
||||
assert(options.blockSize() == 64 * SizeUnit.KB);
|
||||
assert(options.maxBackgroundCompactions() == 10);
|
||||
assert(options.cacheNumShardBits() == 6);
|
||||
assert(options.compressionType() == CompressionType.SNAPPY_COMPRESSION);
|
||||
assert(options.compactionStyle() == CompactionStyle.UNIVERSAL);
|
||||
|
||||
@@ -82,6 +74,15 @@ public class RocksDBSample {
|
||||
options.setTableFormatConfig(new PlainTableConfig());
|
||||
assert(options.tableFactoryName().equals("PlainTable"));
|
||||
|
||||
BlockBasedTableConfig table_options = new BlockBasedTableConfig();
|
||||
table_options.setBlockCacheSize(64 * SizeUnit.KB)
|
||||
.setFilterBitsPerKey(10)
|
||||
.setCacheNumShardBits(6);
|
||||
assert(table_options.blockCacheSize() == 64 * SizeUnit.KB);
|
||||
assert(table_options.cacheNumShardBits() == 6);
|
||||
options.setTableFormatConfig(table_options);
|
||||
assert(options.tableFactoryName().equals("BlockBasedTable"));
|
||||
|
||||
try {
|
||||
db = RocksDB.open(options, db_path_not_found);
|
||||
db.put("hello".getBytes(), "world".getBytes());
|
||||
@@ -254,6 +255,5 @@ public class RocksDBSample {
|
||||
// be sure to dispose c++ pointers
|
||||
options.dispose();
|
||||
readOptions.dispose();
|
||||
filter.dispose();
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,15 @@ public class BackupableDB extends RocksDB {
|
||||
public void createNewBackup(boolean flushBeforeBackup) {
|
||||
createNewBackup(nativeHandle_, flushBeforeBackup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes old backups, keeping latest numBackupsToKeep alive.
|
||||
*
|
||||
* @param numBackupsToKeep Number of latest backups to keep.
|
||||
*/
|
||||
public void purgeOldBackups(int numBackupsToKeep) {
|
||||
purgeOldBackups(nativeHandle_, numBackupsToKeep);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -77,4 +86,5 @@ public class BackupableDB extends RocksDB {
|
||||
|
||||
protected native void open(long rocksDBHandle, long backupDBOptionsHandle);
|
||||
protected native void createNewBackup(long handle, boolean flag);
|
||||
protected native void purgeOldBackups(long handle, int numBackupsToKeep);
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
package org.rocksdb;
|
||||
|
||||
/**
|
||||
* The config for plain table sst format.
|
||||
*
|
||||
* BlockBasedTable is a RocksDB's default SST file format.
|
||||
*/
|
||||
public class BlockBasedTableConfig extends TableFormatConfig {
|
||||
|
||||
public BlockBasedTableConfig() {
|
||||
noBlockCache_ = false;
|
||||
blockCacheSize_ = 8 * 1024 * 1024;
|
||||
blockSize_ = 4 * 1024;
|
||||
blockSizeDeviation_ =10;
|
||||
blockRestartInterval_ =16;
|
||||
wholeKeyFiltering_ = true;
|
||||
bitsPerKey_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable block cache. If this is set to true,
|
||||
* then no block cache should be used, and the block_cache should
|
||||
* point to a nullptr object.
|
||||
* Default: false
|
||||
*
|
||||
* @param noBlockCache if use block cache
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setNoBlockCache(boolean noBlockCache) {
|
||||
noBlockCache_ = noBlockCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if block cache is disabled
|
||||
*/
|
||||
public boolean noBlockCache() {
|
||||
return noBlockCache_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of cache in bytes that will be used by RocksDB.
|
||||
* If cacheSize is non-positive, then cache will not be used.
|
||||
* DEFAULT: 8M
|
||||
*
|
||||
* @param blockCacheSize block cache size in bytes
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setBlockCacheSize(long blockCacheSize) {
|
||||
blockCacheSize_ = blockCacheSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return block cache size in bytes
|
||||
*/
|
||||
public long blockCacheSize() {
|
||||
return blockCacheSize_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the number of shards for the block cache.
|
||||
* This is applied only if cacheSize is set to non-negative.
|
||||
*
|
||||
* @param numShardBits the number of shard bits. The resulting
|
||||
* number of shards would be 2 ^ numShardBits. Any negative
|
||||
* number means use default settings."
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
public BlockBasedTableConfig setCacheNumShardBits(int numShardBits) {
|
||||
numShardBits_ = numShardBits;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of shard bits used in the block cache.
|
||||
* The resulting number of shards would be 2 ^ (returned value).
|
||||
* Any negative number means use default settings.
|
||||
*
|
||||
* @return the number of shard bits used in the block cache.
|
||||
*/
|
||||
public int cacheNumShardBits() {
|
||||
return numShardBits_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Approximate size of user data packed per block. Note that the
|
||||
* block size specified here corresponds to uncompressed data. The
|
||||
* actual size of the unit read from disk may be smaller if
|
||||
* compression is enabled. This parameter can be changed dynamically.
|
||||
* Default: 4K
|
||||
*
|
||||
* @param blockSize block size in bytes
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setBlockSize(long blockSize) {
|
||||
blockSize_ = blockSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return block size in bytes
|
||||
*/
|
||||
public long blockSize() {
|
||||
return blockSize_;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to close a block before it reaches the configured
|
||||
* 'block_size'. If the percentage of free space in the current block is less
|
||||
* than this specified number and adding a new record to the block will
|
||||
* exceed the configured block size, then this block will be closed and the
|
||||
* new record will be written to the next block.
|
||||
* Default is 10.
|
||||
*
|
||||
* @param blockSizeDeviation the deviation to block size allowed
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setBlockSizeDeviation(int blockSizeDeviation) {
|
||||
blockSizeDeviation_ = blockSizeDeviation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the hash table ratio.
|
||||
*/
|
||||
public int blockSizeDeviation() {
|
||||
return blockSizeDeviation_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set block restart interval
|
||||
*
|
||||
* @param restartInterval block restart interval.
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setBlockRestartInterval(int restartInterval) {
|
||||
blockRestartInterval_ = restartInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return block restart interval
|
||||
*/
|
||||
public int blockRestartInterval() {
|
||||
return blockRestartInterval_;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, place whole keys in the filter (not just prefixes).
|
||||
* This must generally be true for gets to be efficient.
|
||||
* Default: true
|
||||
*
|
||||
* @param wholeKeyFiltering if enable whole key filtering
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setWholeKeyFiltering(boolean wholeKeyFiltering) {
|
||||
wholeKeyFiltering_ = wholeKeyFiltering;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if whole key filtering is enabled
|
||||
*/
|
||||
public boolean wholeKeyFiltering() {
|
||||
return wholeKeyFiltering_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified filter policy to reduce disk reads.
|
||||
*
|
||||
* Filter should not be disposed before options instances using this filter is
|
||||
* disposed. If dispose() function is not called, then filter object will be
|
||||
* GC'd automatically.
|
||||
*
|
||||
* Filter instance can be re-used in multiple options instances.
|
||||
*
|
||||
* @param Filter policy java instance.
|
||||
* @return the reference to the current config.
|
||||
*/
|
||||
public BlockBasedTableConfig setFilterBitsPerKey(int bitsPerKey) {
|
||||
bitsPerKey_ = bitsPerKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override protected long newTableFactoryHandle() {
|
||||
return newTableFactoryHandle(noBlockCache_, blockCacheSize_, numShardBits_,
|
||||
blockSize_, blockSizeDeviation_, blockRestartInterval_,
|
||||
wholeKeyFiltering_, bitsPerKey_);
|
||||
}
|
||||
|
||||
private native long newTableFactoryHandle(
|
||||
boolean noBlockCache, long blockCacheSize, int numShardbits,
|
||||
long blockSize, int blockSizeDeviation, int blockRestartInterval,
|
||||
boolean wholeKeyFiltering, int bitsPerKey);
|
||||
|
||||
private boolean noBlockCache_;
|
||||
private long blockCacheSize_;
|
||||
private int numShardBits_;
|
||||
private long shard;
|
||||
private long blockSize_;
|
||||
private int blockSizeDeviation_;
|
||||
private int blockRestartInterval_;
|
||||
private boolean wholeKeyFiltering_;
|
||||
private int bitsPerKey_;
|
||||
}
|
||||
+13
-237
@@ -136,135 +136,6 @@ public class Options extends RocksObject {
|
||||
return maxWriteBufferNumber(nativeHandle_);
|
||||
}
|
||||
|
||||
/*
|
||||
* Approximate size of user data packed per block. Note that the
|
||||
* block size specified here corresponds to uncompressed data. The
|
||||
* actual size of the unit read from disk may be smaller if
|
||||
* compression is enabled. This parameter can be changed dynamically.
|
||||
*
|
||||
* Default: 4K
|
||||
*
|
||||
* @param blockSize the size of each block in bytes.
|
||||
* @return the instance of the current Options.
|
||||
* @see RocksDB.open()
|
||||
*/
|
||||
public Options setBlockSize(long blockSize) {
|
||||
assert(isInitialized());
|
||||
setBlockSize(nativeHandle_, blockSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the size of a block in bytes.
|
||||
*
|
||||
* @return block size.
|
||||
* @see setBlockSize()
|
||||
*/
|
||||
public long blockSize() {
|
||||
assert(isInitialized());
|
||||
return blockSize(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified filter policy to reduce disk reads.
|
||||
*
|
||||
* Filter should not be disposed before options instances using this filter is
|
||||
* disposed. If dispose() function is not called, then filter object will be
|
||||
* GC'd automatically.
|
||||
*
|
||||
* Filter instance can be re-used in multiple options instances.
|
||||
*
|
||||
* @param Filter policy java instance.
|
||||
* @return the instance of the current Options.
|
||||
* @see RocksDB.open()
|
||||
*/
|
||||
public Options setFilter(Filter filter) {
|
||||
assert(isInitialized());
|
||||
setFilterHandle(nativeHandle_, filter.nativeHandle_);
|
||||
filter_ = filter;
|
||||
return this;
|
||||
}
|
||||
private native void setFilterHandle(long optHandle, long filterHandle);
|
||||
|
||||
/*
|
||||
* Disable compaction triggered by seek.
|
||||
* With bloomfilter and fast storage, a miss on one level
|
||||
* is very cheap if the file handle is cached in table cache
|
||||
* (which is true if max_open_files is large).
|
||||
* Default: true
|
||||
*
|
||||
* @param disableSeekCompaction a boolean value to specify whether
|
||||
* to disable seek compaction.
|
||||
* @return the instance of the current Options.
|
||||
* @see RocksDB.open()
|
||||
*/
|
||||
public Options setDisableSeekCompaction(boolean disableSeekCompaction) {
|
||||
assert(isInitialized());
|
||||
setDisableSeekCompaction(nativeHandle_, disableSeekCompaction);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if disable seek compaction is set to true.
|
||||
*
|
||||
* @return true if disable seek compaction is set to true.
|
||||
* @see setDisableSeekCompaction()
|
||||
*/
|
||||
public boolean disableSeekCompaction() {
|
||||
assert(isInitialized());
|
||||
return disableSeekCompaction(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of cache in bytes that will be used by RocksDB.
|
||||
* If cacheSize is non-positive, then cache will not be used.
|
||||
*
|
||||
* DEFAULT: 8M
|
||||
* @see setCacheNumShardBits()
|
||||
*/
|
||||
public Options setCacheSize(long cacheSize) {
|
||||
cacheSize_ = cacheSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount of cache in bytes that will be used by RocksDB.
|
||||
*
|
||||
* @see cacheNumShardBits()
|
||||
*/
|
||||
public long cacheSize() {
|
||||
return cacheSize_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the number of shards for the block cache.
|
||||
* This is applied only if cacheSize is set to non-negative.
|
||||
*
|
||||
* @param numShardBits the number of shard bits. The resulting
|
||||
* number of shards would be 2 ^ numShardBits. Any negative
|
||||
* number means use default settings."
|
||||
* @return the reference to the current option.
|
||||
*
|
||||
* @see setCacheSize()
|
||||
*/
|
||||
public Options setCacheNumShardBits(int numShardBits) {
|
||||
numShardBits_ = numShardBits;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of shard bits used in the block cache.
|
||||
* The resulting number of shards would be 2 ^ (returned value).
|
||||
* Any negative number means use default settings.
|
||||
*
|
||||
* @return the number of shard bits used in the block cache.
|
||||
*
|
||||
* @see cacheSize()
|
||||
*/
|
||||
public int cacheNumShardBits() {
|
||||
return numShardBits_;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, an error will be thrown during RocksDB.open() if the
|
||||
* database already exists.
|
||||
@@ -1344,26 +1215,26 @@ public class Options extends RocksObject {
|
||||
}
|
||||
private native void setBlockRestartInterval(
|
||||
long handle, int blockRestartInterval);
|
||||
|
||||
|
||||
/**
|
||||
* Compress blocks using the specified compression algorithm. This
|
||||
parameter can be changed dynamically.
|
||||
*
|
||||
*
|
||||
* Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression.
|
||||
*
|
||||
*
|
||||
* @return Compression type.
|
||||
*/
|
||||
*/
|
||||
public CompressionType compressionType() {
|
||||
return CompressionType.values()[compressionType(nativeHandle_)];
|
||||
}
|
||||
private native byte compressionType(long handle);
|
||||
|
||||
|
||||
/**
|
||||
* Compress blocks using the specified compression algorithm. This
|
||||
parameter can be changed dynamically.
|
||||
*
|
||||
*
|
||||
* Default: SNAPPY_COMPRESSION, which gives lightweight but fast compression.
|
||||
*
|
||||
*
|
||||
* @param compressionType Compression Type.
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
@@ -1372,22 +1243,22 @@ public class Options extends RocksObject {
|
||||
return this;
|
||||
}
|
||||
private native void setCompressionType(long handle, byte compressionType);
|
||||
|
||||
|
||||
/**
|
||||
* Compaction style for DB.
|
||||
*
|
||||
*
|
||||
* @return Compaction style.
|
||||
*/
|
||||
*/
|
||||
public CompactionStyle compactionStyle() {
|
||||
return CompactionStyle.values()[compactionStyle(nativeHandle_)];
|
||||
}
|
||||
private native byte compactionStyle(long handle);
|
||||
|
||||
|
||||
/**
|
||||
* Set compaction style for DB.
|
||||
*
|
||||
*
|
||||
* Default: LEVEL.
|
||||
*
|
||||
*
|
||||
* @param compactionStyle Compaction style.
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
@@ -1397,33 +1268,6 @@ public class Options extends RocksObject {
|
||||
}
|
||||
private native void setCompactionStyle(long handle, byte compactionStyle);
|
||||
|
||||
/**
|
||||
* If true, place whole keys in the filter (not just prefixes).
|
||||
* This must generally be true for gets to be efficient.
|
||||
* Default: true
|
||||
*
|
||||
* @return if true, then whole-key-filtering is on.
|
||||
*/
|
||||
public boolean wholeKeyFiltering() {
|
||||
return wholeKeyFiltering(nativeHandle_);
|
||||
}
|
||||
private native boolean wholeKeyFiltering(long handle);
|
||||
|
||||
/**
|
||||
* If true, place whole keys in the filter (not just prefixes).
|
||||
* This must generally be true for gets to be efficient.
|
||||
* Default: true
|
||||
*
|
||||
* @param wholeKeyFiltering if true, then whole-key-filtering is on.
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
public Options setWholeKeyFiltering(boolean wholeKeyFiltering) {
|
||||
setWholeKeyFiltering(nativeHandle_, wholeKeyFiltering);
|
||||
return this;
|
||||
}
|
||||
private native void setWholeKeyFiltering(
|
||||
long handle, boolean wholeKeyFiltering);
|
||||
|
||||
/**
|
||||
* If level-styled compaction is used, then this number determines
|
||||
* the total number of levels.
|
||||
@@ -1897,35 +1741,6 @@ public class Options extends RocksObject {
|
||||
private native void setRateLimitDelayMaxMilliseconds(
|
||||
long handle, int rateLimitDelayMaxMilliseconds);
|
||||
|
||||
/**
|
||||
* Disable block cache. If this is set to true,
|
||||
* then no block cache should be used, and the block_cache should
|
||||
* point to a nullptr object.
|
||||
* Default: false
|
||||
*
|
||||
* @return true if block cache is disabled.
|
||||
*/
|
||||
public boolean noBlockCache() {
|
||||
return noBlockCache(nativeHandle_);
|
||||
}
|
||||
private native boolean noBlockCache(long handle);
|
||||
|
||||
/**
|
||||
* Disable block cache. If this is set to true,
|
||||
* then no block cache should be used, and the block_cache should
|
||||
* point to a nullptr object.
|
||||
* Default: false
|
||||
*
|
||||
* @param noBlockCache true if block-cache is disabled.
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
public Options setNoBlockCache(boolean noBlockCache) {
|
||||
setNoBlockCache(nativeHandle_, noBlockCache);
|
||||
return this;
|
||||
}
|
||||
private native void setNoBlockCache(
|
||||
long handle, boolean noBlockCache);
|
||||
|
||||
/**
|
||||
* The size of one block in arena memory allocation.
|
||||
* If <= 0, a proper value is automatically calculated (usually 1/10 of
|
||||
@@ -2023,39 +1838,6 @@ public class Options extends RocksObject {
|
||||
private native void setPurgeRedundantKvsWhileFlush(
|
||||
long handle, boolean purgeRedundantKvsWhileFlush);
|
||||
|
||||
/**
|
||||
* This is used to close a block before it reaches the configured
|
||||
* 'block_size'. If the percentage of free space in the current block is less
|
||||
* than this specified number and adding a new record to the block will
|
||||
* exceed the configured block size, then this block will be closed and the
|
||||
* new record will be written to the next block.
|
||||
* Default is 10.
|
||||
*
|
||||
* @return the target block size
|
||||
*/
|
||||
public int blockSizeDeviation() {
|
||||
return blockSizeDeviation(nativeHandle_);
|
||||
}
|
||||
private native int blockSizeDeviation(long handle);
|
||||
|
||||
/**
|
||||
* This is used to close a block before it reaches the configured
|
||||
* 'block_size'. If the percentage of free space in the current block is less
|
||||
* than this specified number and adding a new record to the block will
|
||||
* exceed the configured block size, then this block will be closed and the
|
||||
* new record will be written to the next block.
|
||||
* Default is 10.
|
||||
*
|
||||
* @param blockSizeDeviation the target block size
|
||||
* @return the reference to the current option.
|
||||
*/
|
||||
public Options setBlockSizeDeviation(int blockSizeDeviation) {
|
||||
setBlockSizeDeviation(nativeHandle_, blockSizeDeviation);
|
||||
return this;
|
||||
}
|
||||
private native void setBlockSizeDeviation(
|
||||
long handle, int blockSizeDeviation);
|
||||
|
||||
/**
|
||||
* If true, compaction will verify checksum on every read that happens
|
||||
* as part of compaction
|
||||
@@ -2437,11 +2219,6 @@ public class Options extends RocksObject {
|
||||
private native void setMaxWriteBufferNumber(
|
||||
long handle, int maxWriteBufferNumber);
|
||||
private native int maxWriteBufferNumber(long handle);
|
||||
private native void setBlockSize(long handle, long blockSize);
|
||||
private native long blockSize(long handle);
|
||||
private native void setDisableSeekCompaction(
|
||||
long handle, boolean disableSeekCompaction);
|
||||
private native boolean disableSeekCompaction(long handle);
|
||||
private native void setMaxBackgroundCompactions(
|
||||
long handle, int maxBackgroundCompactions);
|
||||
private native int maxBackgroundCompactions(long handle);
|
||||
@@ -2459,6 +2236,5 @@ public class Options extends RocksObject {
|
||||
|
||||
long cacheSize_;
|
||||
int numShardBits_;
|
||||
Filter filter_;
|
||||
RocksEnv env_;
|
||||
}
|
||||
@@ -99,11 +99,11 @@ public class RocksDB extends RocksObject {
|
||||
/**
|
||||
* The factory constructor of RocksDB that opens a RocksDB instance given
|
||||
* the path to the database using the specified options and db path.
|
||||
*
|
||||
*
|
||||
* Options instance *should* not be disposed before all DBs using this options
|
||||
* instance have been closed. If user doesn't call options dispose explicitly,
|
||||
* then this options instance will be GC'd automatically.
|
||||
*
|
||||
*
|
||||
* Options instance can be re-used to open multiple DBs if DB statistics is
|
||||
* not used. If DB statistics are required, then its recommended to open DB
|
||||
* with new Options instance as underlying native statistics instance does not
|
||||
@@ -115,13 +115,12 @@ public class RocksDB extends RocksObject {
|
||||
// in RocksDB can prevent Java to GC during the life-time of
|
||||
// the currently-created RocksDB.
|
||||
RocksDB db = new RocksDB();
|
||||
db.open(options.nativeHandle_, options.cacheSize_,
|
||||
options.numShardBits_, path);
|
||||
|
||||
db.open(options.nativeHandle_, path);
|
||||
|
||||
db.storeOptionsInstance(options);
|
||||
return db;
|
||||
}
|
||||
|
||||
|
||||
private void storeOptionsInstance(Options options) {
|
||||
options_ = options;
|
||||
}
|
||||
@@ -334,8 +333,7 @@ public class RocksDB extends RocksObject {
|
||||
|
||||
// native methods
|
||||
protected native void open(
|
||||
long optionsHandle, long cacheSize, int numShardBits,
|
||||
String path) throws RocksDBException;
|
||||
long optionsHandle, String path) throws RocksDBException;
|
||||
protected native void put(
|
||||
long handle, byte[] key, int keyLen,
|
||||
byte[] value, int valueLen) throws RocksDBException;
|
||||
|
||||
@@ -446,7 +446,6 @@ public class DbBenchmark {
|
||||
randSeed_ = (Long) flags.get(Flag.seed);
|
||||
databaseDir_ = (String) flags.get(Flag.db);
|
||||
writesPerSeconds_ = (Integer) flags.get(Flag.writes_per_second);
|
||||
cacheSize_ = (Long) flags.get(Flag.cache_size);
|
||||
memtable_ = (String) flags.get(Flag.memtablerep);
|
||||
maxWriteBufferNumber_ = (Integer) flags.get(Flag.max_write_buffer_number);
|
||||
prefixSize_ = (Integer) flags.get(Flag.prefix_size);
|
||||
@@ -491,7 +490,6 @@ public class DbBenchmark {
|
||||
}
|
||||
|
||||
private void prepareOptions(Options options) {
|
||||
options.setCacheSize(cacheSize_);
|
||||
if (!useExisting_) {
|
||||
options.setCreateIfMissing(true);
|
||||
} else {
|
||||
@@ -521,6 +519,13 @@ public class DbBenchmark {
|
||||
if (usePlainTable_) {
|
||||
options.setTableFormatConfig(
|
||||
new PlainTableConfig().setKeySize(keySize_));
|
||||
} else {
|
||||
BlockBasedTableConfig table_options = new BlockBasedTableConfig();
|
||||
table_options.setBlockSize((Long)flags_.get(Flag.block_size))
|
||||
.setBlockCacheSize((Long)flags_.get(Flag.cache_size))
|
||||
.setFilterBitsPerKey((Integer)flags_.get(Flag.bloom_bits))
|
||||
.setCacheNumShardBits((Integer)flags_.get(Flag.cache_numshardbits));
|
||||
options.setTableFormatConfig(table_options);
|
||||
}
|
||||
options.setWriteBufferSize(
|
||||
(Long)flags_.get(Flag.write_buffer_size));
|
||||
@@ -532,12 +537,6 @@ public class DbBenchmark {
|
||||
(Integer)flags_.get(Flag.max_background_compactions));
|
||||
options.setMaxBackgroundFlushes(
|
||||
(Integer)flags_.get(Flag.max_background_flushes));
|
||||
options.setCacheSize(
|
||||
(Long)flags_.get(Flag.cache_size));
|
||||
options.setCacheNumShardBits(
|
||||
(Integer)flags_.get(Flag.cache_numshardbits));
|
||||
options.setBlockSize(
|
||||
(Long)flags_.get(Flag.block_size));
|
||||
options.setMaxOpenFiles(
|
||||
(Integer)flags_.get(Flag.open_files));
|
||||
options.setTableCacheRemoveScanCountLimit(
|
||||
@@ -548,8 +547,6 @@ public class DbBenchmark {
|
||||
(Boolean)flags_.get(Flag.use_fsync));
|
||||
options.setWalDir(
|
||||
(String)flags_.get(Flag.wal_dir));
|
||||
options.setDisableSeekCompaction(
|
||||
(Boolean)flags_.get(Flag.disable_seek_compaction));
|
||||
options.setDeleteObsoleteFilesPeriodMicros(
|
||||
(Integer)flags_.get(Flag.delete_obsolete_files_period_micros));
|
||||
options.setTableCacheNumshardbits(
|
||||
@@ -604,15 +601,6 @@ public class DbBenchmark {
|
||||
(Integer)flags_.get(Flag.max_successive_merges));
|
||||
options.setWalTtlSeconds((Long)flags_.get(Flag.wal_ttl_seconds));
|
||||
options.setWalSizeLimitMB((Long)flags_.get(Flag.wal_size_limit_MB));
|
||||
int bloomBits = (Integer)flags_.get(Flag.bloom_bits);
|
||||
if (bloomBits > 0) {
|
||||
// Internally, options will keep a reference to this BloomFilter.
|
||||
// This will disallow Java to GC this BloomFilter. In addition,
|
||||
// options.dispose() will release the c++ object of this BloomFilter.
|
||||
// As a result, the caller should not directly call
|
||||
// BloomFilter.dispose().
|
||||
options.setFilter(new BloomFilter(bloomBits));
|
||||
}
|
||||
/* TODO(yhchiang): enable the following parameters
|
||||
options.setCompressionType((String)flags_.get(Flag.compression_type));
|
||||
options.setCompressionLevel((Integer)flags_.get(Flag.compression_level));
|
||||
@@ -1160,7 +1148,7 @@ public class DbBenchmark {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
},
|
||||
block_size(defaultOptions_.blockSize(),
|
||||
block_size(defaultBlockBasedTableOptions_.blockSize(),
|
||||
"Number of bytes in a block.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Long.parseLong(value);
|
||||
@@ -1312,12 +1300,6 @@ public class DbBenchmark {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
},
|
||||
disable_seek_compaction(false,"Option to disable compaction\n" +
|
||||
"\ttriggered by read.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
delete_obsolete_files_period_micros(0,"Option to delete\n" +
|
||||
"\tobsolete files periodically. 0 means that obsolete files are\n" +
|
||||
"\tdeleted after every compaction run.") {
|
||||
@@ -1597,7 +1579,6 @@ public class DbBenchmark {
|
||||
final int threadNum_;
|
||||
final int writesPerSeconds_;
|
||||
final long randSeed_;
|
||||
final long cacheSize_;
|
||||
final boolean useExisting_;
|
||||
final String databaseDir_;
|
||||
double compressionRatio_;
|
||||
@@ -1620,6 +1601,8 @@ public class DbBenchmark {
|
||||
// as the scope of a static member equals to the scope of the problem,
|
||||
// we let its c++ pointer to be disposed in its finalizer.
|
||||
static Options defaultOptions_ = new Options();
|
||||
static BlockBasedTableConfig defaultBlockBasedTableOptions_ =
|
||||
new BlockBasedTableConfig();
|
||||
String compressionType_;
|
||||
CompressionType compression_;
|
||||
}
|
||||
@@ -214,24 +214,6 @@ public class OptionsTest {
|
||||
assert(opt.minWriteBufferNumberToMerge() == intValue);
|
||||
}
|
||||
|
||||
{ // BlockSize test
|
||||
long longValue = rand.nextLong();
|
||||
opt.setBlockSize(longValue);
|
||||
assert(opt.blockSize() == longValue);
|
||||
}
|
||||
|
||||
{ // BlockRestartInterval test
|
||||
int intValue = rand.nextInt();
|
||||
opt.setBlockRestartInterval(intValue);
|
||||
assert(opt.blockRestartInterval() == intValue);
|
||||
}
|
||||
|
||||
{ // WholeKeyFiltering test
|
||||
boolean boolValue = rand.nextBoolean();
|
||||
opt.setWholeKeyFiltering(boolValue);
|
||||
assert(opt.wholeKeyFiltering() == boolValue);
|
||||
}
|
||||
|
||||
{ // NumLevels test
|
||||
int intValue = rand.nextInt();
|
||||
opt.setNumLevels(intValue);
|
||||
@@ -304,12 +286,6 @@ public class OptionsTest {
|
||||
assert(opt.maxGrandparentOverlapFactor() == intValue);
|
||||
}
|
||||
|
||||
{ // DisableSeekCompaction test
|
||||
boolean boolValue = rand.nextBoolean();
|
||||
opt.setDisableSeekCompaction(boolValue);
|
||||
assert(opt.disableSeekCompaction() == boolValue);
|
||||
}
|
||||
|
||||
{ // SoftRateLimit test
|
||||
double doubleValue = rand.nextDouble();
|
||||
opt.setSoftRateLimit(doubleValue);
|
||||
@@ -328,12 +304,6 @@ public class OptionsTest {
|
||||
assert(opt.rateLimitDelayMaxMilliseconds() == intValue);
|
||||
}
|
||||
|
||||
{ // NoBlockCache test
|
||||
boolean boolValue = rand.nextBoolean();
|
||||
opt.setNoBlockCache(boolValue);
|
||||
assert(opt.noBlockCache() == boolValue);
|
||||
}
|
||||
|
||||
{ // ArenaBlockSize test
|
||||
long longValue = rand.nextLong();
|
||||
opt.setArenaBlockSize(longValue);
|
||||
@@ -352,12 +322,6 @@ public class OptionsTest {
|
||||
assert(opt.purgeRedundantKvsWhileFlush() == boolValue);
|
||||
}
|
||||
|
||||
{ // BlockSizeDeviation test
|
||||
int intValue = rand.nextInt();
|
||||
opt.setBlockSizeDeviation(intValue);
|
||||
assert(opt.blockSizeDeviation() == intValue);
|
||||
}
|
||||
|
||||
{ // VerifyChecksumsInCompaction test
|
||||
boolean boolValue = rand.nextBoolean();
|
||||
opt.setVerifyChecksumsInCompaction(boolValue);
|
||||
|
||||
@@ -40,7 +40,26 @@ void Java_org_rocksdb_BackupableDB_open(
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_createNewBackup(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle, jboolean jflag) {
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->CreateNewBackup(jflag);
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->CreateNewBackup(jflag);
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: purgeOldBackups
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_purgeOldBackups(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle, jboolean jnumBackupsToKeep) {
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->
|
||||
PurgeOldBackups(jnumBackupsToKeep);
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
@@ -118,17 +117,6 @@ jlong Java_org_rocksdb_Options_statisticsPtr(
|
||||
return reinterpret_cast<jlong>(st);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setFilterHandle
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setFilterHandle(
|
||||
JNIEnv* env, jobject jobj, jlong jopt_handle, jlong jfilter_handle) {
|
||||
reinterpret_cast<rocksdb::Options*>(jopt_handle)->filter_policy =
|
||||
reinterpret_cast<rocksdb::FilterPolicy*>(jfilter_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: maxWriteBufferNumber
|
||||
@@ -139,49 +127,6 @@ jint Java_org_rocksdb_Options_maxWriteBufferNumber(
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->max_write_buffer_number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setBlockSize
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setBlockSize(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jlong jblock_size) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->block_size =
|
||||
static_cast<size_t>(jblock_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: blockSize
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_Options_blockSize(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->block_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setDisableSeekCompaction
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setDisableSeekCompaction(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle,
|
||||
jboolean jdisable_seek_compaction) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->disable_seek_compaction =
|
||||
jdisable_seek_compaction;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: disableSeekCompaction
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_disableSeekCompaction(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->disable_seek_compaction;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: errorIfExists
|
||||
@@ -287,27 +232,6 @@ void Java_org_rocksdb_Options_setUseFsync(
|
||||
static_cast<bool>(use_fsync);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: dbStatsLogInterval
|
||||
* Signature: (J)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Options_dbStatsLogInterval(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->db_stats_log_interval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setDbStatsLogInterval
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setDbStatsLogInterval(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jint db_stats_log_interval) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->db_stats_log_interval =
|
||||
static_cast<int>(db_stats_log_interval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: dbLogDir
|
||||
@@ -914,27 +838,6 @@ void Java_org_rocksdb_Options_setMinWriteBufferNumberToMerge(
|
||||
static_cast<int>(jmin_write_buffer_number_to_merge);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: blockRestartInterval
|
||||
* Signature: (J)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Options_blockRestartInterval(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->block_restart_interval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setBlockRestartInterval
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setBlockRestartInterval(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jint jblock_restart_interval) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->block_restart_interval =
|
||||
static_cast<int>(jblock_restart_interval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setCompressionType
|
||||
@@ -977,27 +880,6 @@ jbyte Java_org_rocksdb_Options_compactionStyle(
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->compaction_style;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: wholeKeyFiltering
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_wholeKeyFiltering(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->whole_key_filtering;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setWholeKeyFiltering
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setWholeKeyFiltering(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jboolean jwhole_key_filtering) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->whole_key_filtering =
|
||||
static_cast<bool>(jwhole_key_filtering);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: numLevels
|
||||
@@ -1345,27 +1227,6 @@ void Java_org_rocksdb_Options_setRateLimitDelayMaxMilliseconds(
|
||||
static_cast<int>(jrate_limit_delay_max_milliseconds);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: noBlockCache
|
||||
* Signature: (J)Z
|
||||
*/
|
||||
jboolean Java_org_rocksdb_Options_noBlockCache(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->no_block_cache;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setNoBlockCache
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setNoBlockCache(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jboolean jno_block_cache) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->no_block_cache =
|
||||
static_cast<bool>(jno_block_cache);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: arenaBlockSize
|
||||
@@ -1435,28 +1296,6 @@ void Java_org_rocksdb_Options_setPurgeRedundantKvsWhileFlush(
|
||||
static_cast<bool>(jpurge_redundant_kvs_while_flush);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: blockSizeDeviation
|
||||
* Signature: (J)I
|
||||
*/
|
||||
jint Java_org_rocksdb_Options_blockSizeDeviation(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->block_size_deviation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setBlockSizeDeviation
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setBlockSizeDeviation(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle,
|
||||
jint jblock_size_deviation) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->block_size_deviation =
|
||||
static_cast<int>(jblock_size_deviation);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: verifyChecksumsInCompaction
|
||||
|
||||
@@ -26,21 +26,8 @@
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_open(
|
||||
JNIEnv* env, jobject jdb, jlong jopt_handle,
|
||||
jlong jcache_size, jint jnum_shardbits, jstring jdb_path) {
|
||||
JNIEnv* env, jobject jdb, jlong jopt_handle, jstring jdb_path) {
|
||||
auto opt = reinterpret_cast<rocksdb::Options*>(jopt_handle);
|
||||
if (jcache_size > 0) {
|
||||
opt->no_block_cache = false;
|
||||
if (jnum_shardbits >= 1) {
|
||||
opt->block_cache = rocksdb::NewLRUCache(jcache_size, jnum_shardbits);
|
||||
} else {
|
||||
opt->block_cache = rocksdb::NewLRUCache(jcache_size);
|
||||
}
|
||||
} else {
|
||||
opt->no_block_cache = true;
|
||||
opt->block_cache = nullptr;
|
||||
}
|
||||
|
||||
rocksdb::DB* db = nullptr;
|
||||
const char* db_path = env->GetStringUTFChars(jdb_path, 0);
|
||||
rocksdb::Status s = rocksdb::DB::Open(*opt, db_path, &db);
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
|
||||
#include <jni.h>
|
||||
#include "include/org_rocksdb_PlainTableConfig.h"
|
||||
#include "include/org_rocksdb_BlockBasedTableConfig.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_PlainTableConfig
|
||||
@@ -24,3 +27,34 @@ jlong Java_org_rocksdb_PlainTableConfig_newTableFactoryHandle(
|
||||
options.index_sparseness = jindex_sparseness;
|
||||
return reinterpret_cast<jlong>(rocksdb::NewPlainTableFactory(options));
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BlockBasedTableConfig
|
||||
* Method: newTableFactoryHandle
|
||||
* Signature: (ZJIJIIZI)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_BlockBasedTableConfig_newTableFactoryHandle(
|
||||
JNIEnv* env, jobject jobj, jboolean no_block_cache, jlong block_cache_size,
|
||||
jint num_shardbits, jlong block_size, jint block_size_deviation,
|
||||
jint block_restart_interval, jboolean whole_key_filtering,
|
||||
jint bits_per_key) {
|
||||
rocksdb::BlockBasedTableOptions options;
|
||||
options.no_block_cache = no_block_cache;
|
||||
|
||||
if (!no_block_cache && block_cache_size > 0) {
|
||||
if (num_shardbits > 0) {
|
||||
options.block_cache =
|
||||
rocksdb::NewLRUCache(block_cache_size, num_shardbits);
|
||||
} else {
|
||||
options.block_cache = rocksdb::NewLRUCache(block_cache_size);
|
||||
}
|
||||
}
|
||||
options.block_size = block_size;
|
||||
options.block_size_deviation = block_size_deviation;
|
||||
options.block_restart_interval = block_restart_interval;
|
||||
options.whole_key_filtering = whole_key_filtering;
|
||||
if (bits_per_key > 0) {
|
||||
options.filter_policy.reset(rocksdb::NewBloomFilterPolicy(bits_per_key));
|
||||
}
|
||||
return reinterpret_cast<jlong>(rocksdb::NewBlockBasedTableFactory(options));
|
||||
}
|
||||
@@ -12,10 +12,12 @@ namespace rocksdb {
|
||||
AdaptiveTableFactory::AdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory)
|
||||
std::shared_ptr<TableFactory> plain_table_factory,
|
||||
std::shared_ptr<TableFactory> cuckoo_table_factory)
|
||||
: table_factory_to_write_(table_factory_to_write),
|
||||
block_based_table_factory_(block_based_table_factory),
|
||||
plain_table_factory_(plain_table_factory) {
|
||||
plain_table_factory_(plain_table_factory),
|
||||
cuckoo_table_factory_(cuckoo_table_factory) {
|
||||
if (!table_factory_to_write_) {
|
||||
table_factory_to_write_ = block_based_table_factory_;
|
||||
}
|
||||
@@ -25,12 +27,16 @@ AdaptiveTableFactory::AdaptiveTableFactory(
|
||||
if (!block_based_table_factory_) {
|
||||
block_based_table_factory_.reset(NewBlockBasedTableFactory());
|
||||
}
|
||||
if (!cuckoo_table_factory_) {
|
||||
cuckoo_table_factory_.reset(NewCuckooTableFactory());
|
||||
}
|
||||
}
|
||||
|
||||
extern const uint64_t kPlainTableMagicNumber;
|
||||
extern const uint64_t kLegacyPlainTableMagicNumber;
|
||||
extern const uint64_t kBlockBasedTableMagicNumber;
|
||||
extern const uint64_t kLegacyBlockBasedTableMagicNumber;
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
Status AdaptiveTableFactory::NewTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
@@ -49,6 +55,9 @@ Status AdaptiveTableFactory::NewTableReader(
|
||||
footer.table_magic_number() == kLegacyBlockBasedTableMagicNumber) {
|
||||
return block_based_table_factory_->NewTableReader(
|
||||
options, soptions, icomp, std::move(file), file_size, table);
|
||||
} else if (footer.table_magic_number() == kCuckooTableMagicNumber) {
|
||||
return cuckoo_table_factory_->NewTableReader(
|
||||
options, soptions, icomp, std::move(file), file_size, table);
|
||||
} else {
|
||||
return Status::NotSupported("Unidentified table format");
|
||||
}
|
||||
@@ -61,12 +70,46 @@ TableBuilder* AdaptiveTableFactory::NewTableBuilder(
|
||||
file, compression_type);
|
||||
}
|
||||
|
||||
std::string AdaptiveTableFactory::GetPrintableTableOptions() const {
|
||||
std::string ret;
|
||||
ret.reserve(20000);
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
if (!table_factory_to_write_) {
|
||||
snprintf(buffer, kBufferSize, " write factory (%s) options:\n%s\n",
|
||||
table_factory_to_write_->Name(),
|
||||
table_factory_to_write_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!plain_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
plain_table_factory_->Name(),
|
||||
plain_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!block_based_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
block_based_table_factory_->Name(),
|
||||
block_based_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!cuckoo_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
cuckoo_table_factory_->Name(),
|
||||
cuckoo_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern TableFactory* NewAdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory) {
|
||||
return new AdaptiveTableFactory(
|
||||
table_factory_to_write, block_based_table_factory, plain_table_factory);
|
||||
std::shared_ptr<TableFactory> plain_table_factory,
|
||||
std::shared_ptr<TableFactory> cuckoo_table_factory) {
|
||||
return new AdaptiveTableFactory(table_factory_to_write,
|
||||
block_based_table_factory, plain_table_factory, cuckoo_table_factory);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
@@ -28,7 +29,8 @@ class AdaptiveTableFactory : public TableFactory {
|
||||
explicit AdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory);
|
||||
std::shared_ptr<TableFactory> plain_table_factory,
|
||||
std::shared_ptr<TableFactory> cuckoo_table_factory);
|
||||
const char* Name() const override { return "AdaptiveTableFactory"; }
|
||||
Status NewTableReader(const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
@@ -40,10 +42,22 @@ class AdaptiveTableFactory : public TableFactory {
|
||||
CompressionType compression_type) const
|
||||
override;
|
||||
|
||||
// Sanitizes the specified DB Options.
|
||||
Status SanitizeDBOptions(const DBOptions* db_opts) const override {
|
||||
if (db_opts->allow_mmap_reads == false) {
|
||||
return Status::NotSupported(
|
||||
"AdaptiveTable with allow_mmap_reads == false is not supported.");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string GetPrintableTableOptions() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<TableFactory> table_factory_to_write_;
|
||||
std::shared_ptr<TableFactory> block_based_table_factory_;
|
||||
std::shared_ptr<TableFactory> plain_table_factory_;
|
||||
std::shared_ptr<TableFactory> cuckoo_table_factory_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+246
-272
@@ -17,44 +17,14 @@
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "table/format.h"
|
||||
#include "table/block_hash_index.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
uint32_t Block::NumRestarts() const {
|
||||
assert(size_ >= 2*sizeof(uint32_t));
|
||||
return DecodeFixed32(data_ + size_ - sizeof(uint32_t));
|
||||
}
|
||||
|
||||
Block::Block(const BlockContents& contents)
|
||||
: data_(contents.data.data()),
|
||||
size_(contents.data.size()),
|
||||
owned_(contents.heap_allocated),
|
||||
cachable_(contents.cachable),
|
||||
compression_type_(contents.compression_type) {
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
size_ = 0; // Error marker
|
||||
} else {
|
||||
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
|
||||
if (restart_offset_ > size_ - sizeof(uint32_t)) {
|
||||
// The size is too small for NumRestarts() and therefore
|
||||
// restart_offset_ wrapped around.
|
||||
size_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Block::~Block() {
|
||||
if (owned_) {
|
||||
delete[] data_;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper routine: decode the next block entry starting at "p",
|
||||
// storing the number of shared key bytes, non_shared key bytes,
|
||||
// and the length of the value in "*shared", "*non_shared", and
|
||||
@@ -85,142 +55,85 @@ static inline const char* DecodeEntry(const char* p, const char* limit,
|
||||
return p;
|
||||
}
|
||||
|
||||
class Block::Iter : public Iterator {
|
||||
private:
|
||||
const Comparator* const comparator_;
|
||||
const char* const data_; // underlying block contents
|
||||
uint32_t const restarts_; // Offset of restart array (list of fixed32)
|
||||
uint32_t const num_restarts_; // Number of uint32_t entries in restart array
|
||||
void BlockIter::Next() {
|
||||
assert(Valid());
|
||||
ParseNextKey();
|
||||
}
|
||||
|
||||
// current_ is offset in data_ of current entry. >= restarts_ if !Valid
|
||||
uint32_t current_;
|
||||
uint32_t restart_index_; // Index of restart block in which current_ falls
|
||||
IterKey key_;
|
||||
Slice value_;
|
||||
Status status_;
|
||||
BlockHashIndex* hash_index_;
|
||||
BlockPrefixIndex* prefix_index_;
|
||||
void BlockIter::Prev() {
|
||||
assert(Valid());
|
||||
|
||||
inline int Compare(const Slice& a, const Slice& b) const {
|
||||
return comparator_->Compare(a, b);
|
||||
}
|
||||
|
||||
// Return the offset in data_ just past the end of the current entry.
|
||||
inline uint32_t NextEntryOffset() const {
|
||||
return (value_.data() + value_.size()) - data_;
|
||||
}
|
||||
|
||||
uint32_t GetRestartPoint(uint32_t index) {
|
||||
assert(index < num_restarts_);
|
||||
return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void SeekToRestartPoint(uint32_t index) {
|
||||
key_.Clear();
|
||||
restart_index_ = index;
|
||||
// current_ will be fixed by ParseNextKey();
|
||||
|
||||
// ParseNextKey() starts at the end of value_, so set value_ accordingly
|
||||
uint32_t offset = GetRestartPoint(index);
|
||||
value_ = Slice(data_ + offset, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
Iter(const Comparator* comparator, const char* data, uint32_t restarts,
|
||||
uint32_t num_restarts, BlockHashIndex* hash_index,
|
||||
BlockPrefixIndex* prefix_index)
|
||||
: comparator_(comparator),
|
||||
data_(data),
|
||||
restarts_(restarts),
|
||||
num_restarts_(num_restarts),
|
||||
current_(restarts_),
|
||||
restart_index_(num_restarts_),
|
||||
hash_index_(hash_index),
|
||||
prefix_index_(prefix_index) {
|
||||
assert(num_restarts_ > 0);
|
||||
}
|
||||
|
||||
virtual bool Valid() const { return current_ < restarts_; }
|
||||
virtual Status status() const { return status_; }
|
||||
virtual Slice key() const {
|
||||
assert(Valid());
|
||||
return key_.GetKey();
|
||||
}
|
||||
virtual Slice value() const {
|
||||
assert(Valid());
|
||||
return value_;
|
||||
}
|
||||
|
||||
virtual void Next() {
|
||||
assert(Valid());
|
||||
ParseNextKey();
|
||||
}
|
||||
|
||||
virtual void Prev() {
|
||||
assert(Valid());
|
||||
|
||||
// Scan backwards to a restart point before current_
|
||||
const uint32_t original = current_;
|
||||
while (GetRestartPoint(restart_index_) >= original) {
|
||||
if (restart_index_ == 0) {
|
||||
// No more entries
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
return;
|
||||
}
|
||||
restart_index_--;
|
||||
}
|
||||
|
||||
SeekToRestartPoint(restart_index_);
|
||||
do {
|
||||
// Loop until end of current entry hits the start of original entry
|
||||
} while (ParseNextKey() && NextEntryOffset() < original);
|
||||
}
|
||||
|
||||
virtual void Seek(const Slice& target) {
|
||||
uint32_t index = 0;
|
||||
bool ok = false;
|
||||
if (prefix_index_) {
|
||||
ok = PrefixSeek(target, &index);
|
||||
} else {
|
||||
ok = hash_index_ ? HashSeek(target, &index)
|
||||
: BinarySeek(target, 0, num_restarts_ - 1, &index);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
// Scan backwards to a restart point before current_
|
||||
const uint32_t original = current_;
|
||||
while (GetRestartPoint(restart_index_) >= original) {
|
||||
if (restart_index_ == 0) {
|
||||
// No more entries
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(index);
|
||||
// Linear search (within restart block) for first key >= target
|
||||
restart_index_--;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (!ParseNextKey() || Compare(key_.GetKey(), target) >= 0) {
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(restart_index_);
|
||||
do {
|
||||
// Loop until end of current entry hits the start of original entry
|
||||
} while (ParseNextKey() && NextEntryOffset() < original);
|
||||
}
|
||||
|
||||
void BlockIter::Seek(const Slice& target) {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
uint32_t index = 0;
|
||||
bool ok = false;
|
||||
if (prefix_index_) {
|
||||
ok = PrefixSeek(target, &index);
|
||||
} else {
|
||||
ok = hash_index_ ? HashSeek(target, &index)
|
||||
: BinarySeek(target, 0, num_restarts_ - 1, &index);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(index);
|
||||
// Linear search (within restart block) for first key >= target
|
||||
|
||||
while (true) {
|
||||
if (!ParseNextKey() || Compare(key_.GetKey(), target) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
virtual void SeekToFirst() {
|
||||
SeekToRestartPoint(0);
|
||||
ParseNextKey();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void SeekToLast() {
|
||||
SeekToRestartPoint(num_restarts_ - 1);
|
||||
while (ParseNextKey() && NextEntryOffset() < restarts_) {
|
||||
// Keep skipping
|
||||
}
|
||||
void BlockIter::SeekToFirst() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(0);
|
||||
ParseNextKey();
|
||||
}
|
||||
|
||||
private:
|
||||
void CorruptionError() {
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
status_ = Status::Corruption("bad entry in block");
|
||||
key_.Clear();
|
||||
value_.clear();
|
||||
void BlockIter::SeekToLast() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(num_restarts_ - 1);
|
||||
while (ParseNextKey() && NextEntryOffset() < restarts_) {
|
||||
// Keep skipping
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseNextKey() {
|
||||
void BlockIter::CorruptionError() {
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
status_ = Status::Corruption("bad entry in block");
|
||||
key_.Clear();
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
bool BlockIter::ParseNextKey() {
|
||||
current_ = NextEntryOffset();
|
||||
const char* p = data_ + current_;
|
||||
const char* limit = data_ + restarts_; // Restarts come right after data
|
||||
@@ -248,150 +161,200 @@ class Block::Iter : public Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search in restart array to find the first restart point
|
||||
// with a key >= target (TODO: this comment is inaccurate)
|
||||
bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
// Binary search in restart array to find the first restart point
|
||||
// with a key >= target (TODO: this comment is inaccurate)
|
||||
bool BlockIter::BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
uint32_t* index) {
|
||||
assert(left <= right);
|
||||
assert(left <= right);
|
||||
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right + 1) / 2;
|
||||
uint32_t region_offset = GetRestartPoint(mid);
|
||||
uint32_t shared, non_shared, value_length;
|
||||
const char* key_ptr =
|
||||
DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
|
||||
&non_shared, &value_length);
|
||||
if (key_ptr == nullptr || (shared != 0)) {
|
||||
CorruptionError();
|
||||
return false;
|
||||
}
|
||||
Slice mid_key(key_ptr, non_shared);
|
||||
int cmp = Compare(mid_key, target);
|
||||
if (cmp < 0) {
|
||||
// Key at "mid" is smaller than "target". Therefore all
|
||||
// blocks before "mid" are uninteresting.
|
||||
left = mid;
|
||||
} else if (cmp > 0) {
|
||||
// Key at "mid" is >= "target". Therefore all blocks at or
|
||||
// after "mid" are uninteresting.
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
*index = left;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare target key and the block key of the block of `block_index`.
|
||||
// Return -1 if error.
|
||||
int CompareBlockKey(uint32_t block_index, const Slice& target) {
|
||||
uint32_t region_offset = GetRestartPoint(block_index);
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right + 1) / 2;
|
||||
uint32_t region_offset = GetRestartPoint(mid);
|
||||
uint32_t shared, non_shared, value_length;
|
||||
const char* key_ptr = DecodeEntry(data_ + region_offset, data_ + restarts_,
|
||||
&shared, &non_shared, &value_length);
|
||||
const char* key_ptr =
|
||||
DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
|
||||
&non_shared, &value_length);
|
||||
if (key_ptr == nullptr || (shared != 0)) {
|
||||
CorruptionError();
|
||||
return 1; // Return target is smaller
|
||||
return false;
|
||||
}
|
||||
Slice block_key(key_ptr, non_shared);
|
||||
return Compare(block_key, target);
|
||||
}
|
||||
|
||||
// Binary search in block_ids to find the first block
|
||||
// with a key >= target
|
||||
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
uint32_t left, uint32_t right,
|
||||
uint32_t* index) {
|
||||
assert(left <= right);
|
||||
uint32_t left_bound = left;
|
||||
|
||||
while (left <= right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
|
||||
int cmp = CompareBlockKey(block_ids[mid], target);
|
||||
if (!status_.ok()) {
|
||||
return false;
|
||||
}
|
||||
if (cmp < 0) {
|
||||
// Key at "target" is larger than "mid". Therefore all
|
||||
// blocks before or at "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "target" is <= "mid". Therefore all blocks
|
||||
// after "mid" are uninteresting.
|
||||
// If there is only one block left, we found it.
|
||||
if (left == right) break;
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (left == right) {
|
||||
// In one of the two following cases:
|
||||
// (1) left is the first one of block_ids
|
||||
// (2) there is a gap of blocks between block of `left` and `left-1`.
|
||||
// we can further distinguish the case of key in the block or key not
|
||||
// existing, by comparing the target key and the key of the previous
|
||||
// block to the left of the block found.
|
||||
if (block_ids[left] > 0 &&
|
||||
(left == left_bound || block_ids[left - 1] != block_ids[left] - 1) &&
|
||||
CompareBlockKey(block_ids[left] - 1, target) > 0) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
}
|
||||
|
||||
*index = block_ids[left];
|
||||
return true;
|
||||
Slice mid_key(key_ptr, non_shared);
|
||||
int cmp = Compare(mid_key, target);
|
||||
if (cmp < 0) {
|
||||
// Key at "mid" is smaller than "target". Therefore all
|
||||
// blocks before "mid" are uninteresting.
|
||||
left = mid;
|
||||
} else if (cmp > 0) {
|
||||
// Key at "mid" is >= "target". Therefore all blocks at or
|
||||
// after "mid" are uninteresting.
|
||||
right = mid - 1;
|
||||
} else {
|
||||
assert(left > right);
|
||||
// Mark iterator invalid
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
left = right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
bool HashSeek(const Slice& target, uint32_t* index) {
|
||||
assert(hash_index_);
|
||||
auto restart_index = hash_index_->GetRestartIndex(target);
|
||||
if (restart_index == nullptr) {
|
||||
*index = left;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Compare target key and the block key of the block of `block_index`.
|
||||
// Return -1 if error.
|
||||
int BlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) {
|
||||
uint32_t region_offset = GetRestartPoint(block_index);
|
||||
uint32_t shared, non_shared, value_length;
|
||||
const char* key_ptr = DecodeEntry(data_ + region_offset, data_ + restarts_,
|
||||
&shared, &non_shared, &value_length);
|
||||
if (key_ptr == nullptr || (shared != 0)) {
|
||||
CorruptionError();
|
||||
return 1; // Return target is smaller
|
||||
}
|
||||
Slice block_key(key_ptr, non_shared);
|
||||
return Compare(block_key, target);
|
||||
}
|
||||
|
||||
// Binary search in block_ids to find the first block
|
||||
// with a key >= target
|
||||
bool BlockIter::BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
uint32_t left, uint32_t right,
|
||||
uint32_t* index) {
|
||||
assert(left <= right);
|
||||
uint32_t left_bound = left;
|
||||
|
||||
while (left <= right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
|
||||
int cmp = CompareBlockKey(block_ids[mid], target);
|
||||
if (!status_.ok()) {
|
||||
return false;
|
||||
}
|
||||
if (cmp < 0) {
|
||||
// Key at "target" is larger than "mid". Therefore all
|
||||
// blocks before or at "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "target" is <= "mid". Therefore all blocks
|
||||
// after "mid" are uninteresting.
|
||||
// If there is only one block left, we found it.
|
||||
if (left == right) break;
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (left == right) {
|
||||
// In one of the two following cases:
|
||||
// (1) left is the first one of block_ids
|
||||
// (2) there is a gap of blocks between block of `left` and `left-1`.
|
||||
// we can further distinguish the case of key in the block or key not
|
||||
// existing, by comparing the target key and the key of the previous
|
||||
// block to the left of the block found.
|
||||
if (block_ids[left] > 0 &&
|
||||
(left == left_bound || block_ids[left - 1] != block_ids[left] - 1) &&
|
||||
CompareBlockKey(block_ids[left] - 1, target) > 0) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// the elements in restart_array[index : index + num_blocks]
|
||||
// are all with same prefix. We'll do binary search in that small range.
|
||||
auto left = restart_index->first_index;
|
||||
auto right = restart_index->first_index + restart_index->num_blocks - 1;
|
||||
return BinarySeek(target, left, right, index);
|
||||
*index = block_ids[left];
|
||||
return true;
|
||||
} else {
|
||||
assert(left > right);
|
||||
// Mark iterator invalid
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockIter::HashSeek(const Slice& target, uint32_t* index) {
|
||||
assert(hash_index_);
|
||||
auto restart_index = hash_index_->GetRestartIndex(target);
|
||||
if (restart_index == nullptr) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PrefixSeek(const Slice& target, uint32_t* index) {
|
||||
assert(prefix_index_);
|
||||
uint32_t* block_ids = nullptr;
|
||||
uint32_t num_blocks = prefix_index_->GetBlocks(target, &block_ids);
|
||||
// the elements in restart_array[index : index + num_blocks]
|
||||
// are all with same prefix. We'll do binary search in that small range.
|
||||
auto left = restart_index->first_index;
|
||||
auto right = restart_index->first_index + restart_index->num_blocks - 1;
|
||||
return BinarySeek(target, left, right, index);
|
||||
}
|
||||
|
||||
bool BlockIter::PrefixSeek(const Slice& target, uint32_t* index) {
|
||||
assert(prefix_index_);
|
||||
uint32_t* block_ids = nullptr;
|
||||
uint32_t num_blocks = prefix_index_->GetBlocks(target, &block_ids);
|
||||
|
||||
if (num_blocks == 0) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
} else {
|
||||
return BinaryBlockIndexSeek(target, block_ids, 0, num_blocks - 1, index);
|
||||
if (num_blocks == 0) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
} else {
|
||||
return BinaryBlockIndexSeek(target, block_ids, 0, num_blocks - 1, index);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Block::NumRestarts() const {
|
||||
assert(size_ >= 2*sizeof(uint32_t));
|
||||
return DecodeFixed32(data_ + size_ - sizeof(uint32_t));
|
||||
}
|
||||
|
||||
Block::Block(const BlockContents& contents)
|
||||
: data_(contents.data.data()),
|
||||
size_(contents.data.size()),
|
||||
owned_(contents.heap_allocated),
|
||||
cachable_(contents.cachable),
|
||||
compression_type_(contents.compression_type) {
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
size_ = 0; // Error marker
|
||||
} else {
|
||||
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
|
||||
if (restart_offset_ > size_ - sizeof(uint32_t)) {
|
||||
// The size is too small for NumRestarts() and therefore
|
||||
// restart_offset_ wrapped around.
|
||||
size_ = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Iterator* Block::NewIterator(const Comparator* cmp) {
|
||||
Block::~Block() {
|
||||
if (owned_) {
|
||||
delete[] data_;
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* Block::NewIterator(
|
||||
const Comparator* cmp, BlockIter* iter, bool total_order_seek) {
|
||||
if (size_ < 2*sizeof(uint32_t)) {
|
||||
return NewErrorIterator(Status::Corruption("bad block contents"));
|
||||
if (iter != nullptr) {
|
||||
iter->SetStatus(Status::Corruption("bad block contents"));
|
||||
return iter;
|
||||
} else {
|
||||
return NewErrorIterator(Status::Corruption("bad block contents"));
|
||||
}
|
||||
}
|
||||
const uint32_t num_restarts = NumRestarts();
|
||||
if (num_restarts == 0) {
|
||||
return NewEmptyIterator();
|
||||
if (iter != nullptr) {
|
||||
iter->SetStatus(Status::OK());
|
||||
return iter;
|
||||
} else {
|
||||
return NewEmptyIterator();
|
||||
}
|
||||
} else {
|
||||
return new Iter(cmp, data_, restart_offset_, num_restarts,
|
||||
hash_index_.get(), prefix_index_.get());
|
||||
BlockHashIndex* hash_index_ptr =
|
||||
total_order_seek ? nullptr : hash_index_.get();
|
||||
BlockPrefixIndex* prefix_index_ptr =
|
||||
total_order_seek ? nullptr : prefix_index_.get();
|
||||
|
||||
if (iter != nullptr) {
|
||||
iter->Initialize(cmp, data_, restart_offset_, num_restarts,
|
||||
hash_index_ptr, prefix_index_ptr);
|
||||
} else {
|
||||
iter = new BlockIter(cmp, data_, restart_offset_, num_restarts,
|
||||
hash_index_ptr, prefix_index_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
void Block::SetBlockHashIndex(BlockHashIndex* hash_index) {
|
||||
@@ -402,4 +365,15 @@ void Block::SetBlockPrefixIndex(BlockPrefixIndex* prefix_index) {
|
||||
prefix_index_.reset(prefix_index);
|
||||
}
|
||||
|
||||
size_t Block::ApproximateMemoryUsage() const {
|
||||
size_t usage = size();
|
||||
if (hash_index_) {
|
||||
usage += hash_index_->ApproximateMemoryUsage();
|
||||
}
|
||||
if (prefix_index_) {
|
||||
usage += prefix_index_->ApproximateMemoryUsage();
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+133
-2
@@ -13,11 +13,13 @@
|
||||
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct BlockContents;
|
||||
class Comparator;
|
||||
class BlockIter;
|
||||
class BlockHashIndex;
|
||||
class BlockPrefixIndex;
|
||||
|
||||
@@ -40,10 +42,21 @@ class Block {
|
||||
// NOTE: for the hash based lookup, if a key prefix doesn't match any key,
|
||||
// the iterator will simply be set as "invalid", rather than returning
|
||||
// the key that is just pass the target key.
|
||||
Iterator* NewIterator(const Comparator* comparator);
|
||||
//
|
||||
// If iter is null, return new Iterator
|
||||
// If iter is not null, update this one and return it as Iterator*
|
||||
//
|
||||
// If total_order_seek is true, hash_index_ and prefix_index_ are ignored.
|
||||
// This option only applies for index block. For data block, hash_index_
|
||||
// and prefix_index_ are null, so this option does not matter.
|
||||
Iterator* NewIterator(const Comparator* comparator,
|
||||
BlockIter* iter = nullptr, bool total_order_seek = true);
|
||||
void SetBlockHashIndex(BlockHashIndex* hash_index);
|
||||
void SetBlockPrefixIndex(BlockPrefixIndex* prefix_index);
|
||||
|
||||
// Report an approximation of how much memory has been used.
|
||||
size_t ApproximateMemoryUsage() const;
|
||||
|
||||
private:
|
||||
const char* data_;
|
||||
size_t size_;
|
||||
@@ -57,8 +70,126 @@ class Block {
|
||||
// No copying allowed
|
||||
Block(const Block&);
|
||||
void operator=(const Block&);
|
||||
};
|
||||
|
||||
class BlockIter : public Iterator {
|
||||
public:
|
||||
BlockIter()
|
||||
: comparator_(nullptr),
|
||||
data_(nullptr),
|
||||
restarts_(0),
|
||||
num_restarts_(0),
|
||||
current_(0),
|
||||
restart_index_(0),
|
||||
status_(Status::OK()),
|
||||
hash_index_(nullptr),
|
||||
prefix_index_(nullptr) {}
|
||||
|
||||
BlockIter(const Comparator* comparator, const char* data, uint32_t restarts,
|
||||
uint32_t num_restarts, BlockHashIndex* hash_index,
|
||||
BlockPrefixIndex* prefix_index)
|
||||
: BlockIter() {
|
||||
Initialize(comparator, data, restarts, num_restarts,
|
||||
hash_index, prefix_index);
|
||||
}
|
||||
|
||||
void Initialize(const Comparator* comparator, const char* data,
|
||||
uint32_t restarts, uint32_t num_restarts, BlockHashIndex* hash_index,
|
||||
BlockPrefixIndex* prefix_index) {
|
||||
assert(data_ == nullptr); // Ensure it is called only once
|
||||
assert(num_restarts > 0); // Ensure the param is valid
|
||||
|
||||
comparator_ = comparator;
|
||||
data_ = data;
|
||||
restarts_ = restarts;
|
||||
num_restarts_ = num_restarts;
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
hash_index_ = hash_index;
|
||||
prefix_index_ = prefix_index;
|
||||
}
|
||||
|
||||
void SetStatus(Status s) {
|
||||
status_ = s;
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return current_ < restarts_; }
|
||||
virtual Status status() const override { return status_; }
|
||||
virtual Slice key() const override {
|
||||
assert(Valid());
|
||||
return key_.GetKey();
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(Valid());
|
||||
return value_;
|
||||
}
|
||||
|
||||
virtual void Next() override;
|
||||
|
||||
virtual void Prev() override;
|
||||
|
||||
virtual void Seek(const Slice& target) override;
|
||||
|
||||
virtual void SeekToFirst() override;
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
private:
|
||||
const Comparator* comparator_;
|
||||
const char* data_; // underlying block contents
|
||||
uint32_t restarts_; // Offset of restart array (list of fixed32)
|
||||
uint32_t num_restarts_; // Number of uint32_t entries in restart array
|
||||
|
||||
// current_ is offset in data_ of current entry. >= restarts_ if !Valid
|
||||
uint32_t current_;
|
||||
uint32_t restart_index_; // Index of restart block in which current_ falls
|
||||
IterKey key_;
|
||||
Slice value_;
|
||||
Status status_;
|
||||
BlockHashIndex* hash_index_;
|
||||
BlockPrefixIndex* prefix_index_;
|
||||
|
||||
inline int Compare(const Slice& a, const Slice& b) const {
|
||||
return comparator_->Compare(a, b);
|
||||
}
|
||||
|
||||
// Return the offset in data_ just past the end of the current entry.
|
||||
inline uint32_t NextEntryOffset() const {
|
||||
return (value_.data() + value_.size()) - data_;
|
||||
}
|
||||
|
||||
uint32_t GetRestartPoint(uint32_t index) {
|
||||
assert(index < num_restarts_);
|
||||
return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void SeekToRestartPoint(uint32_t index) {
|
||||
key_.Clear();
|
||||
restart_index_ = index;
|
||||
// current_ will be fixed by ParseNextKey();
|
||||
|
||||
// ParseNextKey() starts at the end of value_, so set value_ accordingly
|
||||
uint32_t offset = GetRestartPoint(index);
|
||||
value_ = Slice(data_ + offset, 0);
|
||||
}
|
||||
|
||||
void CorruptionError();
|
||||
|
||||
bool ParseNextKey();
|
||||
|
||||
bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
uint32_t* index);
|
||||
|
||||
int CompareBlockKey(uint32_t block_index, const Slice& target);
|
||||
|
||||
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
uint32_t left, uint32_t right,
|
||||
uint32_t* index);
|
||||
|
||||
bool HashSeek(const Slice& target, uint32_t* index);
|
||||
|
||||
bool PrefixSeek(const Slice& target, uint32_t* index);
|
||||
|
||||
class Iter;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -45,7 +45,6 @@ namespace rocksdb {
|
||||
|
||||
extern const std::string kHashIndexPrefixesBlock;
|
||||
extern const std::string kHashIndexPrefixesMetadataBlock;
|
||||
namespace {
|
||||
|
||||
typedef BlockBasedTableOptions::IndexType IndexType;
|
||||
|
||||
@@ -117,7 +116,7 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
public:
|
||||
explicit ShortenedIndexBuilder(const Comparator* comparator)
|
||||
: IndexBuilder(comparator),
|
||||
index_block_builder_(1 /* block_restart_interval == 1 */, comparator) {}
|
||||
index_block_builder_(1 /* block_restart_interval == 1 */) {}
|
||||
|
||||
virtual void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
@@ -335,8 +334,6 @@ Slice CompressBlock(const Slice& raw,
|
||||
return raw;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// kBlockBasedTableMagicNumber was picked by running
|
||||
// echo rocksdb.table.block_based | sha1sum
|
||||
// and taking the leading 64 bits.
|
||||
@@ -388,7 +385,8 @@ class BlockBasedTableBuilder::BlockBasedTablePropertiesCollector
|
||||
};
|
||||
|
||||
struct BlockBasedTableBuilder::Rep {
|
||||
Options options;
|
||||
const Options options;
|
||||
const BlockBasedTableOptions table_options;
|
||||
const InternalKeyComparator& internal_comparator;
|
||||
WritableFile* file;
|
||||
uint64_t offset = 0;
|
||||
@@ -400,7 +398,6 @@ struct BlockBasedTableBuilder::Rep {
|
||||
|
||||
std::string last_key;
|
||||
CompressionType compression_type;
|
||||
ChecksumType checksum_type;
|
||||
TableProperties props;
|
||||
|
||||
bool closed = false; // Either Finish() or Abandon() has been called.
|
||||
@@ -416,31 +413,32 @@ struct BlockBasedTableBuilder::Rep {
|
||||
std::vector<std::unique_ptr<TablePropertiesCollector>>
|
||||
table_properties_collectors;
|
||||
|
||||
Rep(const Options& opt, const InternalKeyComparator& icomparator,
|
||||
WritableFile* f, FlushBlockPolicyFactory* flush_block_policy_factory,
|
||||
CompressionType compression_type, IndexType index_block_type,
|
||||
ChecksumType checksum_type)
|
||||
Rep(const Options& opt, const BlockBasedTableOptions& table_opt,
|
||||
const InternalKeyComparator& icomparator,
|
||||
WritableFile* f, CompressionType compression_type)
|
||||
: options(opt),
|
||||
table_options(table_opt),
|
||||
internal_comparator(icomparator),
|
||||
file(f),
|
||||
data_block(options, &internal_comparator),
|
||||
data_block(table_options.block_restart_interval),
|
||||
internal_prefix_transform(options.prefix_extractor.get()),
|
||||
index_builder(CreateIndexBuilder(index_block_type, &internal_comparator,
|
||||
&this->internal_prefix_transform)),
|
||||
index_builder(CreateIndexBuilder(
|
||||
table_options.index_type, &internal_comparator,
|
||||
&this->internal_prefix_transform)),
|
||||
compression_type(compression_type),
|
||||
checksum_type(checksum_type),
|
||||
filter_block(opt.filter_policy == nullptr
|
||||
? nullptr
|
||||
: new FilterBlockBuilder(opt, &internal_comparator)),
|
||||
flush_block_policy(flush_block_policy_factory->NewFlushBlockPolicy(
|
||||
options, data_block)) {
|
||||
filter_block(table_options.filter_policy == nullptr ?
|
||||
nullptr :
|
||||
new FilterBlockBuilder(opt, table_options, &internal_comparator)),
|
||||
flush_block_policy(
|
||||
table_options.flush_block_policy_factory->NewFlushBlockPolicy(
|
||||
table_options, data_block)) {
|
||||
for (auto& collector_factories :
|
||||
options.table_properties_collector_factories) {
|
||||
table_properties_collectors.emplace_back(
|
||||
collector_factories->CreateTablePropertiesCollector());
|
||||
}
|
||||
table_properties_collectors.emplace_back(
|
||||
new BlockBasedTablePropertiesCollector(index_block_type));
|
||||
new BlockBasedTablePropertiesCollector(table_options.index_type));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -448,16 +446,14 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
const Options& options, const BlockBasedTableOptions& table_options,
|
||||
const InternalKeyComparator& internal_comparator, WritableFile* file,
|
||||
CompressionType compression_type)
|
||||
: rep_(new Rep(options, internal_comparator, file,
|
||||
table_options.flush_block_policy_factory.get(),
|
||||
compression_type, table_options.index_type,
|
||||
table_options.checksum)) {
|
||||
: rep_(new Rep(options, table_options, internal_comparator,
|
||||
file, compression_type)) {
|
||||
if (rep_->filter_block != nullptr) {
|
||||
rep_->filter_block->StartBlock(0);
|
||||
}
|
||||
if (options.block_cache_compressed.get() != nullptr) {
|
||||
if (table_options.block_cache_compressed.get() != nullptr) {
|
||||
BlockBasedTable::GenerateCachePrefix(
|
||||
options.block_cache_compressed.get(), file,
|
||||
table_options.block_cache_compressed.get(), file,
|
||||
&rep_->compressed_cache_key_prefix[0],
|
||||
&rep_->compressed_cache_key_prefix_size);
|
||||
}
|
||||
@@ -496,7 +492,7 @@ void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
}
|
||||
|
||||
if (r->filter_block != nullptr) {
|
||||
r->filter_block->AddKey(key);
|
||||
r->filter_block->AddKey(ExtractUserKey(key));
|
||||
}
|
||||
|
||||
r->last_key.assign(key.data(), key.size());
|
||||
@@ -569,7 +565,7 @@ void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
char trailer[kBlockTrailerSize];
|
||||
trailer[0] = type;
|
||||
char* trailer_without_type = trailer + 1;
|
||||
switch (r->checksum_type) {
|
||||
switch (r->table_options.checksum) {
|
||||
case kNoChecksum:
|
||||
// we don't support no checksum yet
|
||||
assert(false);
|
||||
@@ -615,7 +611,7 @@ Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents,
|
||||
const CompressionType type,
|
||||
const BlockHandle* handle) {
|
||||
Rep* r = rep_;
|
||||
Cache* block_cache_compressed = r->options.block_cache_compressed.get();
|
||||
Cache* block_cache_compressed = r->table_options.block_cache_compressed.get();
|
||||
|
||||
if (type != kNoCompression && block_cache_compressed != nullptr) {
|
||||
|
||||
@@ -704,7 +700,7 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
// Add mapping from "<filter_block_prefix>.Name" to location
|
||||
// of filter data.
|
||||
std::string key = BlockBasedTable::kFilterBlockPrefix;
|
||||
key.append(r->options.filter_policy->Name());
|
||||
key.append(r->table_options.filter_policy->Name());
|
||||
meta_index_builder.Add(key, filter_block_handle);
|
||||
}
|
||||
|
||||
@@ -712,8 +708,8 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
{
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
std::vector<std::string> failed_user_prop_collectors;
|
||||
r->props.filter_policy_name = r->options.filter_policy != nullptr ?
|
||||
r->options.filter_policy->Name() : "";
|
||||
r->props.filter_policy_name = r->table_options.filter_policy != nullptr ?
|
||||
r->table_options.filter_policy->Name() : "";
|
||||
r->props.index_size =
|
||||
r->index_builder->EstimatedSize() + kBlockTrailerSize;
|
||||
|
||||
@@ -753,12 +749,12 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
// TODO(icanadi) at some point in the future, when we're absolutely sure
|
||||
// nobody will roll back to RocksDB 2.x versions, retire the legacy magic
|
||||
// number and always write new table files with new magic number
|
||||
bool legacy = (r->checksum_type == kCRC32c);
|
||||
bool legacy = (r->table_options.checksum == kCRC32c);
|
||||
Footer footer(legacy ? kLegacyBlockBasedTableMagicNumber
|
||||
: kBlockBasedTableMagicNumber);
|
||||
footer.set_metaindex_handle(metaindex_block_handle);
|
||||
footer.set_index_handle(index_block_handle);
|
||||
footer.set_checksum(r->checksum_type);
|
||||
footer.set_checksum(r->table_options.checksum);
|
||||
std::string footer_encoding;
|
||||
footer.EncodeTo(&footer_encoding);
|
||||
r->status = r->file->Append(footer_encoding);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "port/port.h"
|
||||
@@ -28,6 +29,15 @@ BlockBasedTableFactory::BlockBasedTableFactory(
|
||||
table_options_.flush_block_policy_factory.reset(
|
||||
new FlushBlockBySizePolicyFactory());
|
||||
}
|
||||
if (table_options_.no_block_cache) {
|
||||
table_options_.block_cache.reset();
|
||||
} else if (table_options_.block_cache == nullptr) {
|
||||
table_options_.block_cache = NewLRUCache(8 << 20);
|
||||
}
|
||||
if (table_options_.block_size_deviation < 0 ||
|
||||
table_options_.block_size_deviation > 100) {
|
||||
table_options_.block_size_deviation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Status BlockBasedTableFactory::NewTableReader(
|
||||
@@ -43,12 +53,73 @@ Status BlockBasedTableFactory::NewTableReader(
|
||||
TableBuilder* BlockBasedTableFactory::NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const {
|
||||
|
||||
auto table_builder = new BlockBasedTableBuilder(
|
||||
options, table_options_, internal_comparator, file, compression_type);
|
||||
|
||||
return table_builder;
|
||||
}
|
||||
|
||||
std::string BlockBasedTableFactory::GetPrintableTableOptions() const {
|
||||
std::string ret;
|
||||
ret.reserve(20000);
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
snprintf(buffer, kBufferSize, " flush_block_policy_factory: %s (%p)\n",
|
||||
table_options_.flush_block_policy_factory->Name(),
|
||||
table_options_.flush_block_policy_factory.get());
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " cache_index_and_filter_blocks: %d\n",
|
||||
table_options_.cache_index_and_filter_blocks);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " index_type: %d\n",
|
||||
table_options_.index_type);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " hash_index_allow_collision: %d\n",
|
||||
table_options_.hash_index_allow_collision);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " checksum: %d\n",
|
||||
table_options_.checksum);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " no_block_cache: %d\n",
|
||||
table_options_.no_block_cache);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " block_cache: %p\n",
|
||||
table_options_.block_cache.get());
|
||||
ret.append(buffer);
|
||||
if (table_options_.block_cache) {
|
||||
snprintf(buffer, kBufferSize, " block_cache_size: %zd\n",
|
||||
table_options_.block_cache->GetCapacity());
|
||||
ret.append(buffer);
|
||||
}
|
||||
snprintf(buffer, kBufferSize, " block_cache_compressed: %p\n",
|
||||
table_options_.block_cache_compressed.get());
|
||||
ret.append(buffer);
|
||||
if (table_options_.block_cache_compressed) {
|
||||
snprintf(buffer, kBufferSize, " block_cache_compressed_size: %zd\n",
|
||||
table_options_.block_cache_compressed->GetCapacity());
|
||||
ret.append(buffer);
|
||||
}
|
||||
snprintf(buffer, kBufferSize, " block_size: %zd\n",
|
||||
table_options_.block_size);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " block_size_deviation: %d\n",
|
||||
table_options_.block_size_deviation);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " block_restart_interval: %d\n",
|
||||
table_options_.block_restart_interval);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " filter_policy: %s\n",
|
||||
table_options_.filter_policy == nullptr ?
|
||||
"nullptr" : table_options_.filter_policy->Name());
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " whole_key_filtering: %d\n",
|
||||
table_options_.whole_key_filtering);
|
||||
ret.append(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
TableFactory* NewBlockBasedTableFactory(
|
||||
const BlockBasedTableOptions& table_options) {
|
||||
return new BlockBasedTableFactory(table_options);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -43,6 +44,13 @@ class BlockBasedTableFactory : public TableFactory {
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const override;
|
||||
|
||||
// Sanitizes the specified DB Options.
|
||||
Status SanitizeDBOptions(const DBOptions* db_opts) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string GetPrintableTableOptions() const override;
|
||||
|
||||
private:
|
||||
BlockBasedTableOptions table_options_;
|
||||
};
|
||||
|
||||
@@ -135,11 +135,18 @@ class BlockBasedTable::IndexReader {
|
||||
virtual ~IndexReader() {}
|
||||
|
||||
// Create an iterator for index access.
|
||||
virtual Iterator* NewIterator() = 0;
|
||||
// An iter is passed in, if it is not null, update this one and return it
|
||||
// If it is null, create a new Iterator
|
||||
virtual Iterator* NewIterator(
|
||||
BlockIter* iter = nullptr, bool total_order_seek = true) = 0;
|
||||
|
||||
// The size of the index.
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
// Report an approximation of how much memory has been used other than memory
|
||||
// that was allocated in block cache.
|
||||
virtual size_t ApproximateMemoryUsage() const = 0;
|
||||
|
||||
protected:
|
||||
const Comparator* comparator_;
|
||||
};
|
||||
@@ -168,12 +175,18 @@ class BinarySearchIndexReader : public IndexReader {
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Iterator* NewIterator() override {
|
||||
return index_block_->NewIterator(comparator_);
|
||||
virtual Iterator* NewIterator(
|
||||
BlockIter* iter = nullptr, bool dont_care = true) override {
|
||||
return index_block_->NewIterator(comparator_, iter, true);
|
||||
}
|
||||
|
||||
virtual size_t size() const override { return index_block_->size(); }
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() const override {
|
||||
assert(index_block_);
|
||||
return index_block_->ApproximateMemoryUsage();
|
||||
}
|
||||
|
||||
private:
|
||||
BinarySearchIndexReader(const Comparator* comparator, Block* index_block)
|
||||
: IndexReader(comparator), index_block_(index_block) {
|
||||
@@ -284,12 +297,19 @@ class HashIndexReader : public IndexReader {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Iterator* NewIterator() override {
|
||||
return index_block_->NewIterator(comparator_);
|
||||
virtual Iterator* NewIterator(
|
||||
BlockIter* iter = nullptr, bool total_order_seek = true) override {
|
||||
return index_block_->NewIterator(comparator_, iter, total_order_seek);
|
||||
}
|
||||
|
||||
virtual size_t size() const override { return index_block_->size(); }
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() const override {
|
||||
assert(index_block_);
|
||||
return index_block_->ApproximateMemoryUsage() +
|
||||
prefixes_contents_.data.size();
|
||||
}
|
||||
|
||||
private:
|
||||
HashIndexReader(const Comparator* comparator, Block* index_block)
|
||||
: IndexReader(comparator),
|
||||
@@ -317,11 +337,16 @@ class HashIndexReader : public IndexReader {
|
||||
|
||||
struct BlockBasedTable::Rep {
|
||||
Rep(const EnvOptions& storage_options,
|
||||
const BlockBasedTableOptions& table_opt,
|
||||
const InternalKeyComparator& internal_comparator)
|
||||
: soptions(storage_options), internal_comparator(internal_comparator) {}
|
||||
: soptions(storage_options), table_options(table_opt),
|
||||
filter_policy(table_opt.filter_policy.get()),
|
||||
internal_comparator(internal_comparator) {}
|
||||
|
||||
Options options;
|
||||
const EnvOptions& soptions;
|
||||
const BlockBasedTableOptions& table_options;
|
||||
const FilterPolicy* const filter_policy;
|
||||
const InternalKeyComparator& internal_comparator;
|
||||
Status status;
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
@@ -381,13 +406,13 @@ void BlockBasedTable::SetupCacheKeyPrefix(Rep* rep) {
|
||||
assert(kMaxCacheKeyPrefixSize >= 10);
|
||||
rep->cache_key_prefix_size = 0;
|
||||
rep->compressed_cache_key_prefix_size = 0;
|
||||
if (rep->options.block_cache != nullptr) {
|
||||
GenerateCachePrefix(rep->options.block_cache.get(), rep->file.get(),
|
||||
if (rep->table_options.block_cache != nullptr) {
|
||||
GenerateCachePrefix(rep->table_options.block_cache.get(), rep->file.get(),
|
||||
&rep->cache_key_prefix[0],
|
||||
&rep->cache_key_prefix_size);
|
||||
}
|
||||
if (rep->options.block_cache_compressed != nullptr) {
|
||||
GenerateCachePrefix(rep->options.block_cache_compressed.get(),
|
||||
if (rep->table_options.block_cache_compressed != nullptr) {
|
||||
GenerateCachePrefix(rep->table_options.block_cache_compressed.get(),
|
||||
rep->file.get(), &rep->compressed_cache_key_prefix[0],
|
||||
&rep->compressed_cache_key_prefix_size);
|
||||
}
|
||||
@@ -435,7 +460,8 @@ Status BlockBasedTable::Open(const Options& options, const EnvOptions& soptions,
|
||||
|
||||
// We've successfully read the footer and the index block: we're
|
||||
// ready to serve requests.
|
||||
Rep* rep = new BlockBasedTable::Rep(soptions, internal_comparator);
|
||||
Rep* rep = new BlockBasedTable::Rep(
|
||||
soptions, table_options, internal_comparator);
|
||||
rep->options = options;
|
||||
rep->file = std::move(file);
|
||||
rep->footer = footer;
|
||||
@@ -476,7 +502,8 @@ Status BlockBasedTable::Open(const Options& options, const EnvOptions& soptions,
|
||||
}
|
||||
|
||||
// Will use block cache for index/filter blocks access?
|
||||
if (options.block_cache && table_options.cache_index_and_filter_blocks) {
|
||||
if (table_options.block_cache &&
|
||||
table_options.cache_index_and_filter_blocks) {
|
||||
// Hack: Call NewIndexIterator() to implicitly add index to the block_cache
|
||||
unique_ptr<Iterator> iter(new_table->NewIndexIterator(ReadOptions()));
|
||||
s = iter->status();
|
||||
@@ -484,7 +511,7 @@ Status BlockBasedTable::Open(const Options& options, const EnvOptions& soptions,
|
||||
if (s.ok()) {
|
||||
// Hack: Call GetFilter() to implicitly add filter to the block_cache
|
||||
auto filter_entry = new_table->GetFilter();
|
||||
filter_entry.Release(options.block_cache.get());
|
||||
filter_entry.Release(table_options.block_cache.get());
|
||||
}
|
||||
} else {
|
||||
// If we don't use block cache for index/filter blocks access, we'll
|
||||
@@ -498,9 +525,9 @@ Status BlockBasedTable::Open(const Options& options, const EnvOptions& soptions,
|
||||
rep->index_reader.reset(index_reader);
|
||||
|
||||
// Set filter block
|
||||
if (rep->options.filter_policy) {
|
||||
if (rep->filter_policy) {
|
||||
std::string key = kFilterBlockPrefix;
|
||||
key.append(rep->options.filter_policy->Name());
|
||||
key.append(rep->filter_policy->Name());
|
||||
BlockHandle handle;
|
||||
if (FindMetaBlock(meta_iter.get(), key, &handle).ok()) {
|
||||
rep->filter.reset(ReadFilter(handle, rep));
|
||||
@@ -542,6 +569,17 @@ std::shared_ptr<const TableProperties> BlockBasedTable::GetTableProperties()
|
||||
return rep_->table_properties;
|
||||
}
|
||||
|
||||
size_t BlockBasedTable::ApproximateMemoryUsage() const {
|
||||
size_t usage = 0;
|
||||
if (rep_->filter) {
|
||||
usage += rep_->filter->ApproximateMemoryUsage();
|
||||
}
|
||||
if (rep_->index_reader) {
|
||||
usage += rep_->index_reader->ApproximateMemoryUsage();
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
// Load the meta-block from the file. On success, return the loaded meta block
|
||||
// and its iterator.
|
||||
Status BlockBasedTable::ReadMetaBlock(
|
||||
@@ -717,7 +755,7 @@ FilterBlockReader* BlockBasedTable::ReadFilter(const BlockHandle& filter_handle,
|
||||
}
|
||||
|
||||
return new FilterBlockReader(
|
||||
rep->options, block.data, block.heap_allocated);
|
||||
rep->options, rep->table_options, block.data, block.heap_allocated);
|
||||
}
|
||||
|
||||
BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
@@ -727,13 +765,13 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
return {rep_->filter.get(), nullptr /* cache handle */};
|
||||
}
|
||||
|
||||
if (rep_->options.filter_policy == nullptr /* do not use filter at all */ ||
|
||||
rep_->options.block_cache == nullptr /* no block cache at all */) {
|
||||
Cache* block_cache = rep_->table_options.block_cache.get();
|
||||
if (rep_->filter_policy == nullptr /* do not use filter */ ||
|
||||
block_cache == nullptr /* no block cache at all */) {
|
||||
return {nullptr /* filter */, nullptr /* cache handle */};
|
||||
}
|
||||
|
||||
// Fetching from the cache
|
||||
Cache* block_cache = rep_->options.block_cache.get();
|
||||
char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
auto key = GetCacheKey(
|
||||
rep_->cache_key_prefix,
|
||||
@@ -762,7 +800,7 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
|
||||
if (s.ok()) {
|
||||
std::string filter_block_key = kFilterBlockPrefix;
|
||||
filter_block_key.append(rep_->options.filter_policy->Name());
|
||||
filter_block_key.append(rep_->filter_policy->Name());
|
||||
BlockHandle handle;
|
||||
if (FindMetaBlock(iter.get(), filter_block_key, &handle).ok()) {
|
||||
filter = ReadFilter(handle, rep_, &filter_size);
|
||||
@@ -779,14 +817,16 @@ BlockBasedTable::CachableEntry<FilterBlockReader> BlockBasedTable::GetFilter(
|
||||
return { filter, cache_handle };
|
||||
}
|
||||
|
||||
Iterator* BlockBasedTable::NewIndexIterator(const ReadOptions& read_options) {
|
||||
Iterator* BlockBasedTable::NewIndexIterator(const ReadOptions& read_options,
|
||||
BlockIter* input_iter) {
|
||||
// index reader has already been pre-populated.
|
||||
if (rep_->index_reader) {
|
||||
return rep_->index_reader->NewIterator();
|
||||
return rep_->index_reader->NewIterator(
|
||||
input_iter, read_options.total_order_seek);
|
||||
}
|
||||
|
||||
bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
Cache* block_cache = rep_->options.block_cache.get();
|
||||
Cache* block_cache = rep_->table_options.block_cache.get();
|
||||
char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
auto key = GetCacheKey(rep_->cache_key_prefix, rep_->cache_key_prefix_size,
|
||||
rep_->footer.index_handle(), cache_key);
|
||||
@@ -796,7 +836,12 @@ Iterator* BlockBasedTable::NewIndexIterator(const ReadOptions& read_options) {
|
||||
BLOCK_CACHE_INDEX_HIT, statistics);
|
||||
|
||||
if (cache_handle == nullptr && no_io) {
|
||||
return NewErrorIterator(Status::Incomplete("no blocking io"));
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(Status::Incomplete("no blocking io"));
|
||||
return input_iter;
|
||||
} else {
|
||||
return NewErrorIterator(Status::Incomplete("no blocking io"));
|
||||
}
|
||||
}
|
||||
|
||||
IndexReader* index_reader = nullptr;
|
||||
@@ -811,7 +856,12 @@ Iterator* BlockBasedTable::NewIndexIterator(const ReadOptions& read_options) {
|
||||
if (!s.ok()) {
|
||||
// make sure if something goes wrong, index_reader shall remain intact.
|
||||
assert(index_reader == nullptr);
|
||||
return NewErrorIterator(s);
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(s);
|
||||
return input_iter;
|
||||
} else {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
}
|
||||
|
||||
cache_handle = block_cache->Insert(key, index_reader, index_reader->size(),
|
||||
@@ -820,20 +870,23 @@ Iterator* BlockBasedTable::NewIndexIterator(const ReadOptions& read_options) {
|
||||
}
|
||||
|
||||
assert(cache_handle);
|
||||
auto iter = index_reader->NewIterator();
|
||||
auto* iter = index_reader->NewIterator(
|
||||
input_iter, read_options.total_order_seek);
|
||||
iter->RegisterCleanup(&ReleaseCachedEntry, block_cache, cache_handle);
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
// Convert an index iterator value (i.e., an encoded BlockHandle)
|
||||
// into an iterator over the contents of the corresponding block.
|
||||
// If input_iter is null, new a iterator
|
||||
// If input_iter is not null, update this iter and return it
|
||||
Iterator* BlockBasedTable::NewDataBlockIterator(Rep* rep,
|
||||
const ReadOptions& ro, const Slice& index_value) {
|
||||
const ReadOptions& ro, const Slice& index_value,
|
||||
BlockIter* input_iter) {
|
||||
const bool no_io = (ro.read_tier == kBlockCacheTier);
|
||||
Cache* block_cache = rep->options.block_cache.get();
|
||||
Cache* block_cache_compressed = rep->options.
|
||||
block_cache_compressed.get();
|
||||
Cache* block_cache = rep->table_options.block_cache.get();
|
||||
Cache* block_cache_compressed =
|
||||
rep->table_options.block_cache_compressed.get();
|
||||
CachableEntry<Block> block;
|
||||
|
||||
BlockHandle handle;
|
||||
@@ -843,7 +896,12 @@ Iterator* BlockBasedTable::NewDataBlockIterator(Rep* rep,
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(s);
|
||||
return input_iter;
|
||||
} else {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
}
|
||||
|
||||
// If either block cache is enabled, we'll try to read from it.
|
||||
@@ -889,7 +947,12 @@ Iterator* BlockBasedTable::NewDataBlockIterator(Rep* rep,
|
||||
if (block.value == nullptr) {
|
||||
if (no_io) {
|
||||
// Could not read from block_cache and can't do IO
|
||||
return NewErrorIterator(Status::Incomplete("no blocking io"));
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(Status::Incomplete("no blocking io"));
|
||||
return input_iter;
|
||||
} else {
|
||||
return NewErrorIterator(Status::Incomplete("no blocking io"));
|
||||
}
|
||||
}
|
||||
s = ReadBlockFromFile(rep->file.get(), rep->footer, ro, handle,
|
||||
&block.value, rep->options.env);
|
||||
@@ -897,15 +960,20 @@ Iterator* BlockBasedTable::NewDataBlockIterator(Rep* rep,
|
||||
|
||||
Iterator* iter;
|
||||
if (block.value != nullptr) {
|
||||
iter = block.value->NewIterator(&rep->internal_comparator);
|
||||
iter = block.value->NewIterator(&rep->internal_comparator, input_iter);
|
||||
if (block.cache_handle != nullptr) {
|
||||
iter->RegisterCleanup(&ReleaseCachedEntry, block_cache,
|
||||
block.cache_handle);
|
||||
block.cache_handle);
|
||||
} else {
|
||||
iter->RegisterCleanup(&DeleteHeldResource<Block>, block.value, nullptr);
|
||||
}
|
||||
} else {
|
||||
iter = NewErrorIterator(s);
|
||||
if (input_iter != nullptr) {
|
||||
input_iter->SetStatus(s);
|
||||
iter = input_iter;
|
||||
} else {
|
||||
iter = NewErrorIterator(s);
|
||||
}
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
@@ -923,6 +991,9 @@ class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
}
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_key) override {
|
||||
if (read_options_.total_order_seek) {
|
||||
return true;
|
||||
}
|
||||
return table_->PrefixMayMatch(internal_key);
|
||||
}
|
||||
|
||||
@@ -934,8 +1005,8 @@ class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
|
||||
// This will be broken if the user specifies an unusual implementation
|
||||
// of Options.comparator, or if the user specifies an unusual
|
||||
// definition of prefixes in Options.filter_policy. In particular, we
|
||||
// require the following three properties:
|
||||
// definition of prefixes in BlockBasedTableOptions.filter_policy.
|
||||
// In particular, we require the following three properties:
|
||||
//
|
||||
// 1) key.starts_with(prefix(key))
|
||||
// 2) Compare(prefix(key), key) <= 0.
|
||||
@@ -945,7 +1016,7 @@ class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
//
|
||||
// REQUIRES: this method shouldn't be called while the DB lock is held.
|
||||
bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
if (!rep_->options.filter_policy) {
|
||||
if (!rep_->filter_policy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -996,10 +1067,9 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
s = handle.DecodeFrom(&handle_value);
|
||||
assert(s.ok());
|
||||
auto filter_entry = GetFilter(true /* no io */);
|
||||
may_match =
|
||||
filter_entry.value == nullptr ||
|
||||
filter_entry.value->PrefixMayMatch(handle.offset(), internal_prefix);
|
||||
filter_entry.Release(rep_->options.block_cache.get());
|
||||
may_match = filter_entry.value == nullptr ||
|
||||
filter_entry.value->PrefixMayMatch(handle.offset(), prefix);
|
||||
filter_entry.Release(rep_->table_options.block_cache.get());
|
||||
}
|
||||
|
||||
Statistics* statistics = rep_->options.statistics.get();
|
||||
@@ -1023,18 +1093,19 @@ Status BlockBasedTable::Get(
|
||||
const Slice& v),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context)) {
|
||||
Status s;
|
||||
Iterator* iiter = NewIndexIterator(read_options);
|
||||
BlockIter iiter;
|
||||
NewIndexIterator(read_options, &iiter);
|
||||
|
||||
auto filter_entry = GetFilter(read_options.read_tier == kBlockCacheTier);
|
||||
FilterBlockReader* filter = filter_entry.value;
|
||||
bool done = false;
|
||||
for (iiter->Seek(key); iiter->Valid() && !done; iiter->Next()) {
|
||||
Slice handle_value = iiter->value();
|
||||
for (iiter.Seek(key); iiter.Valid() && !done; iiter.Next()) {
|
||||
Slice handle_value = iiter.value();
|
||||
|
||||
BlockHandle handle;
|
||||
bool may_not_exist_in_filter =
|
||||
filter != nullptr &&
|
||||
handle.DecodeFrom(&handle_value).ok() &&
|
||||
!filter->KeyMayMatch(handle.offset(), key);
|
||||
filter != nullptr && handle.DecodeFrom(&handle_value).ok() &&
|
||||
!filter->KeyMayMatch(handle.offset(), ExtractUserKey(key));
|
||||
|
||||
if (may_not_exist_in_filter) {
|
||||
// Not found
|
||||
@@ -1043,39 +1114,43 @@ Status BlockBasedTable::Get(
|
||||
RecordTick(rep_->options.statistics.get(), BLOOM_FILTER_USEFUL);
|
||||
break;
|
||||
} else {
|
||||
unique_ptr<Iterator> block_iter(
|
||||
NewDataBlockIterator(rep_, read_options, iiter->value()));
|
||||
BlockIter biter;
|
||||
NewDataBlockIterator(rep_, read_options, iiter.value(), &biter);
|
||||
|
||||
if (read_options.read_tier && block_iter->status().IsIncomplete()) {
|
||||
if (read_options.read_tier && biter.status().IsIncomplete()) {
|
||||
// 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_handler)(handle_context);
|
||||
break;
|
||||
}
|
||||
if (!biter.status().ok()) {
|
||||
s = biter.status();
|
||||
break;
|
||||
}
|
||||
|
||||
// Call the *saver function on each entry/block until it returns false
|
||||
for (block_iter->Seek(key); block_iter->Valid(); block_iter->Next()) {
|
||||
for (biter.Seek(key); biter.Valid(); biter.Next()) {
|
||||
ParsedInternalKey parsed_key;
|
||||
if (!ParseInternalKey(block_iter->key(), &parsed_key)) {
|
||||
if (!ParseInternalKey(biter.key(), &parsed_key)) {
|
||||
s = Status::Corruption(Slice());
|
||||
}
|
||||
|
||||
if (!(*result_handler)(handle_context, parsed_key,
|
||||
block_iter->value())) {
|
||||
biter.value())) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s = block_iter->status();
|
||||
s = biter.status();
|
||||
}
|
||||
}
|
||||
|
||||
filter_entry.Release(rep_->options.block_cache.get());
|
||||
filter_entry.Release(rep_->table_options.block_cache.get());
|
||||
if (s.ok()) {
|
||||
s = iiter->status();
|
||||
s = iiter.status();
|
||||
}
|
||||
delete iiter;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1090,7 +1165,7 @@ bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
|
||||
Slice input = iiter->value();
|
||||
Status s = handle.DecodeFrom(&input);
|
||||
assert(s.ok());
|
||||
Cache* block_cache = rep_->options.block_cache.get();
|
||||
Cache* block_cache = rep_->table_options.block_cache.get();
|
||||
assert(block_cache != nullptr);
|
||||
|
||||
char cache_key_storage[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace rocksdb {
|
||||
|
||||
class Block;
|
||||
class BlockIter;
|
||||
class BlockHandle;
|
||||
class Cache;
|
||||
class FilterBlockReader;
|
||||
@@ -95,6 +96,8 @@ class BlockBasedTable : public TableReader {
|
||||
|
||||
std::shared_ptr<const TableProperties> GetTableProperties() const override;
|
||||
|
||||
size_t ApproximateMemoryUsage() const override;
|
||||
|
||||
~BlockBasedTable();
|
||||
|
||||
bool TEST_filter_block_preloaded() const;
|
||||
@@ -111,8 +114,10 @@ class BlockBasedTable : public TableReader {
|
||||
bool compaction_optimized_;
|
||||
|
||||
class BlockEntryIteratorState;
|
||||
// input_iter: if it is not null, update this one and return it as Iterator
|
||||
static Iterator* NewDataBlockIterator(Rep* rep, const ReadOptions& ro,
|
||||
const Slice& index_value);
|
||||
const Slice& index_value,
|
||||
BlockIter* input_iter = nullptr);
|
||||
|
||||
// For the following two functions:
|
||||
// if `no_io == true`, we will not try to read filter/index from sst file
|
||||
@@ -120,6 +125,8 @@ class BlockBasedTable : public TableReader {
|
||||
CachableEntry<FilterBlockReader> GetFilter(bool no_io = false) const;
|
||||
|
||||
// Get the iterator from the index reader.
|
||||
// If input_iter is not set, return new Iterator
|
||||
// If input_iter is set, update it and return it as Iterator
|
||||
//
|
||||
// Note: ErrorIterator with Status::Incomplete shall be returned if all the
|
||||
// following conditions are met:
|
||||
@@ -127,7 +134,8 @@ class BlockBasedTable : public TableReader {
|
||||
// 2. index is not present in block cache.
|
||||
// 3. We disallowed any io to be performed, that is, read_options ==
|
||||
// kBlockCacheTier
|
||||
Iterator* NewIndexIterator(const ReadOptions& read_options);
|
||||
Iterator* NewIndexIterator(const ReadOptions& read_options,
|
||||
BlockIter* input_iter = nullptr);
|
||||
|
||||
// Read block cache from block caches (if set): block_cache and
|
||||
// block_cache_compressed.
|
||||
|
||||
@@ -41,10 +41,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
BlockBuilder::BlockBuilder(int block_restart_interval,
|
||||
const Comparator* comparator)
|
||||
BlockBuilder::BlockBuilder(int block_restart_interval)
|
||||
: block_restart_interval_(block_restart_interval),
|
||||
comparator_(comparator),
|
||||
restarts_(),
|
||||
counter_(0),
|
||||
finished_(false) {
|
||||
@@ -52,9 +50,6 @@ BlockBuilder::BlockBuilder(int block_restart_interval,
|
||||
restarts_.push_back(0); // First restart point is at offset 0
|
||||
}
|
||||
|
||||
BlockBuilder::BlockBuilder(const Options& options, const Comparator* comparator)
|
||||
: BlockBuilder(options.block_restart_interval, comparator) {}
|
||||
|
||||
void BlockBuilder::Reset() {
|
||||
buffer_.clear();
|
||||
restarts_.clear();
|
||||
@@ -99,8 +94,6 @@ void BlockBuilder::Add(const Slice& key, const Slice& value) {
|
||||
Slice last_key_piece(last_key_);
|
||||
assert(!finished_);
|
||||
assert(counter_ <= block_restart_interval_);
|
||||
assert(buffer_.empty() // No values yet?
|
||||
|| comparator_->Compare(key, last_key_piece) > 0);
|
||||
size_t shared = 0;
|
||||
if (counter_ < block_restart_interval_) {
|
||||
// See how much sharing to do with previous string
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct Options;
|
||||
class Comparator;
|
||||
|
||||
class BlockBuilder {
|
||||
public:
|
||||
BlockBuilder(int block_builder, const Comparator* comparator);
|
||||
explicit BlockBuilder(const Options& options, const Comparator* comparator);
|
||||
|
||||
BlockBuilder(const BlockBuilder&) = delete;
|
||||
void operator=(const BlockBuilder&) = delete;
|
||||
|
||||
explicit BlockBuilder(int block_restart_interval);
|
||||
|
||||
// Reset the contents as if the BlockBuilder was just constructed.
|
||||
void Reset();
|
||||
|
||||
@@ -49,17 +50,12 @@ class BlockBuilder {
|
||||
|
||||
private:
|
||||
const int block_restart_interval_;
|
||||
const Comparator* comparator_;
|
||||
|
||||
std::string buffer_; // Destination buffer
|
||||
std::vector<uint32_t> restarts_; // Restart points
|
||||
int counter_; // Number of entries emitted since restart
|
||||
bool finished_; // Has Finish() been called?
|
||||
std::string last_key_;
|
||||
|
||||
// No copying allowed
|
||||
BlockBuilder(const BlockBuilder&);
|
||||
void operator=(const BlockBuilder&);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
inline uint32_t Hash(const Slice& s) {
|
||||
return rocksdb::Hash(s.data(), s.size(), 0);
|
||||
}
|
||||
@@ -26,8 +24,6 @@ inline uint32_t PrefixToBucket(const Slice& prefix, uint32_t num_buckets) {
|
||||
return Hash(prefix) % num_buckets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The prefix block index is simply a bucket array, with each entry pointing to
|
||||
// the blocks that span the prefixes hashed to this bucket.
|
||||
//
|
||||
@@ -64,7 +60,6 @@ inline uint32_t EncodeIndex(uint32_t index) {
|
||||
return index | kBlockArrayMask;
|
||||
}
|
||||
|
||||
|
||||
// temporary storage for prefix information during index building
|
||||
struct PrefixRecord {
|
||||
Slice prefix;
|
||||
@@ -74,8 +69,6 @@ struct PrefixRecord {
|
||||
PrefixRecord* next;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlockPrefixIndex::Builder {
|
||||
public:
|
||||
explicit Builder(const SliceTransform* internal_prefix_extractor)
|
||||
|
||||
+3
-4
@@ -76,7 +76,7 @@ TEST(BlockTest, SimpleTest) {
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
BlockBuilder builder(options, ic.get());
|
||||
BlockBuilder builder(16);
|
||||
int num_records = 100000;
|
||||
|
||||
GenerateRandomKVs(&keys, &values, 0, num_records);
|
||||
@@ -132,8 +132,7 @@ BlockContents GetBlockContents(std::unique_ptr<BlockBuilder> *builder,
|
||||
const std::vector<std::string> &keys,
|
||||
const std::vector<std::string> &values,
|
||||
const int prefix_group_size = 1) {
|
||||
builder->reset(
|
||||
new BlockBuilder(1 /* restart interval */, BytewiseComparator()));
|
||||
builder->reset(new BlockBuilder(1 /* restart interval */));
|
||||
|
||||
// Add only half of the keys
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
@@ -172,7 +171,7 @@ void CheckBlockContents(BlockContents contents, const int max_key,
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> hash_iter(
|
||||
reader1.NewIterator(BytewiseComparator()));
|
||||
reader1.NewIterator(BytewiseComparator(), nullptr, false));
|
||||
|
||||
std::unique_ptr<Iterator> regular_iter(
|
||||
reader2.NewIterator(BytewiseComparator()));
|
||||
|
||||
+237
-167
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
#include "table/format.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -23,38 +25,37 @@
|
||||
namespace rocksdb {
|
||||
const std::string CuckooTablePropertyNames::kEmptyKey =
|
||||
"rocksdb.cuckoo.bucket.empty.key";
|
||||
const std::string CuckooTablePropertyNames::kNumHashTable =
|
||||
const std::string CuckooTablePropertyNames::kNumHashFunc =
|
||||
"rocksdb.cuckoo.hash.num";
|
||||
const std::string CuckooTablePropertyNames::kMaxNumBuckets =
|
||||
"rocksdb.cuckoo.bucket.maxnum";
|
||||
const std::string CuckooTablePropertyNames::kHashTableSize =
|
||||
"rocksdb.cuckoo.hash.size";
|
||||
const std::string CuckooTablePropertyNames::kValueLength =
|
||||
"rocksdb.cuckoo.value.length";
|
||||
const std::string CuckooTablePropertyNames::kIsLastLevel =
|
||||
"rocksdb.cuckoo.file.islastlevel";
|
||||
const std::string CuckooTablePropertyNames::kCuckooBlockSize =
|
||||
"rocksdb.cuckoo.hash.cuckooblocksize";
|
||||
|
||||
// Obtained by running echo rocksdb.table.cuckoo | sha1sum
|
||||
extern const uint64_t kCuckooTableMagicNumber = 0x926789d0c5f17873ull;
|
||||
|
||||
CuckooTableBuilder::CuckooTableBuilder(
|
||||
WritableFile* file, uint32_t fixed_key_length,
|
||||
uint32_t fixed_value_length, double hash_table_ratio,
|
||||
uint64_t file_size, uint32_t max_num_hash_table,
|
||||
uint32_t max_search_depth, bool is_last_level,
|
||||
uint64_t (*GetSliceHashPtr)(const Slice&, uint32_t, uint64_t))
|
||||
: num_hash_table_(2),
|
||||
WritableFile* file, double max_hash_table_ratio,
|
||||
uint32_t max_num_hash_table, uint32_t max_search_depth,
|
||||
const Comparator* user_comparator, uint32_t cuckoo_block_size,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t))
|
||||
: num_hash_func_(2),
|
||||
file_(file),
|
||||
value_length_(fixed_value_length),
|
||||
// 8 is the difference between sizes of user key and InternalKey.
|
||||
bucket_size_(fixed_key_length +
|
||||
fixed_value_length - (is_last_level ? 8 : 0)),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
max_num_buckets_(file_size / bucket_size_),
|
||||
max_num_hash_table_(max_num_hash_table),
|
||||
max_hash_table_ratio_(max_hash_table_ratio),
|
||||
max_num_hash_func_(max_num_hash_table),
|
||||
max_search_depth_(max_search_depth),
|
||||
is_last_level_file_(is_last_level),
|
||||
buckets_(max_num_buckets_),
|
||||
make_space_for_key_call_id_(0),
|
||||
GetSliceHash(GetSliceHashPtr) {
|
||||
cuckoo_block_size_(std::max(1U, cuckoo_block_size)),
|
||||
hash_table_size_(2),
|
||||
is_last_level_file_(false),
|
||||
has_seen_first_key_(false),
|
||||
ucomp_(user_comparator),
|
||||
get_slice_hash_(get_slice_hash),
|
||||
closed_(false) {
|
||||
properties_.num_entries = 0;
|
||||
// Data is in a huge block.
|
||||
properties_.num_data_blocks = 1;
|
||||
@@ -62,138 +63,173 @@ CuckooTableBuilder::CuckooTableBuilder(
|
||||
properties_.filter_size = 0;
|
||||
}
|
||||
|
||||
CuckooTableBuilder::~CuckooTableBuilder() {
|
||||
}
|
||||
|
||||
void CuckooTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
if (NumEntries() == max_num_buckets_) {
|
||||
status_ = Status::Corruption("Hash Table is full.");
|
||||
if (properties_.num_entries >= kMaxVectorIdx - 1) {
|
||||
status_ = Status::NotSupported("Number of keys in a file must be < 2^32-1");
|
||||
return;
|
||||
}
|
||||
uint64_t bucket_id;
|
||||
bool bucket_found = false;
|
||||
autovector<uint64_t> hash_vals;
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(key, &ikey)) {
|
||||
status_ = Status::Corruption("Unable to parse key into inernal key.");
|
||||
return;
|
||||
}
|
||||
Slice user_key = ikey.user_key;
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_table_; ++hash_cnt) {
|
||||
uint64_t hash_val = GetSliceHash(user_key, hash_cnt, max_num_buckets_);
|
||||
if (buckets_[hash_val].is_empty) {
|
||||
bucket_id = hash_val;
|
||||
bucket_found = true;
|
||||
break;
|
||||
} else {
|
||||
if (user_key.compare(
|
||||
is_last_level_file_ ? Slice(buckets_[hash_val].key)
|
||||
: ExtractUserKey(Slice(buckets_[hash_val].key))) == 0) {
|
||||
status_ = Status::Corruption("Same key is being inserted again.");
|
||||
return;
|
||||
}
|
||||
hash_vals.push_back(hash_val);
|
||||
}
|
||||
}
|
||||
while (!bucket_found && !MakeSpaceForKey(key, &bucket_id, hash_vals)) {
|
||||
// Rehash by increashing number of hash tables.
|
||||
if (num_hash_table_ >= max_num_hash_table_) {
|
||||
status_ = Status::Corruption("Too many collissions. Unable to hash.");
|
||||
return;
|
||||
}
|
||||
// We don't really need to rehash the entire table because old hashes are
|
||||
// still valid and we only increased the number of hash functions.
|
||||
uint64_t hash_val = GetSliceHash(user_key,
|
||||
num_hash_table_, max_num_buckets_);
|
||||
++num_hash_table_;
|
||||
if (buckets_[hash_val].is_empty) {
|
||||
bucket_found = true;
|
||||
bucket_id = hash_val;
|
||||
break;
|
||||
} else {
|
||||
hash_vals.push_back(hash_val);
|
||||
}
|
||||
// Determine if we can ignore the sequence number and value type from
|
||||
// internal keys by looking at sequence number from first key. We assume
|
||||
// that if first key has a zero sequence number, then all the remaining
|
||||
// keys will have zero seq. no.
|
||||
if (!has_seen_first_key_) {
|
||||
is_last_level_file_ = ikey.sequence == 0;
|
||||
has_seen_first_key_ = true;
|
||||
smallest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
|
||||
largest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
|
||||
}
|
||||
// Even if one sequence number is non-zero, then it is not last level.
|
||||
assert(!is_last_level_file_ || ikey.sequence == 0);
|
||||
if (is_last_level_file_) {
|
||||
buckets_[bucket_id].key.assign(user_key.data(), user_key.size());
|
||||
kvs_.emplace_back(std::make_pair(
|
||||
ikey.user_key.ToString(), value.ToString()));
|
||||
} else {
|
||||
buckets_[bucket_id].key.assign(key.data(), key.size());
|
||||
kvs_.emplace_back(std::make_pair(key.ToString(), value.ToString()));
|
||||
}
|
||||
buckets_[bucket_id].value.assign(value.data(), value.size());
|
||||
buckets_[bucket_id].is_empty = false;
|
||||
|
||||
properties_.num_entries++;
|
||||
|
||||
// We assume that the keys are inserted in sorted order. To identify an
|
||||
// unused key, which will be used in filling empty buckets in the table,
|
||||
// we try to find gaps between successive keys inserted. This is done by
|
||||
// maintaining the previous key and comparing it with next key.
|
||||
if (unused_user_key_.empty()) {
|
||||
if (prev_key_.empty()) {
|
||||
prev_key_ = user_key.ToString();
|
||||
return;
|
||||
}
|
||||
std::string new_user_key = prev_key_;
|
||||
new_user_key.back()++;
|
||||
// We ignore carry-overs and check that it is larger than previous key.
|
||||
if ((new_user_key > prev_key_) &&
|
||||
(new_user_key < user_key.ToString())) {
|
||||
unused_user_key_ = new_user_key;
|
||||
} else {
|
||||
prev_key_ = user_key.ToString();
|
||||
}
|
||||
// In order to fill the empty buckets in the hash table, we identify a
|
||||
// key which is not used so far (unused_user_key). We determine this by
|
||||
// maintaining smallest and largest keys inserted so far in bytewise order
|
||||
// and use them to find a key outside this range in Finish() operation.
|
||||
// Note that this strategy is independent of user comparator used here.
|
||||
if (ikey.user_key.compare(smallest_user_key_) < 0) {
|
||||
smallest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
|
||||
} else if (ikey.user_key.compare(largest_user_key_) > 0) {
|
||||
largest_user_key_.assign(ikey.user_key.data(), ikey.user_key.size());
|
||||
}
|
||||
if (hash_table_size_ < kvs_.size() / max_hash_table_ratio_) {
|
||||
hash_table_size_ *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
Status CuckooTableBuilder::status() const { return status_; }
|
||||
Status CuckooTableBuilder::MakeHashTable(std::vector<CuckooBucket>* buckets) {
|
||||
uint64_t hash_table_size_minus_one = hash_table_size_ - 1;
|
||||
buckets->resize(hash_table_size_minus_one + cuckoo_block_size_);
|
||||
uint64_t make_space_for_key_call_id = 0;
|
||||
for (uint32_t vector_idx = 0; vector_idx < kvs_.size(); vector_idx++) {
|
||||
uint64_t bucket_id;
|
||||
bool bucket_found = false;
|
||||
autovector<uint64_t> hash_vals;
|
||||
Slice user_key = is_last_level_file_ ? kvs_[vector_idx].first :
|
||||
ExtractUserKey(kvs_[vector_idx].first);
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_ && !bucket_found;
|
||||
++hash_cnt) {
|
||||
uint64_t hash_val = CuckooHash(user_key, hash_cnt,
|
||||
hash_table_size_minus_one, get_slice_hash_);
|
||||
// If there is a collision, check next cuckoo_block_size_ locations for
|
||||
// empty locations. While checking, if we reach end of the hash table,
|
||||
// stop searching and proceed for next hash function.
|
||||
for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
|
||||
++block_idx, ++hash_val) {
|
||||
if ((*buckets)[hash_val].vector_idx == kMaxVectorIdx) {
|
||||
bucket_id = hash_val;
|
||||
bucket_found = true;
|
||||
break;
|
||||
} else {
|
||||
if (ucomp_->Compare(user_key, is_last_level_file_
|
||||
? Slice(kvs_[(*buckets)[hash_val].vector_idx].first)
|
||||
: ExtractUserKey(
|
||||
kvs_[(*buckets)[hash_val].vector_idx].first)) == 0) {
|
||||
return Status::NotSupported("Same key is being inserted again.");
|
||||
}
|
||||
hash_vals.push_back(hash_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!bucket_found && !MakeSpaceForKey(hash_vals,
|
||||
++make_space_for_key_call_id, buckets, &bucket_id)) {
|
||||
// Rehash by increashing number of hash tables.
|
||||
if (num_hash_func_ >= max_num_hash_func_) {
|
||||
return Status::NotSupported("Too many collisions. Unable to hash.");
|
||||
}
|
||||
// We don't really need to rehash the entire table because old hashes are
|
||||
// still valid and we only increased the number of hash functions.
|
||||
uint64_t hash_val = CuckooHash(user_key, num_hash_func_,
|
||||
hash_table_size_minus_one, get_slice_hash_);
|
||||
++num_hash_func_;
|
||||
for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
|
||||
++block_idx, ++hash_val) {
|
||||
if ((*buckets)[hash_val].vector_idx == kMaxVectorIdx) {
|
||||
bucket_found = true;
|
||||
bucket_id = hash_val;
|
||||
break;
|
||||
} else {
|
||||
hash_vals.push_back(hash_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
(*buckets)[bucket_id].vector_idx = vector_idx;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CuckooTableBuilder::Finish() {
|
||||
assert(!closed_);
|
||||
closed_ = true;
|
||||
|
||||
if (unused_user_key_.empty()) {
|
||||
if (prev_key_.empty()) {
|
||||
return Status::Corruption("Unable to find unused key");
|
||||
std::vector<CuckooBucket> buckets;
|
||||
Status s;
|
||||
std::string unused_bucket;
|
||||
if (!kvs_.empty()) {
|
||||
s = MakeHashTable(&buckets);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// Try to find the key next to prev_key_ by handling carryovers.
|
||||
std::string new_user_key = prev_key_;
|
||||
int curr_pos = new_user_key.size() - 1;
|
||||
// Determine unused_user_key to fill empty buckets.
|
||||
std::string unused_user_key = smallest_user_key_;
|
||||
int curr_pos = unused_user_key.size() - 1;
|
||||
while (curr_pos >= 0) {
|
||||
++new_user_key[curr_pos];
|
||||
if (new_user_key > prev_key_) {
|
||||
unused_user_key_ = new_user_key;
|
||||
--unused_user_key[curr_pos];
|
||||
if (Slice(unused_user_key).compare(smallest_user_key_) < 0) {
|
||||
break;
|
||||
}
|
||||
--curr_pos;
|
||||
}
|
||||
if (curr_pos < 0) {
|
||||
// Try using the largest key to identify an unused key.
|
||||
unused_user_key = largest_user_key_;
|
||||
curr_pos = unused_user_key.size() - 1;
|
||||
while (curr_pos >= 0) {
|
||||
++unused_user_key[curr_pos];
|
||||
if (Slice(unused_user_key).compare(largest_user_key_) > 0) {
|
||||
break;
|
||||
}
|
||||
--curr_pos;
|
||||
}
|
||||
}
|
||||
if (curr_pos < 0) {
|
||||
return Status::Corruption("Unable to find unused key");
|
||||
}
|
||||
if (is_last_level_file_) {
|
||||
unused_bucket = unused_user_key;
|
||||
} else {
|
||||
ParsedInternalKey ikey(unused_user_key, 0, kTypeValue);
|
||||
AppendInternalKey(&unused_bucket, ikey);
|
||||
}
|
||||
}
|
||||
std::string unused_bucket;
|
||||
if (is_last_level_file_) {
|
||||
unused_bucket = unused_user_key_;
|
||||
} else {
|
||||
ParsedInternalKey ikey(unused_user_key_, 0, kTypeValue);
|
||||
AppendInternalKey(&unused_bucket, ikey);
|
||||
}
|
||||
properties_.num_entries = kvs_.size();
|
||||
properties_.fixed_key_len = unused_bucket.size();
|
||||
uint32_t value_length = kvs_.empty() ? 0 : kvs_[0].second.size();
|
||||
uint32_t bucket_size = value_length + properties_.fixed_key_len;
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kValueLength].assign(
|
||||
reinterpret_cast<const char*>(&value_length_), sizeof(value_length_));
|
||||
reinterpret_cast<const char*>(&value_length), sizeof(value_length));
|
||||
|
||||
unused_bucket.resize(bucket_size_, 'a');
|
||||
unused_bucket.resize(bucket_size, 'a');
|
||||
// Write the table.
|
||||
uint32_t num_added = 0;
|
||||
for (auto& bucket : buckets_) {
|
||||
Status s;
|
||||
if (bucket.is_empty) {
|
||||
for (auto& bucket : buckets) {
|
||||
if (bucket.vector_idx == kMaxVectorIdx) {
|
||||
s = file_->Append(Slice(unused_bucket));
|
||||
} else {
|
||||
++num_added;
|
||||
s = file_->Append(Slice(bucket.key));
|
||||
s = file_->Append(kvs_[bucket.vector_idx].first);
|
||||
if (s.ok()) {
|
||||
s = file_->Append(Slice(bucket.value));
|
||||
s = file_->Append(kvs_[bucket.vector_idx].second);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
@@ -201,22 +237,31 @@ Status CuckooTableBuilder::Finish() {
|
||||
}
|
||||
}
|
||||
assert(num_added == NumEntries());
|
||||
properties_.raw_key_size = num_added * properties_.fixed_key_len;
|
||||
properties_.raw_value_size = num_added * value_length;
|
||||
|
||||
uint64_t offset = buckets_.size() * bucket_size_;
|
||||
uint64_t offset = buckets.size() * bucket_size;
|
||||
properties_.data_size = offset;
|
||||
unused_bucket.resize(properties_.fixed_key_len);
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kEmptyKey] = unused_bucket;
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kNumHashTable].assign(
|
||||
reinterpret_cast<char*>(&num_hash_table_), sizeof(num_hash_table_));
|
||||
CuckooTablePropertyNames::kNumHashFunc].assign(
|
||||
reinterpret_cast<char*>(&num_hash_func_), sizeof(num_hash_func_));
|
||||
|
||||
uint64_t hash_table_size = buckets.size() - cuckoo_block_size_ + 1;
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kMaxNumBuckets].assign(
|
||||
reinterpret_cast<const char*>(&max_num_buckets_),
|
||||
sizeof(max_num_buckets_));
|
||||
CuckooTablePropertyNames::kHashTableSize].assign(
|
||||
reinterpret_cast<const char*>(&hash_table_size),
|
||||
sizeof(hash_table_size));
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kIsLastLevel].assign(
|
||||
reinterpret_cast<const char*>(&is_last_level_file_),
|
||||
sizeof(is_last_level_file_));
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kCuckooBlockSize].assign(
|
||||
reinterpret_cast<const char*>(&cuckoo_block_size_),
|
||||
sizeof(cuckoo_block_size_));
|
||||
|
||||
// Write meta blocks.
|
||||
MetaIndexBuilder meta_index_builder;
|
||||
@@ -228,7 +273,7 @@ Status CuckooTableBuilder::Finish() {
|
||||
BlockHandle property_block_handle;
|
||||
property_block_handle.set_offset(offset);
|
||||
property_block_handle.set_size(property_block.size());
|
||||
Status s = file_->Append(property_block);
|
||||
s = file_->Append(property_block);
|
||||
offset += property_block.size();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -260,22 +305,43 @@ void CuckooTableBuilder::Abandon() {
|
||||
}
|
||||
|
||||
uint64_t CuckooTableBuilder::NumEntries() const {
|
||||
return properties_.num_entries;
|
||||
return kvs_.size();
|
||||
}
|
||||
|
||||
uint64_t CuckooTableBuilder::FileSize() const {
|
||||
if (closed_) {
|
||||
return file_->GetFileSize();
|
||||
} else {
|
||||
// This is not the actual size of the file as we need to account for
|
||||
// hash table ratio. This returns the size of filled buckets in the table
|
||||
// scaled up by a factor of 1/hash table ratio.
|
||||
return (properties_.num_entries * bucket_size_) / hash_table_ratio_;
|
||||
} else if (properties_.num_entries == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Account for buckets being a power of two.
|
||||
// As elements are added, file size remains constant for a while and doubles
|
||||
// its size. Since compaction algorithm stops adding elements only after it
|
||||
// exceeds the file limit, we account for the extra element being added here.
|
||||
uint64_t expected_hash_table_size = hash_table_size_;
|
||||
if (expected_hash_table_size < (kvs_.size() + 1) / max_hash_table_ratio_) {
|
||||
expected_hash_table_size *= 2;
|
||||
}
|
||||
return (kvs_[0].first.size() + kvs_[0].second.size()) *
|
||||
expected_hash_table_size;
|
||||
}
|
||||
|
||||
bool CuckooTableBuilder::MakeSpaceForKey(const Slice& key,
|
||||
uint64_t *bucket_id, autovector<uint64_t> hash_vals) {
|
||||
// This method is invoked when there is no place to insert the target key.
|
||||
// It searches for a set of elements that can be moved to accommodate target
|
||||
// key. The search is a BFS graph traversal with first level (hash_vals)
|
||||
// being all the buckets target key could go to.
|
||||
// Then, from each node (curr_node), we find all the buckets that curr_node
|
||||
// could go to. They form the children of curr_node in the tree.
|
||||
// We continue the traversal until we find an empty bucket, in which case, we
|
||||
// move all elements along the path from first level to this empty bucket, to
|
||||
// make space for target key which is inserted at first level (*bucket_id).
|
||||
// If tree depth exceedes max depth, we return false indicating failure.
|
||||
bool CuckooTableBuilder::MakeSpaceForKey(
|
||||
const autovector<uint64_t>& hash_vals,
|
||||
const uint64_t make_space_for_key_call_id,
|
||||
std::vector<CuckooBucket>* buckets,
|
||||
uint64_t* bucket_id) {
|
||||
struct CuckooNode {
|
||||
uint64_t bucket_id;
|
||||
uint32_t depth;
|
||||
@@ -288,66 +354,70 @@ bool CuckooTableBuilder::MakeSpaceForKey(const Slice& key,
|
||||
std::vector<CuckooNode> tree;
|
||||
// We want to identify already visited buckets in the current method call so
|
||||
// that we don't add same buckets again for exploration in the tree.
|
||||
// We do this by maintaining a count of current method call, which acts as a
|
||||
// unique id for this invocation of the method. We store this number into
|
||||
// the nodes that we explore in current method call.
|
||||
// We do this by maintaining a count of current method call in
|
||||
// make_space_for_key_call_id, which acts as a unique id for this invocation
|
||||
// of the method. We store this number into the nodes that we explore in
|
||||
// current method call.
|
||||
// It is unlikely for the increment operation to overflow because the maximum
|
||||
// number of times this will be called is <= max_num_hash_table_ +
|
||||
// max_num_buckets_.
|
||||
++make_space_for_key_call_id_;
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_table_; ++hash_cnt) {
|
||||
// no. of times this will be called is <= max_num_hash_func_ + kvs_.size().
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) {
|
||||
uint64_t bucket_id = hash_vals[hash_cnt];
|
||||
buckets_[bucket_id].make_space_for_key_call_id =
|
||||
make_space_for_key_call_id_;
|
||||
(*buckets)[bucket_id].make_space_for_key_call_id =
|
||||
make_space_for_key_call_id;
|
||||
tree.push_back(CuckooNode(bucket_id, 0, 0));
|
||||
}
|
||||
uint64_t hash_table_size_minus_one = hash_table_size_ - 1;
|
||||
bool null_found = false;
|
||||
uint32_t curr_pos = 0;
|
||||
while (!null_found && curr_pos < tree.size()) {
|
||||
CuckooNode& curr_node = tree[curr_pos];
|
||||
if (curr_node.depth >= max_search_depth_) {
|
||||
uint32_t curr_depth = curr_node.depth;
|
||||
if (curr_depth >= max_search_depth_) {
|
||||
break;
|
||||
}
|
||||
CuckooBucket& curr_bucket = buckets_[curr_node.bucket_id];
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_table_; ++hash_cnt) {
|
||||
uint64_t child_bucket_id = GetSliceHash(
|
||||
is_last_level_file_ ? curr_bucket.key
|
||||
: ExtractUserKey(Slice(curr_bucket.key)),
|
||||
hash_cnt, max_num_buckets_);
|
||||
if (buckets_[child_bucket_id].make_space_for_key_call_id ==
|
||||
make_space_for_key_call_id_) {
|
||||
continue;
|
||||
}
|
||||
buckets_[child_bucket_id].make_space_for_key_call_id =
|
||||
make_space_for_key_call_id_;
|
||||
tree.push_back(CuckooNode(child_bucket_id, curr_node.depth + 1,
|
||||
curr_pos));
|
||||
if (buckets_[child_bucket_id].is_empty) {
|
||||
null_found = true;
|
||||
break;
|
||||
CuckooBucket& curr_bucket = (*buckets)[curr_node.bucket_id];
|
||||
for (uint32_t hash_cnt = 0;
|
||||
hash_cnt < num_hash_func_ && !null_found; ++hash_cnt) {
|
||||
uint64_t child_bucket_id = CuckooHash(
|
||||
(is_last_level_file_ ? kvs_[curr_bucket.vector_idx].first :
|
||||
ExtractUserKey(Slice(kvs_[curr_bucket.vector_idx].first))),
|
||||
hash_cnt, hash_table_size_minus_one, get_slice_hash_);
|
||||
// Iterate inside Cuckoo Block.
|
||||
for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
|
||||
++block_idx, ++child_bucket_id) {
|
||||
if ((*buckets)[child_bucket_id].make_space_for_key_call_id ==
|
||||
make_space_for_key_call_id) {
|
||||
continue;
|
||||
}
|
||||
(*buckets)[child_bucket_id].make_space_for_key_call_id =
|
||||
make_space_for_key_call_id;
|
||||
tree.push_back(CuckooNode(child_bucket_id, curr_depth + 1,
|
||||
curr_pos));
|
||||
if ((*buckets)[child_bucket_id].vector_idx == kMaxVectorIdx) {
|
||||
null_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
++curr_pos;
|
||||
}
|
||||
|
||||
if (null_found) {
|
||||
// There is an empty node in tree.back(). Now, traverse the path from this
|
||||
// empty node to top of the tree and at every node in the path, replace
|
||||
// child with the parent. Stop when first level is reached in the tree
|
||||
// (happens when 0 <= bucket_to_replace_pos < num_hash_func_) and return
|
||||
// this location in first level for target key to be inserted.
|
||||
uint32_t bucket_to_replace_pos = tree.size()-1;
|
||||
while (bucket_to_replace_pos >= 0) {
|
||||
while (bucket_to_replace_pos >= num_hash_func_) {
|
||||
CuckooNode& curr_node = tree[bucket_to_replace_pos];
|
||||
if (bucket_to_replace_pos >= num_hash_table_) {
|
||||
buckets_[curr_node.bucket_id] =
|
||||
buckets_[tree[curr_node.parent_pos].bucket_id];
|
||||
bucket_to_replace_pos = curr_node.parent_pos;
|
||||
} else {
|
||||
*bucket_id = curr_node.bucket_id;
|
||||
return true;
|
||||
}
|
||||
(*buckets)[curr_node.bucket_id] =
|
||||
(*buckets)[tree[curr_node.parent_pos].bucket_id];
|
||||
bucket_to_replace_pos = curr_node.parent_pos;
|
||||
}
|
||||
assert(false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
*bucket_id = tree[bucket_to_replace_pos].bucket_id;
|
||||
}
|
||||
return null_found;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <stdint.h>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/table_builder.h"
|
||||
@@ -19,14 +21,13 @@ namespace rocksdb {
|
||||
class CuckooTableBuilder: public TableBuilder {
|
||||
public:
|
||||
CuckooTableBuilder(
|
||||
WritableFile* file, uint32_t fixed_key_length,
|
||||
uint32_t fixed_value_length, double hash_table_ratio,
|
||||
uint64_t file_size, uint32_t max_num_hash_table,
|
||||
uint32_t max_search_depth, bool is_last_level,
|
||||
uint64_t (*GetSliceHash)(const Slice&, uint32_t, uint64_t));
|
||||
WritableFile* file, double max_hash_table_ratio,
|
||||
uint32_t max_num_hash_func, uint32_t max_search_depth,
|
||||
const Comparator* user_comparator, uint32_t cuckoo_block_size,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t));
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~CuckooTableBuilder();
|
||||
~CuckooTableBuilder() {}
|
||||
|
||||
// Add key,value to the table being constructed.
|
||||
// REQUIRES: key is after any previously added key according to comparator.
|
||||
@@ -34,7 +35,7 @@ class CuckooTableBuilder: public TableBuilder {
|
||||
void Add(const Slice& key, const Slice& value) override;
|
||||
|
||||
// Return non-ok iff some error has been detected.
|
||||
Status status() const override;
|
||||
Status status() const override { return status_; }
|
||||
|
||||
// Finish building the table. Stops using the file passed to the
|
||||
// constructor after this function returns.
|
||||
@@ -57,35 +58,41 @@ class CuckooTableBuilder: public TableBuilder {
|
||||
|
||||
private:
|
||||
struct CuckooBucket {
|
||||
CuckooBucket(): is_empty(true), make_space_for_key_call_id(0) {}
|
||||
std::string key;
|
||||
std::string value;
|
||||
bool is_empty;
|
||||
uint64_t make_space_for_key_call_id;
|
||||
CuckooBucket()
|
||||
: vector_idx(kMaxVectorIdx), make_space_for_key_call_id(0) {}
|
||||
uint32_t vector_idx;
|
||||
// This number will not exceed kvs_.size() + max_num_hash_func_.
|
||||
// We assume number of items is <= 2^32.
|
||||
uint32_t make_space_for_key_call_id;
|
||||
};
|
||||
static const uint32_t kMaxVectorIdx = std::numeric_limits<int32_t>::max();
|
||||
|
||||
bool MakeSpaceForKey(const Slice& key, uint64_t* bucket_id,
|
||||
autovector<uint64_t> hash_vals);
|
||||
bool MakeSpaceForKey(
|
||||
const autovector<uint64_t>& hash_vals,
|
||||
const uint64_t call_id,
|
||||
std::vector<CuckooBucket>* buckets,
|
||||
uint64_t* bucket_id);
|
||||
Status MakeHashTable(std::vector<CuckooBucket>* buckets);
|
||||
|
||||
uint32_t num_hash_table_;
|
||||
uint32_t num_hash_func_;
|
||||
WritableFile* file_;
|
||||
const uint32_t value_length_;
|
||||
const uint32_t bucket_size_;
|
||||
const double hash_table_ratio_;
|
||||
const uint64_t max_num_buckets_;
|
||||
const uint32_t max_num_hash_table_;
|
||||
const double max_hash_table_ratio_;
|
||||
const uint32_t max_num_hash_func_;
|
||||
const uint32_t max_search_depth_;
|
||||
const bool is_last_level_file_;
|
||||
const uint32_t cuckoo_block_size_;
|
||||
uint64_t hash_table_size_;
|
||||
bool is_last_level_file_;
|
||||
Status status_;
|
||||
std::vector<CuckooBucket> buckets_;
|
||||
std::vector<std::pair<std::string, std::string>> kvs_;
|
||||
TableProperties properties_;
|
||||
uint64_t make_space_for_key_call_id_;
|
||||
uint64_t (*GetSliceHash)(const Slice& s, uint32_t index,
|
||||
bool has_seen_first_key_;
|
||||
const Comparator* ucomp_;
|
||||
uint64_t (*get_slice_hash_)(const Slice& s, uint32_t index,
|
||||
uint64_t max_num_buckets);
|
||||
std::string unused_user_key_ = "";
|
||||
std::string prev_key_;
|
||||
std::string largest_user_key_ = "";
|
||||
std::string smallest_user_key_ = "";
|
||||
|
||||
bool closed_ = false; // Either Finish() or Abandon() has been called.
|
||||
bool closed_; // Either Finish() or Abandon() has been called.
|
||||
|
||||
// No copying allowed
|
||||
CuckooTableBuilder(const CuckooTableBuilder&) = delete;
|
||||
|
||||
+379
-379
@@ -10,26 +10,15 @@
|
||||
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/cuckoo_table_builder.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
namespace {
|
||||
std::unordered_map<std::string, std::vector<uint64_t>> hash_map;
|
||||
|
||||
void AddHashLookups(const std::string& s, uint64_t bucket_id,
|
||||
uint32_t num_hash_fun) {
|
||||
std::vector<uint64_t> v;
|
||||
for (uint32_t i = 0; i < num_hash_fun; i++) {
|
||||
v.push_back(bucket_id + i);
|
||||
}
|
||||
hash_map[s] = v;
|
||||
}
|
||||
|
||||
uint64_t GetSliceHash(const Slice& s, uint32_t index,
|
||||
uint64_t max_num_buckets) {
|
||||
return hash_map[s.ToString()][index];
|
||||
@@ -40,11 +29,17 @@ class CuckooBuilderTest {
|
||||
public:
|
||||
CuckooBuilderTest() {
|
||||
env_ = Env::Default();
|
||||
Options options;
|
||||
options.allow_mmap_reads = true;
|
||||
env_options_ = EnvOptions(options);
|
||||
}
|
||||
|
||||
void CheckFileContents(const std::string& expected_data,
|
||||
std::string expected_unused_bucket, uint64_t expected_max_buckets,
|
||||
uint32_t expected_num_hash_fun, bool expected_is_last_level) {
|
||||
void CheckFileContents(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values,
|
||||
const std::vector<uint64_t>& expected_locations,
|
||||
std::string expected_unused_bucket, uint64_t expected_table_size,
|
||||
uint32_t expected_num_hash_func, bool expected_is_last_level,
|
||||
uint32_t expected_cuckoo_block_size = 1) {
|
||||
// Read file
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &read_file, env_options_));
|
||||
@@ -55,436 +50,441 @@ class CuckooBuilderTest {
|
||||
TableProperties* props = nullptr;
|
||||
ASSERT_OK(ReadTableProperties(read_file.get(), read_file_size,
|
||||
kCuckooTableMagicNumber, env_, nullptr, &props));
|
||||
ASSERT_EQ(props->num_entries, num_items);
|
||||
ASSERT_EQ(props->fixed_key_len, key_length);
|
||||
ASSERT_EQ(props->num_entries, keys.size());
|
||||
ASSERT_EQ(props->fixed_key_len, keys.empty() ? 0 : keys[0].size());
|
||||
ASSERT_EQ(props->data_size, expected_unused_bucket.size() *
|
||||
(expected_table_size + expected_cuckoo_block_size - 1));
|
||||
ASSERT_EQ(props->raw_key_size, keys.size()*props->fixed_key_len);
|
||||
|
||||
// Check unused bucket.
|
||||
std::string unused_key = props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kEmptyKey];
|
||||
ASSERT_EQ(expected_unused_bucket.substr(0, key_length), unused_key);
|
||||
ASSERT_EQ(expected_unused_bucket.substr(0,
|
||||
props->fixed_key_len), unused_key);
|
||||
|
||||
uint32_t value_len_found =
|
||||
*reinterpret_cast<const uint32_t*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kValueLength].data());
|
||||
ASSERT_EQ(value_length, value_len_found);
|
||||
const uint64_t max_buckets =
|
||||
ASSERT_EQ(values.empty() ? 0 : values[0].size(), value_len_found);
|
||||
ASSERT_EQ(props->raw_value_size, values.size()*value_len_found);
|
||||
const uint64_t table_size =
|
||||
*reinterpret_cast<const uint64_t*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kMaxNumBuckets].data());
|
||||
ASSERT_EQ(expected_max_buckets, max_buckets);
|
||||
const uint32_t num_hash_fun_found =
|
||||
CuckooTablePropertyNames::kHashTableSize].data());
|
||||
ASSERT_EQ(expected_table_size, table_size);
|
||||
const uint32_t num_hash_func_found =
|
||||
*reinterpret_cast<const uint32_t*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kNumHashTable].data());
|
||||
ASSERT_EQ(expected_num_hash_fun, num_hash_fun_found);
|
||||
CuckooTablePropertyNames::kNumHashFunc].data());
|
||||
ASSERT_EQ(expected_num_hash_func, num_hash_func_found);
|
||||
const uint32_t cuckoo_block_size =
|
||||
*reinterpret_cast<const uint32_t*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kCuckooBlockSize].data());
|
||||
ASSERT_EQ(expected_cuckoo_block_size, cuckoo_block_size);
|
||||
const bool is_last_level_found =
|
||||
*reinterpret_cast<const bool*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kIsLastLevel].data());
|
||||
ASSERT_EQ(expected_is_last_level, is_last_level_found);
|
||||
delete props;
|
||||
|
||||
// Check contents of the bucket.
|
||||
std::string read_data;
|
||||
read_data.resize(expected_data.size());
|
||||
Slice read_slice;
|
||||
ASSERT_OK(read_file->Read(0, expected_data.size(),
|
||||
&read_slice, &read_data[0]));
|
||||
ASSERT_EQ(expected_data, read_data);
|
||||
std::vector<bool> keys_found(keys.size(), false);
|
||||
uint32_t bucket_size = expected_unused_bucket.size();
|
||||
for (uint32_t i = 0; i < table_size + cuckoo_block_size - 1; ++i) {
|
||||
Slice read_slice;
|
||||
ASSERT_OK(read_file->Read(i*bucket_size, bucket_size,
|
||||
&read_slice, nullptr));
|
||||
uint32_t key_idx = std::find(expected_locations.begin(),
|
||||
expected_locations.end(), i) - expected_locations.begin();
|
||||
if (key_idx == keys.size()) {
|
||||
// i is not one of the expected locaitons. Empty bucket.
|
||||
ASSERT_EQ(read_slice.compare(expected_unused_bucket), 0);
|
||||
} else {
|
||||
keys_found[key_idx] = true;
|
||||
ASSERT_EQ(read_slice.compare(keys[key_idx] + values[key_idx]), 0);
|
||||
}
|
||||
}
|
||||
for (auto key_found : keys_found) {
|
||||
// Check that all keys were found.
|
||||
ASSERT_TRUE(key_found);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetInternalKey(Slice user_key, bool zero_seqno) {
|
||||
IterKey ikey;
|
||||
ikey.SetInternalKey(user_key, zero_seqno ? 0 : 1000, kTypeValue);
|
||||
return ikey.GetKey().ToString();
|
||||
}
|
||||
|
||||
uint64_t NextPowOf2(uint64_t num) {
|
||||
uint64_t n = 2;
|
||||
while (n <= num) {
|
||||
n *= 2;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
const EnvOptions env_options_;
|
||||
EnvOptions env_options_;
|
||||
std::string fname;
|
||||
uint64_t file_size = 100000;
|
||||
uint32_t num_items = 20;
|
||||
uint32_t num_hash_fun = 64;
|
||||
double hash_table_ratio = 0.9;
|
||||
uint32_t ikey_length;
|
||||
uint32_t user_key_length;
|
||||
uint32_t key_length;
|
||||
uint32_t value_length;
|
||||
uint32_t bucket_length;
|
||||
const double kHashTableRatio = 0.9;
|
||||
};
|
||||
|
||||
TEST(CuckooBuilderTest, NoCollision) {
|
||||
hash_map.clear();
|
||||
uint32_t expected_num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
uint64_t bucket_ids = 0;
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
AddHashLookups(user_keys[i], bucket_ids, num_hash_fun);
|
||||
bucket_ids += num_hash_fun;
|
||||
}
|
||||
|
||||
ikey_length = keys[0].size();
|
||||
key_length = ikey_length;
|
||||
value_length = values[0].size();
|
||||
bucket_length = ikey_length + value_length;
|
||||
uint64_t expected_max_buckets = file_size / bucket_length;
|
||||
std::string expected_unused_user_key = "keys10:";
|
||||
ParsedInternalKey ikey(expected_unused_user_key, 0, kTypeValue);
|
||||
std::string expected_unused_bucket;
|
||||
AppendInternalKey(&expected_unused_bucket, ikey);
|
||||
expected_unused_bucket.resize(bucket_length, 'a');
|
||||
TEST(CuckooBuilderTest, SuccessWithEmptyFile) {
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/BasicTest_writable_file";
|
||||
fname = test::TmpDir() + "/EmptyFile";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio,
|
||||
file_size, num_hash_fun, 100, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
uint32_t key_idx = 0;
|
||||
std::string expected_file_data = "";
|
||||
for (uint32_t i = 0; i < expected_max_buckets; i++) {
|
||||
if (key_idx * num_hash_fun == i && key_idx < num_items) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
expected_file_data.append(keys[key_idx] + values[key_idx]);
|
||||
++key_idx;
|
||||
} else {
|
||||
expected_file_data.append(expected_unused_bucket);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, false);
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
4, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
CheckFileContents({}, {}, {}, "", 0, 2, false);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, NoCollisionLastLevel) {
|
||||
hash_map.clear();
|
||||
uint32_t expected_num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
uint64_t bucket_ids = 0;
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
// Set zero sequence number in all keys.
|
||||
ParsedInternalKey ikey(user_keys[i], 0, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
AddHashLookups(user_keys[i], bucket_ids, num_hash_fun);
|
||||
bucket_ids += num_hash_fun;
|
||||
TEST(CuckooBuilderTest, WriteSuccessNoCollisionFullKey) {
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1, 2, 3}},
|
||||
{user_keys[1], {1, 2, 3, 4}},
|
||||
{user_keys[2], {2, 3, 4, 5}},
|
||||
{user_keys[3], {3, 4, 5, 6}}
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
|
||||
std::vector<std::string> keys;
|
||||
for (auto& user_key : user_keys) {
|
||||
keys.push_back(GetInternalKey(user_key, false));
|
||||
}
|
||||
ikey_length = keys[0].size();
|
||||
user_key_length = user_keys[0].size();
|
||||
key_length = user_key_length;
|
||||
value_length = values[0].size();
|
||||
bucket_length = key_length + value_length;
|
||||
uint64_t expected_max_buckets = file_size / bucket_length;
|
||||
std::string expected_unused_bucket = "keys10:";
|
||||
expected_unused_bucket.resize(bucket_length, 'a');
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/NoCollisionLastLevel_writable_file";
|
||||
fname = test::TmpDir() + "/NoCollisionFullKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio,
|
||||
file_size, num_hash_fun, 100, true, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
uint32_t key_idx = 0;
|
||||
std::string expected_file_data = "";
|
||||
for (uint32_t i = 0; i < expected_max_buckets; i++) {
|
||||
if (key_idx * num_hash_fun == i && key_idx < num_items) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
expected_file_data.append(user_keys[key_idx] + values[key_idx]);
|
||||
++key_idx;
|
||||
} else {
|
||||
expected_file_data.append(expected_unused_bucket);
|
||||
}
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(keys[i]), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, true);
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = GetInternalKey("key00", true);
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 2, false);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WithCollision) {
|
||||
// Take keys with colliding hash function values.
|
||||
hash_map.clear();
|
||||
num_hash_fun = 20;
|
||||
num_items = num_hash_fun;
|
||||
uint32_t expected_num_hash_fun = num_hash_fun;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
// Make all hash values collide.
|
||||
AddHashLookups(user_keys[i], 0, num_hash_fun);
|
||||
TEST(CuckooBuilderTest, WriteSuccessWithCollisionFullKey) {
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1, 2, 3}},
|
||||
{user_keys[1], {0, 1, 2, 3}},
|
||||
{user_keys[2], {0, 1, 2, 3}},
|
||||
{user_keys[3], {0, 1, 2, 3}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
|
||||
std::vector<std::string> keys;
|
||||
for (auto& user_key : user_keys) {
|
||||
keys.push_back(GetInternalKey(user_key, false));
|
||||
}
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
key_length = ikey_length;
|
||||
bucket_length = key_length + value_length;
|
||||
uint64_t expected_max_buckets = file_size / bucket_length;
|
||||
std::string expected_unused_user_key = "keys10:";
|
||||
ParsedInternalKey ikey(expected_unused_user_key, 0, kTypeValue);
|
||||
std::string expected_unused_bucket;
|
||||
AppendInternalKey(&expected_unused_bucket, ikey);
|
||||
expected_unused_bucket.resize(bucket_length, 'a');
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollision_writable_file";
|
||||
fname = test::TmpDir() + "/WithCollisionFullKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), key_length, value_length, hash_table_ratio,
|
||||
file_size, num_hash_fun, 100, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
uint32_t key_idx = 0;
|
||||
std::string expected_file_data = "";
|
||||
for (uint32_t i = 0; i < expected_max_buckets; i++) {
|
||||
if (key_idx == i && key_idx < num_items) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
expected_file_data.append(keys[key_idx] + values[key_idx]);
|
||||
++key_idx;
|
||||
} else {
|
||||
expected_file_data.append(expected_unused_bucket);
|
||||
}
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(keys[i]), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, false);
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = GetInternalKey("key00", true);
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 4, false);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, FailWithTooManyCollisions) {
|
||||
// Take keys with colliding hash function values.
|
||||
// Take more keys than the number of hash functions.
|
||||
hash_map.clear();
|
||||
num_hash_fun = 20;
|
||||
num_items = num_hash_fun + 1;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
// Make all hash values collide.
|
||||
AddHashLookups(user_keys[i], 0, num_hash_fun);
|
||||
TEST(CuckooBuilderTest, WriteSuccessWithCollisionAndCuckooBlock) {
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1, 2, 3}},
|
||||
{user_keys[1], {0, 1, 2, 3}},
|
||||
{user_keys[2], {0, 1, 2, 3}},
|
||||
{user_keys[3], {0, 1, 2, 3}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
|
||||
std::vector<std::string> keys;
|
||||
for (auto& user_key : user_keys) {
|
||||
keys.push_back(GetInternalKey(user_key, false));
|
||||
}
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWithTooManyCollisions_writable";
|
||||
uint32_t cuckoo_block_size = 2;
|
||||
fname = test::TmpDir() + "/WithCollisionFullKey2";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio, file_size, num_hash_fun,
|
||||
100, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
for (uint32_t key_idx = 0; key_idx < num_items-1; key_idx++) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), cuckoo_block_size, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(keys[i]), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
cuckoo_builder.Add(Slice(keys.back()), Slice(values.back()));
|
||||
ASSERT_TRUE(cuckoo_builder.status().IsCorruption());
|
||||
cuckoo_builder.Abandon();
|
||||
writable_file->Close();
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = GetInternalKey("key00", true);
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 3, false, cuckoo_block_size);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, FailWhenSameKeyInserted) {
|
||||
hash_map.clear();
|
||||
std::string user_key = "repeatedkey";
|
||||
AddHashLookups(user_key, 0, num_hash_fun);
|
||||
std::string key_to_reuse1, key_to_reuse2;
|
||||
ParsedInternalKey ikey1(user_key, 1000, kTypeValue);
|
||||
ParsedInternalKey ikey2(user_key, 1001, kTypeValue);
|
||||
AppendInternalKey(&key_to_reuse1, ikey1);
|
||||
AppendInternalKey(&key_to_reuse2, ikey2);
|
||||
std::string value = "value";
|
||||
ikey_length = key_to_reuse1.size();
|
||||
value_length = value.size();
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWhenSameKeyInserted_writable_file";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio, file_size, num_hash_fun,
|
||||
100, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
cuckoo_builder.Add(Slice(key_to_reuse1), Slice(value));
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), 1U);
|
||||
cuckoo_builder.Add(Slice(key_to_reuse2), Slice(value));
|
||||
ASSERT_TRUE(cuckoo_builder.status().IsCorruption());
|
||||
cuckoo_builder.Abandon();
|
||||
writable_file->Close();
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WithACollisionPath) {
|
||||
hash_map.clear();
|
||||
TEST(CuckooBuilderTest, WithCollisionPathFullKey) {
|
||||
// Have two hash functions. Insert elements with overlapping hashes.
|
||||
// Finally insert an element with hash value somewhere in the middle
|
||||
// so that it displaces all the elements after that.
|
||||
num_hash_fun = 2;
|
||||
uint32_t expected_num_hash_fun = num_hash_fun;
|
||||
uint32_t max_search_depth = 100;
|
||||
num_items = 2*max_search_depth + 2;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
std::vector<uint64_t> expected_bucket_id(num_items);
|
||||
for (uint32_t i = 0; i < num_items - 1; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
// Make all hash values collide with the next element.
|
||||
AddHashLookups(user_keys[i], i, num_hash_fun);
|
||||
if (i <= max_search_depth) {
|
||||
expected_bucket_id[i] = i;
|
||||
} else {
|
||||
expected_bucket_id[i] = i+1;
|
||||
}
|
||||
}
|
||||
user_keys.back() = "keys" + std::to_string(num_items + 99);
|
||||
ParsedInternalKey ikey(user_keys.back(), num_items + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys.back(), ikey);
|
||||
values.back() = "value" + std::to_string(num_items+100);
|
||||
// Make hash values collide with first and middle elements.
|
||||
// Inserting at 0 will fail after exceeding search depth limit.
|
||||
hash_map[user_keys.back()] = {0, max_search_depth + 1};
|
||||
expected_bucket_id.back() = max_search_depth + 1;
|
||||
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
key_length = ikey_length;
|
||||
bucket_length = key_length + value_length;
|
||||
|
||||
uint64_t expected_max_buckets = file_size / bucket_length;
|
||||
std::string expected_unused_user_key = "keys10:";
|
||||
ikey = ParsedInternalKey(expected_unused_user_key, 0, kTypeValue);
|
||||
std::string expected_unused_bucket;
|
||||
AppendInternalKey(&expected_unused_bucket, ikey);
|
||||
expected_unused_bucket.resize(bucket_length, 'a');
|
||||
std::string expected_file_data = "";
|
||||
for (uint32_t i = 0; i < expected_max_buckets; i++) {
|
||||
expected_file_data += expected_unused_bucket;
|
||||
uint32_t num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03",
|
||||
"key04", "key05"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1}},
|
||||
{user_keys[1], {1, 2}},
|
||||
{user_keys[2], {2, 3}},
|
||||
{user_keys[3], {3, 4}},
|
||||
{user_keys[4], {0, 2}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 3, 4, 2};
|
||||
std::vector<std::string> keys;
|
||||
for (auto& user_key : user_keys) {
|
||||
keys.push_back(GetInternalKey(user_key, false));
|
||||
}
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollisionPath_writable_file";
|
||||
fname = test::TmpDir() + "/WithCollisionPathFullKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), key_length,
|
||||
value_length, hash_table_ratio, file_size,
|
||||
num_hash_fun, max_search_depth, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
for (uint32_t key_idx = 0; key_idx < num_items; key_idx++) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
expected_file_data.replace(expected_bucket_id[key_idx]*bucket_length,
|
||||
bucket_length, keys[key_idx] + values[key_idx]);
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(keys[i]), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, false);
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = GetInternalKey("key00", true);
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 2, false);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WithCollisionPathFullKeyAndCuckooBlock) {
|
||||
uint32_t num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03",
|
||||
"key04", "key05"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1}},
|
||||
{user_keys[1], {1, 2}},
|
||||
{user_keys[2], {3, 4}},
|
||||
{user_keys[3], {4, 5}},
|
||||
{user_keys[4], {0, 3}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {2, 1, 3, 4, 0};
|
||||
std::vector<std::string> keys;
|
||||
for (auto& user_key : user_keys) {
|
||||
keys.push_back(GetInternalKey(user_key, false));
|
||||
}
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollisionPathFullKeyAndCuckooBlock";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 2, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(keys[i]), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = GetInternalKey("key00", true);
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 2, false, 2);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WriteSuccessNoCollisionUserKey) {
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1, 2, 3}},
|
||||
{user_keys[1], {1, 2, 3, 4}},
|
||||
{user_keys[2], {2, 3, 4, 5}},
|
||||
{user_keys[3], {3, 4, 5, 6}}
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/NoCollisionUserKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(user_keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = "key00";
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(user_keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 2, true);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WriteSuccessWithCollisionUserKey) {
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03", "key04"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1, 2, 3}},
|
||||
{user_keys[1], {0, 1, 2, 3}},
|
||||
{user_keys[2], {0, 1, 2, 3}},
|
||||
{user_keys[3], {0, 1, 2, 3}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 2, 3};
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollisionUserKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(user_keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = "key00";
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(user_keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 4, true);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, WithCollisionPathUserKey) {
|
||||
uint32_t num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03",
|
||||
"key04", "key05"};
|
||||
std::vector<std::string> values = {"v01", "v02", "v03", "v04", "v05"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1}},
|
||||
{user_keys[1], {1, 2}},
|
||||
{user_keys[2], {2, 3}},
|
||||
{user_keys[3], {3, 4}},
|
||||
{user_keys[4], {0, 2}},
|
||||
};
|
||||
std::vector<uint64_t> expected_locations = {0, 1, 3, 4, 2};
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollisionPathUserKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 2, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(GetInternalKey(user_keys[i], true)), Slice(values[i]));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
|
||||
uint32_t expected_table_size = NextPowOf2(user_keys.size() / kHashTableRatio);
|
||||
std::string expected_unused_bucket = "key00";
|
||||
expected_unused_bucket += std::string(values[0].size(), 'a');
|
||||
CheckFileContents(user_keys, values, expected_locations,
|
||||
expected_unused_bucket, expected_table_size, 2, true);
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, FailWhenCollisionPathTooLong) {
|
||||
hash_map.clear();
|
||||
// Have two hash functions. Insert elements with overlapping hashes.
|
||||
// Finally insert an element which will displace all the current elements.
|
||||
num_hash_fun = 2;
|
||||
// Finally try inserting an element with hash value somewhere in the middle
|
||||
// and it should fail because the no. of elements to displace is too high.
|
||||
uint32_t num_hash_fun = 2;
|
||||
std::vector<std::string> user_keys = {"key01", "key02", "key03",
|
||||
"key04", "key05"};
|
||||
hash_map = {
|
||||
{user_keys[0], {0, 1}},
|
||||
{user_keys[1], {1, 2}},
|
||||
{user_keys[2], {2, 3}},
|
||||
{user_keys[3], {3, 4}},
|
||||
{user_keys[4], {0, 1}},
|
||||
};
|
||||
|
||||
uint32_t max_search_depth = 100;
|
||||
num_items = 2*max_search_depth + 3;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
for (uint32_t i = 0; i < num_items - 1; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
// Make all hash values collide with the next element.
|
||||
AddHashLookups(user_keys[i], i, num_hash_fun);
|
||||
}
|
||||
user_keys.back() = "keys" + std::to_string(num_items + 99);
|
||||
ParsedInternalKey ikey(user_keys.back(), num_items + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys.back(), ikey);
|
||||
values.back() = "value" + std::to_string(num_items+100);
|
||||
// Make hash values collide with middle element.
|
||||
hash_map[user_keys.back()] = {0, max_search_depth + 1};
|
||||
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWhenCollisionPathTooLong_writable";
|
||||
fname = test::TmpDir() + "/WithCollisionPathUserKey";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio, file_size, num_hash_fun,
|
||||
max_search_depth, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
for (uint32_t key_idx = 0; key_idx < num_items-1; key_idx++) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 2, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t i = 0; i < user_keys.size(); i++) {
|
||||
builder.Add(Slice(GetInternalKey(user_keys[i], false)), Slice("value"));
|
||||
ASSERT_EQ(builder.NumEntries(), i + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
}
|
||||
cuckoo_builder.Add(Slice(keys.back()), Slice(values.back()));
|
||||
ASSERT_TRUE(cuckoo_builder.status().IsCorruption());
|
||||
cuckoo_builder.Abandon();
|
||||
writable_file->Close();
|
||||
ASSERT_TRUE(builder.Finish().IsNotSupported());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
}
|
||||
|
||||
TEST(CuckooBuilderTest, FailWhenTableIsFull) {
|
||||
hash_map.clear();
|
||||
file_size = 160;
|
||||
|
||||
num_items = 7;
|
||||
std::vector<std::string> user_keys(num_items);
|
||||
std::vector<std::string> keys(num_items);
|
||||
std::vector<std::string> values(num_items);
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+1000);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
AddHashLookups(user_keys[i], i, num_hash_fun);
|
||||
}
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
bucket_length = ikey_length + value_length;
|
||||
// Check that number of items is tight.
|
||||
ASSERT_GT(bucket_length * num_items, file_size);
|
||||
ASSERT_LE(bucket_length * (num_items-1), file_size);
|
||||
TEST(CuckooBuilderTest, FailWhenSameKeyInserted) {
|
||||
hash_map = {{"repeatedkey", {0, 1, 2, 3}}};
|
||||
uint32_t num_hash_fun = 4;
|
||||
std::string user_key = "repeatedkey";
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWhenTabelIsFull_writable";
|
||||
fname = test::TmpDir() + "/FailWhenSameKeyInserted";
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &writable_file, env_options_));
|
||||
CuckooTableBuilder cuckoo_builder(
|
||||
writable_file.get(), ikey_length,
|
||||
value_length, hash_table_ratio, file_size, num_hash_fun,
|
||||
100, false, GetSliceHash);
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
for (uint32_t key_idx = 0; key_idx < num_items-1; key_idx++) {
|
||||
cuckoo_builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_OK(cuckoo_builder.status());
|
||||
ASSERT_EQ(cuckoo_builder.NumEntries(), key_idx + 1);
|
||||
}
|
||||
cuckoo_builder.Add(Slice(keys.back()), Slice(values.back()));
|
||||
ASSERT_TRUE(cuckoo_builder.status().IsCorruption());
|
||||
cuckoo_builder.Abandon();
|
||||
writable_file->Close();
|
||||
CuckooTableBuilder builder(writable_file.get(), kHashTableRatio,
|
||||
num_hash_fun, 100, BytewiseComparator(), 1, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
|
||||
builder.Add(Slice(GetInternalKey(user_key, false)), Slice("value1"));
|
||||
ASSERT_EQ(builder.NumEntries(), 1u);
|
||||
ASSERT_OK(builder.status());
|
||||
builder.Add(Slice(GetInternalKey(user_key, true)), Slice("value2"));
|
||||
ASSERT_EQ(builder.NumEntries(), 2u);
|
||||
ASSERT_OK(builder.status());
|
||||
|
||||
ASSERT_TRUE(builder.Finish().IsNotSupported());
|
||||
ASSERT_OK(writable_file->Close());
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "table/cuckoo_table_builder.h"
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
|
||||
namespace rocksdb {
|
||||
Status CuckooTableFactory::NewTableReader(const Options& options,
|
||||
const EnvOptions& soptions, const InternalKeyComparator& icomp,
|
||||
std::unique_ptr<RandomAccessFile>&& file, uint64_t file_size,
|
||||
std::unique_ptr<TableReader>* table) const {
|
||||
std::unique_ptr<CuckooTableReader> new_reader(new CuckooTableReader(options,
|
||||
std::move(file), file_size, icomp.user_comparator(), nullptr));
|
||||
Status s = new_reader->status();
|
||||
if (s.ok()) {
|
||||
*table = std::move(new_reader);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
TableBuilder* CuckooTableFactory::NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const {
|
||||
return new CuckooTableBuilder(file, hash_table_ratio_, 64, max_search_depth_,
|
||||
internal_comparator.user_comparator(), cuckoo_block_size_, nullptr);
|
||||
}
|
||||
|
||||
std::string CuckooTableFactory::GetPrintableTableOptions() const {
|
||||
std::string ret;
|
||||
ret.reserve(2000);
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
snprintf(buffer, kBufferSize, " hash_table_ratio: %lf\n",
|
||||
hash_table_ratio_);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " max_search_depth: %u\n",
|
||||
max_search_depth_);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " cuckoo_block_size: %u\n",
|
||||
cuckoo_block_size_);
|
||||
ret.append(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
TableFactory* NewCuckooTableFactory(double hash_table_ratio,
|
||||
uint32_t max_search_depth, uint32_t cuckoo_block_size) {
|
||||
return new CuckooTableFactory(
|
||||
hash_table_ratio, max_search_depth, cuckoo_block_size);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -6,27 +6,66 @@
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/murmurhash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static const uint32_t kMaxNumHashTable = 64;
|
||||
|
||||
uint64_t GetSliceMurmurHash(const Slice& s, uint32_t index,
|
||||
uint64_t max_num_buckets) {
|
||||
static constexpr uint32_t seeds[kMaxNumHashTable] = {
|
||||
816922183, 506425713, 949485004, 22513986, 421427259, 500437285,
|
||||
888981693, 847587269, 511007211, 722295391, 934013645, 566947683,
|
||||
193618736, 428277388, 770956674, 819994962, 755946528, 40807421,
|
||||
263144466, 241420041, 444294464, 731606396, 304158902, 563235655,
|
||||
968740453, 336996831, 462831574, 407970157, 985877240, 637708754,
|
||||
736932700, 205026023, 755371467, 729648411, 807744117, 46482135,
|
||||
847092855, 620960699, 102476362, 314094354, 625838942, 550889395,
|
||||
639071379, 834567510, 397667304, 151945969, 443634243, 196618243,
|
||||
421986347, 407218337, 964502417, 327741231, 493359459, 452453139,
|
||||
692216398, 108161624, 816246924, 234779764, 618949448, 496133787,
|
||||
156374056, 316589799, 982915425, 553105889 };
|
||||
return MurmurHash(s.data(), s.size(), seeds[index]) % max_num_buckets;
|
||||
const uint32_t kCuckooMurmurSeedMultiplier = 816922183;
|
||||
static inline uint64_t CuckooHash(
|
||||
const Slice& user_key, uint32_t hash_cnt, uint64_t table_size_minus_one,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t)) {
|
||||
#ifndef NDEBUG
|
||||
// This part is used only in unit tests.
|
||||
if (get_slice_hash != nullptr) {
|
||||
return get_slice_hash(user_key, hash_cnt, table_size_minus_one + 1);
|
||||
}
|
||||
#endif
|
||||
return MurmurHash(user_key.data(), user_key.size(),
|
||||
kCuckooMurmurSeedMultiplier * hash_cnt) & table_size_minus_one;
|
||||
}
|
||||
|
||||
// Cuckoo Table is designed for applications that require fast point lookups
|
||||
// but not fast range scans.
|
||||
//
|
||||
// Some assumptions:
|
||||
// - Key length and Value length are fixed.
|
||||
// - Does not support Snapshot.
|
||||
// - Does not support Merge operations.
|
||||
class CuckooTableFactory : public TableFactory {
|
||||
public:
|
||||
CuckooTableFactory(double hash_table_ratio, uint32_t max_search_depth,
|
||||
uint32_t cuckoo_block_size)
|
||||
: hash_table_ratio_(hash_table_ratio),
|
||||
max_search_depth_(max_search_depth),
|
||||
cuckoo_block_size_(cuckoo_block_size) {}
|
||||
~CuckooTableFactory() {}
|
||||
|
||||
const char* Name() const override { return "CuckooTable"; }
|
||||
|
||||
Status NewTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table) const override;
|
||||
|
||||
TableBuilder* NewTableBuilder(const Options& options,
|
||||
const InternalKeyComparator& icomparator, WritableFile* file,
|
||||
CompressionType compression_type) const override;
|
||||
|
||||
// Sanitizes the specified DB Options.
|
||||
Status SanitizeDBOptions(const DBOptions* db_opts) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string GetPrintableTableOptions() const override;
|
||||
|
||||
private:
|
||||
const double hash_table_ratio_;
|
||||
const uint32_t max_search_depth_;
|
||||
const uint32_t cuckoo_block_size_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
+253
-30
@@ -10,11 +10,21 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace {
|
||||
static const uint64_t CACHE_LINE_MASK = ~((uint64_t)CACHE_LINE_SIZE - 1);
|
||||
}
|
||||
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
@@ -22,10 +32,11 @@ CuckooTableReader::CuckooTableReader(
|
||||
const Options& options,
|
||||
std::unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size,
|
||||
uint64_t (*GetSliceHashPtr)(const Slice&, uint32_t, uint64_t))
|
||||
const Comparator* comparator,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t))
|
||||
: file_(std::move(file)),
|
||||
file_size_(file_size),
|
||||
GetSliceHash(GetSliceHashPtr) {
|
||||
ucomp_(comparator),
|
||||
get_slice_hash_(get_slice_hash) {
|
||||
if (!options.allow_mmap_reads) {
|
||||
status_ = Status::InvalidArgument("File is not mmaped");
|
||||
}
|
||||
@@ -37,12 +48,12 @@ CuckooTableReader::CuckooTableReader(
|
||||
}
|
||||
table_props_.reset(props);
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto hash_funs = user_props.find(CuckooTablePropertyNames::kNumHashTable);
|
||||
auto hash_funs = user_props.find(CuckooTablePropertyNames::kNumHashFunc);
|
||||
if (hash_funs == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Number of hash functions not found");
|
||||
return;
|
||||
}
|
||||
num_hash_fun_ = *reinterpret_cast<const uint32_t*>(hash_funs->second.data());
|
||||
num_hash_func_ = *reinterpret_cast<const uint32_t*>(hash_funs->second.data());
|
||||
auto unused_key = user_props.find(CuckooTablePropertyNames::kEmptyKey);
|
||||
if (unused_key == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Empty bucket value not found");
|
||||
@@ -60,18 +71,29 @@ CuckooTableReader::CuckooTableReader(
|
||||
value_length->second.data());
|
||||
bucket_length_ = key_length_ + value_length_;
|
||||
|
||||
auto num_buckets = user_props.find(CuckooTablePropertyNames::kMaxNumBuckets);
|
||||
if (num_buckets == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Num buckets not found");
|
||||
auto hash_table_size = user_props.find(
|
||||
CuckooTablePropertyNames::kHashTableSize);
|
||||
if (hash_table_size == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Hash table size not found");
|
||||
return;
|
||||
}
|
||||
num_buckets_ = *reinterpret_cast<const uint64_t*>(num_buckets->second.data());
|
||||
table_size_minus_one_ = *reinterpret_cast<const uint64_t*>(
|
||||
hash_table_size->second.data()) - 1;
|
||||
auto is_last_level = user_props.find(CuckooTablePropertyNames::kIsLastLevel);
|
||||
if (is_last_level == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Is last level not found");
|
||||
return;
|
||||
}
|
||||
is_last_level_ = *reinterpret_cast<const bool*>(is_last_level->second.data());
|
||||
auto cuckoo_block_size = user_props.find(
|
||||
CuckooTablePropertyNames::kCuckooBlockSize);
|
||||
if (cuckoo_block_size == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Cuckoo block size not found");
|
||||
return;
|
||||
}
|
||||
cuckoo_block_size_ = *reinterpret_cast<const uint32_t*>(
|
||||
cuckoo_block_size->second.data());
|
||||
cuckoo_block_bytes_minus_one_ = cuckoo_block_size_ * bucket_length_ - 1;
|
||||
status_ = file_->Read(0, file_size, &file_data_, nullptr);
|
||||
}
|
||||
|
||||
@@ -80,33 +102,234 @@ Status CuckooTableReader::Get(
|
||||
bool (*result_handler)(void* arg, const ParsedInternalKey& k,
|
||||
const Slice& v),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context)) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(key, &ikey)) {
|
||||
return Status::Corruption("Unable to parse key into inernal key.");
|
||||
}
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_fun_; ++hash_cnt) {
|
||||
uint64_t hash_val = GetSliceHash(ikey.user_key, hash_cnt, num_buckets_);
|
||||
assert(hash_val < num_buckets_);
|
||||
uint64_t offset = hash_val * bucket_length_;
|
||||
assert(key.size() == key_length_ + (is_last_level_ ? 8 : 0));
|
||||
Slice user_key = ExtractUserKey(key);
|
||||
for (uint32_t hash_cnt = 0; hash_cnt < num_hash_func_; ++hash_cnt) {
|
||||
uint64_t offset = bucket_length_ * CuckooHash(
|
||||
user_key, hash_cnt, table_size_minus_one_, get_slice_hash_);
|
||||
const char* bucket = &file_data_.data()[offset];
|
||||
if (unused_key_.compare(0, key_length_, bucket, key_length_) == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
// Here, we compare only the user key part as we support only one entry
|
||||
// per user key and we don't support sanpshot.
|
||||
if (ikey.user_key.compare(Slice(bucket, ikey.user_key.size())) == 0) {
|
||||
Slice value = Slice(&bucket[key_length_], value_length_);
|
||||
result_handler(handle_context, ikey, value);
|
||||
// We don't support merge operations. So, we return here.
|
||||
return Status::OK();
|
||||
for (uint32_t block_idx = 0; block_idx < cuckoo_block_size_;
|
||||
++block_idx, bucket += bucket_length_) {
|
||||
if (ucomp_->Compare(Slice(unused_key_.data(), user_key.size()),
|
||||
Slice(bucket, user_key.size())) == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
// Here, we compare only the user key part as we support only one entry
|
||||
// per user key and we don't support sanpshot.
|
||||
if (ucomp_->Compare(user_key, Slice(bucket, user_key.size())) == 0) {
|
||||
Slice value = Slice(&bucket[key_length_], value_length_);
|
||||
if (is_last_level_) {
|
||||
ParsedInternalKey found_ikey(
|
||||
Slice(bucket, key_length_), 0, kTypeValue);
|
||||
result_handler(handle_context, found_ikey, value);
|
||||
} else {
|
||||
Slice full_key(bucket, key_length_);
|
||||
ParsedInternalKey found_ikey;
|
||||
ParseInternalKey(full_key, &found_ikey);
|
||||
result_handler(handle_context, found_ikey, value);
|
||||
}
|
||||
// We don't support merge operations. So, we return here.
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Iterator* CuckooTableReader::NewIterator(const ReadOptions&, Arena* arena) {
|
||||
// TODO(rbs): Implement this as this will be used in compaction.
|
||||
return nullptr;
|
||||
void CuckooTableReader::Prepare(const Slice& key) {
|
||||
// Prefetch the first Cuckoo Block.
|
||||
Slice user_key = ExtractUserKey(key);
|
||||
uint64_t addr = reinterpret_cast<uint64_t>(file_data_.data()) +
|
||||
bucket_length_ * CuckooHash(user_key, 0, table_size_minus_one_, nullptr);
|
||||
uint64_t end_addr = addr + cuckoo_block_bytes_minus_one_;
|
||||
for (addr &= CACHE_LINE_MASK; addr < end_addr; addr += CACHE_LINE_SIZE) {
|
||||
PREFETCH(reinterpret_cast<const char*>(addr), 0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
class CuckooTableIterator : public Iterator {
|
||||
public:
|
||||
explicit CuckooTableIterator(CuckooTableReader* reader);
|
||||
~CuckooTableIterator() {}
|
||||
bool Valid() const override;
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
void Seek(const Slice& target) override;
|
||||
void Next() override;
|
||||
void Prev() override;
|
||||
Slice key() const override;
|
||||
Slice value() const override;
|
||||
Status status() const override { return status_; }
|
||||
void LoadKeysFromReader();
|
||||
|
||||
private:
|
||||
struct CompareKeys {
|
||||
CompareKeys(const Comparator* ucomp, const bool last_level)
|
||||
: ucomp_(ucomp),
|
||||
is_last_level_(last_level) {}
|
||||
bool operator()(const std::pair<Slice, uint32_t>& first,
|
||||
const std::pair<Slice, uint32_t>& second) const {
|
||||
if (is_last_level_) {
|
||||
return ucomp_->Compare(first.first, second.first) < 0;
|
||||
} else {
|
||||
return ucomp_->Compare(ExtractUserKey(first.first),
|
||||
ExtractUserKey(second.first)) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* ucomp_;
|
||||
const bool is_last_level_;
|
||||
};
|
||||
const CompareKeys comparator_;
|
||||
void PrepareKVAtCurrIdx();
|
||||
CuckooTableReader* reader_;
|
||||
Status status_;
|
||||
// Contains a map of keys to bucket_id sorted in key order.
|
||||
std::vector<std::pair<Slice, uint32_t>> key_to_bucket_id_;
|
||||
// We assume that the number of items can be stored in uint32 (4 Billion).
|
||||
uint32_t curr_key_idx_;
|
||||
Slice curr_value_;
|
||||
IterKey curr_key_;
|
||||
// No copying allowed
|
||||
CuckooTableIterator(const CuckooTableIterator&) = delete;
|
||||
void operator=(const Iterator&) = delete;
|
||||
};
|
||||
|
||||
CuckooTableIterator::CuckooTableIterator(CuckooTableReader* reader)
|
||||
: comparator_(reader->ucomp_, reader->is_last_level_),
|
||||
reader_(reader),
|
||||
curr_key_idx_(std::numeric_limits<int32_t>::max()) {
|
||||
key_to_bucket_id_.clear();
|
||||
curr_value_.clear();
|
||||
curr_key_.Clear();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::LoadKeysFromReader() {
|
||||
key_to_bucket_id_.reserve(reader_->GetTableProperties()->num_entries);
|
||||
uint64_t num_buckets = reader_->table_size_minus_one_ +
|
||||
reader_->cuckoo_block_size_;
|
||||
for (uint32_t bucket_id = 0; bucket_id < num_buckets; bucket_id++) {
|
||||
Slice read_key;
|
||||
status_ = reader_->file_->Read(bucket_id * reader_->bucket_length_,
|
||||
reader_->key_length_, &read_key, nullptr);
|
||||
if (read_key != Slice(reader_->unused_key_)) {
|
||||
key_to_bucket_id_.push_back(std::make_pair(read_key, bucket_id));
|
||||
}
|
||||
}
|
||||
assert(key_to_bucket_id_.size() ==
|
||||
reader_->GetTableProperties()->num_entries);
|
||||
std::sort(key_to_bucket_id_.begin(), key_to_bucket_id_.end(), comparator_);
|
||||
curr_key_idx_ = key_to_bucket_id_.size();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::SeekToFirst() {
|
||||
curr_key_idx_ = 0;
|
||||
PrepareKVAtCurrIdx();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::SeekToLast() {
|
||||
curr_key_idx_ = key_to_bucket_id_.size() - 1;
|
||||
PrepareKVAtCurrIdx();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::Seek(const Slice& target) {
|
||||
// We assume that the target is an internal key. If this is last level file,
|
||||
// we need to take only the user key part to seek.
|
||||
Slice target_to_search = reader_->is_last_level_ ?
|
||||
ExtractUserKey(target) : target;
|
||||
auto seek_it = std::lower_bound(key_to_bucket_id_.begin(),
|
||||
key_to_bucket_id_.end(),
|
||||
std::make_pair(target_to_search, 0),
|
||||
comparator_);
|
||||
curr_key_idx_ = std::distance(key_to_bucket_id_.begin(), seek_it);
|
||||
PrepareKVAtCurrIdx();
|
||||
}
|
||||
|
||||
bool CuckooTableIterator::Valid() const {
|
||||
return curr_key_idx_ < key_to_bucket_id_.size();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::PrepareKVAtCurrIdx() {
|
||||
if (!Valid()) {
|
||||
curr_value_.clear();
|
||||
curr_key_.Clear();
|
||||
return;
|
||||
}
|
||||
uint64_t offset = ((uint64_t) key_to_bucket_id_[curr_key_idx_].second
|
||||
* reader_->bucket_length_) + reader_->key_length_;
|
||||
status_ = reader_->file_->Read(offset, reader_->value_length_,
|
||||
&curr_value_, nullptr);
|
||||
if (reader_->is_last_level_) {
|
||||
// Always return internal key.
|
||||
curr_key_.SetInternalKey(
|
||||
key_to_bucket_id_[curr_key_idx_].first, 0, kTypeValue);
|
||||
}
|
||||
}
|
||||
|
||||
void CuckooTableIterator::Next() {
|
||||
if (!Valid()) {
|
||||
curr_value_.clear();
|
||||
curr_key_.Clear();
|
||||
return;
|
||||
}
|
||||
++curr_key_idx_;
|
||||
PrepareKVAtCurrIdx();
|
||||
}
|
||||
|
||||
void CuckooTableIterator::Prev() {
|
||||
if (curr_key_idx_ == 0) {
|
||||
curr_key_idx_ = key_to_bucket_id_.size();
|
||||
}
|
||||
if (!Valid()) {
|
||||
curr_value_.clear();
|
||||
curr_key_.Clear();
|
||||
return;
|
||||
}
|
||||
--curr_key_idx_;
|
||||
PrepareKVAtCurrIdx();
|
||||
}
|
||||
|
||||
Slice CuckooTableIterator::key() const {
|
||||
assert(Valid());
|
||||
if (reader_->is_last_level_) {
|
||||
return curr_key_.GetKey();
|
||||
} else {
|
||||
return key_to_bucket_id_[curr_key_idx_].first;
|
||||
}
|
||||
}
|
||||
|
||||
Slice CuckooTableIterator::value() const {
|
||||
assert(Valid());
|
||||
return curr_value_;
|
||||
}
|
||||
|
||||
extern Iterator* NewErrorIterator(const Status& status, Arena* arena);
|
||||
|
||||
Iterator* CuckooTableReader::NewIterator(
|
||||
const ReadOptions& read_options, Arena* arena) {
|
||||
if (!status().ok()) {
|
||||
return NewErrorIterator(
|
||||
Status::Corruption("CuckooTableReader status is not okay."), arena);
|
||||
}
|
||||
if (read_options.total_order_seek) {
|
||||
return NewErrorIterator(
|
||||
Status::InvalidArgument("total_order_seek is not supported."), arena);
|
||||
}
|
||||
CuckooTableIterator* iter;
|
||||
if (arena == nullptr) {
|
||||
iter = new CuckooTableIterator(this);
|
||||
} else {
|
||||
auto iter_mem = arena->AllocateAligned(sizeof(CuckooTableIterator));
|
||||
iter = new (iter_mem) CuckooTableIterator(this);
|
||||
}
|
||||
if (iter->status().ok()) {
|
||||
iter->LoadKeysFromReader();
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
|
||||
size_t CuckooTableReader::ApproximateMemoryUsage() const { return 0; }
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif
|
||||
+22
-11
@@ -11,6 +11,8 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -27,10 +29,11 @@ class CuckooTableReader: public TableReader {
|
||||
const Options& options,
|
||||
std::unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size,
|
||||
uint64_t (*GetSliceHash)(const Slice&, uint32_t, uint64_t));
|
||||
const Comparator* user_comparator,
|
||||
uint64_t (*get_slice_hash)(const Slice&, uint32_t, uint64_t));
|
||||
~CuckooTableReader() {}
|
||||
|
||||
std::shared_ptr<const TableProperties> GetTableProperties() const {
|
||||
std::shared_ptr<const TableProperties> GetTableProperties() const override {
|
||||
return table_props_;
|
||||
}
|
||||
|
||||
@@ -40,30 +43,38 @@ class CuckooTableReader: public TableReader {
|
||||
const ReadOptions& readOptions, const Slice& key, void* handle_context,
|
||||
bool (*result_handler)(void* arg, const ParsedInternalKey& k,
|
||||
const Slice& v),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context) = nullptr);
|
||||
void (*mark_key_may_exist_handler)(void* handle_context) = nullptr)
|
||||
override;
|
||||
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr);
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr) override;
|
||||
void Prepare(const Slice& target) override;
|
||||
|
||||
// Report an approximation of how much memory has been used.
|
||||
size_t ApproximateMemoryUsage() const override;
|
||||
|
||||
// Following methods are not implemented for Cuckoo Table Reader
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) { return 0; }
|
||||
void SetupForCompaction() {}
|
||||
void Prepare(const Slice& target) {}
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) override { return 0; }
|
||||
void SetupForCompaction() override {}
|
||||
// End of methods not implemented.
|
||||
|
||||
private:
|
||||
friend class CuckooTableIterator;
|
||||
void LoadAllKeys(std::vector<std::pair<Slice, uint32_t>>* key_to_bucket_id);
|
||||
std::unique_ptr<RandomAccessFile> file_;
|
||||
Slice file_data_;
|
||||
const uint64_t file_size_;
|
||||
bool is_last_level_;
|
||||
std::shared_ptr<const TableProperties> table_props_;
|
||||
Status status_;
|
||||
uint32_t num_hash_fun_;
|
||||
uint32_t num_hash_func_;
|
||||
std::string unused_key_;
|
||||
uint32_t key_length_;
|
||||
uint32_t value_length_;
|
||||
uint32_t bucket_length_;
|
||||
uint64_t num_buckets_;
|
||||
uint64_t (*GetSliceHash)(const Slice& s, uint32_t index,
|
||||
uint32_t cuckoo_block_size_;
|
||||
uint32_t cuckoo_block_bytes_minus_one_;
|
||||
uint64_t table_size_minus_one_;
|
||||
const Comparator* ucomp_;
|
||||
uint64_t (*get_slice_hash_)(const Slice& s, uint32_t index,
|
||||
uint64_t max_num_buckets);
|
||||
};
|
||||
|
||||
|
||||
+291
-101
@@ -11,6 +11,8 @@ int main() {
|
||||
}
|
||||
#else
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
@@ -20,6 +22,7 @@ int main() {
|
||||
#include "table/cuckoo_table_builder.h"
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -30,11 +33,11 @@ using GFLAGS::SetUsageMessage;
|
||||
DEFINE_string(file_dir, "", "Directory where the files will be created"
|
||||
" for benchmark. Added for using tmpfs.");
|
||||
DEFINE_bool(enable_perf, false, "Run Benchmark Tests too.");
|
||||
DEFINE_bool(write, false,
|
||||
"Should write new values to file in performance tests?");
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
namespace {
|
||||
const uint32_t kNumHashFunc = 10;
|
||||
// Methods, variables related to Hash functions.
|
||||
@@ -94,34 +97,37 @@ class CuckooReaderTest {
|
||||
values.resize(num_items);
|
||||
}
|
||||
|
||||
void CreateCuckooFile(bool is_last_level) {
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
std::string NumToStr(int64_t i) {
|
||||
return std::string(reinterpret_cast<char*>(&i), sizeof(i));
|
||||
}
|
||||
|
||||
void CreateCuckooFileAndCheckReader(
|
||||
const Comparator* ucomp = BytewiseComparator()) {
|
||||
std::unique_ptr<WritableFile> writable_file;
|
||||
ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
|
||||
CuckooTableBuilder builder(
|
||||
writable_file.get(), keys[0].size(), values[0].size(), 0.9,
|
||||
10000, kNumHashFunc, 100, is_last_level, GetSliceHash);
|
||||
writable_file.get(), 0.9, kNumHashFunc, 100, ucomp, 2, GetSliceHash);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint32_t key_idx = 0; key_idx < num_items; ++key_idx) {
|
||||
builder.Add(Slice(keys[key_idx]), Slice(values[key_idx]));
|
||||
ASSERT_EQ(builder.NumEntries(), key_idx + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
ASSERT_EQ(builder.NumEntries(), key_idx + 1);
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_EQ(num_items, builder.NumEntries());
|
||||
file_size = builder.FileSize();
|
||||
ASSERT_OK(writable_file->Close());
|
||||
}
|
||||
|
||||
void CheckReader() {
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
// Check reader now.
|
||||
std::unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
ucomp,
|
||||
GetSliceHash);
|
||||
ASSERT_OK(reader.status());
|
||||
|
||||
for (uint32_t i = 0; i < num_items; ++i) {
|
||||
ValuesToAssert v(user_keys[i], values[i]);
|
||||
ASSERT_OK(reader.Get(
|
||||
@@ -129,11 +135,80 @@ class CuckooReaderTest {
|
||||
ASSERT_EQ(1, v.call_count);
|
||||
}
|
||||
}
|
||||
void UpdateKeys(bool with_zero_seqno) {
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
ParsedInternalKey ikey(user_keys[i],
|
||||
with_zero_seqno ? 0 : i + 1000, kTypeValue);
|
||||
keys[i].clear();
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckIterator(const Comparator* ucomp = BytewiseComparator()) {
|
||||
std::unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
ucomp,
|
||||
GetSliceHash);
|
||||
ASSERT_OK(reader.status());
|
||||
Iterator* it = reader.NewIterator(ReadOptions(), nullptr);
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(!it->Valid());
|
||||
it->SeekToFirst();
|
||||
int cnt = 0;
|
||||
while (it->Valid()) {
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(Slice(keys[cnt]) == it->key());
|
||||
ASSERT_TRUE(Slice(values[cnt]) == it->value());
|
||||
++cnt;
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(static_cast<uint32_t>(cnt), num_items);
|
||||
|
||||
it->SeekToLast();
|
||||
cnt = num_items - 1;
|
||||
ASSERT_TRUE(it->Valid());
|
||||
while (it->Valid()) {
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(Slice(keys[cnt]) == it->key());
|
||||
ASSERT_TRUE(Slice(values[cnt]) == it->value());
|
||||
--cnt;
|
||||
it->Prev();
|
||||
}
|
||||
ASSERT_EQ(cnt, -1);
|
||||
|
||||
cnt = num_items / 2;
|
||||
it->Seek(keys[cnt]);
|
||||
while (it->Valid()) {
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(Slice(keys[cnt]) == it->key());
|
||||
ASSERT_TRUE(Slice(values[cnt]) == it->value());
|
||||
++cnt;
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(static_cast<uint32_t>(cnt), num_items);
|
||||
delete it;
|
||||
|
||||
Arena arena;
|
||||
it = reader.NewIterator(ReadOptions(), &arena);
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(!it->Valid());
|
||||
it->Seek(keys[num_items/2]);
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(keys[num_items/2] == it->key());
|
||||
ASSERT_TRUE(values[num_items/2] == it->value());
|
||||
ASSERT_OK(it->status());
|
||||
it->~Iterator();
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> user_keys;
|
||||
std::vector<std::string> values;
|
||||
uint32_t num_items;
|
||||
uint64_t num_items;
|
||||
std::string fname;
|
||||
uint64_t file_size;
|
||||
Options options;
|
||||
@@ -142,56 +217,123 @@ class CuckooReaderTest {
|
||||
};
|
||||
|
||||
TEST(CuckooReaderTest, WhenKeyExists) {
|
||||
SetUp(10);
|
||||
SetUp(kNumHashFunc);
|
||||
fname = test::TmpDir() + "/CuckooReader_WhenKeyExists";
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
for (uint64_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "key" + NumToStr(i);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
AddHashLookups(user_keys[i], i * kNumHashFunc, kNumHashFunc);
|
||||
values[i] = "value" + NumToStr(i);
|
||||
// Give disjoint hash values.
|
||||
AddHashLookups(user_keys[i], i, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFile(false);
|
||||
CheckReader();
|
||||
CreateCuckooFileAndCheckReader();
|
||||
// Last level file.
|
||||
CreateCuckooFile(true);
|
||||
CheckReader();
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader();
|
||||
// Test with collision. Make all hash values collide.
|
||||
hash_map.clear();
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
AddHashLookups(user_keys[i], 0, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFile(false);
|
||||
CheckReader();
|
||||
UpdateKeys(false);
|
||||
CreateCuckooFileAndCheckReader();
|
||||
// Last level file.
|
||||
CreateCuckooFile(true);
|
||||
CheckReader();
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader();
|
||||
}
|
||||
|
||||
TEST(CuckooReaderTest, WhenKeyExistsWithUint64Comparator) {
|
||||
SetUp(kNumHashFunc);
|
||||
fname = test::TmpDir() + "/CuckooReaderUint64_WhenKeyExists";
|
||||
for (uint64_t i = 0; i < num_items; i++) {
|
||||
user_keys[i].resize(8);
|
||||
memcpy(&user_keys[i][0], static_cast<void*>(&i), 8);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + NumToStr(i);
|
||||
// Give disjoint hash values.
|
||||
AddHashLookups(user_keys[i], i, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
// Last level file.
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
// Test with collision. Make all hash values collide.
|
||||
hash_map.clear();
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
AddHashLookups(user_keys[i], 0, kNumHashFunc);
|
||||
}
|
||||
UpdateKeys(false);
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
// Last level file.
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
}
|
||||
|
||||
TEST(CuckooReaderTest, CheckIterator) {
|
||||
SetUp(2*kNumHashFunc);
|
||||
fname = test::TmpDir() + "/CuckooReader_CheckIterator";
|
||||
for (uint64_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "key" + NumToStr(i);
|
||||
ParsedInternalKey ikey(user_keys[i], 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + NumToStr(i);
|
||||
// Give disjoint hash values, in reverse order.
|
||||
AddHashLookups(user_keys[i], num_items-i-1, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFileAndCheckReader();
|
||||
CheckIterator();
|
||||
// Last level file.
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader();
|
||||
CheckIterator();
|
||||
}
|
||||
|
||||
TEST(CuckooReaderTest, CheckIteratorUint64) {
|
||||
SetUp(2*kNumHashFunc);
|
||||
fname = test::TmpDir() + "/CuckooReader_CheckIterator";
|
||||
for (uint64_t i = 0; i < num_items; i++) {
|
||||
user_keys[i].resize(8);
|
||||
memcpy(&user_keys[i][0], static_cast<void*>(&i), 8);
|
||||
ParsedInternalKey ikey(user_keys[i], 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + NumToStr(i);
|
||||
// Give disjoint hash values, in reverse order.
|
||||
AddHashLookups(user_keys[i], num_items-i-1, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
CheckIterator(test::Uint64Comparator());
|
||||
// Last level file.
|
||||
UpdateKeys(true);
|
||||
CreateCuckooFileAndCheckReader(test::Uint64Comparator());
|
||||
CheckIterator(test::Uint64Comparator());
|
||||
}
|
||||
|
||||
TEST(CuckooReaderTest, WhenKeyNotFound) {
|
||||
// Add keys with colliding hash values.
|
||||
SetUp(kNumHashFunc / 2);
|
||||
SetUp(kNumHashFunc);
|
||||
fname = test::TmpDir() + "/CuckooReader_WhenKeyNotFound";
|
||||
for (uint32_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "keys" + std::to_string(i+100);
|
||||
for (uint64_t i = 0; i < num_items; i++) {
|
||||
user_keys[i] = "key" + NumToStr(i);
|
||||
ParsedInternalKey ikey(user_keys[i], i + 1000, kTypeValue);
|
||||
AppendInternalKey(&keys[i], ikey);
|
||||
values[i] = "value" + std::to_string(i+100);
|
||||
values[i] = "value" + NumToStr(i);
|
||||
// Make all hash values collide.
|
||||
AddHashLookups(user_keys[i], 0, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFile(false);
|
||||
CheckReader();
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
CreateCuckooFileAndCheckReader();
|
||||
std::unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
BytewiseComparator(),
|
||||
GetSliceHash);
|
||||
ASSERT_OK(reader.status());
|
||||
// Search for a key with colliding hash values.
|
||||
std::string not_found_user_key = "keys" + std::to_string(num_items + 100);
|
||||
std::string not_found_user_key = "key" + NumToStr(num_items);
|
||||
std::string not_found_key;
|
||||
AddHashLookups(not_found_user_key, 0, kNumHashFunc);
|
||||
ParsedInternalKey ikey(not_found_user_key, 1000, kTypeValue);
|
||||
@@ -202,32 +344,23 @@ TEST(CuckooReaderTest, WhenKeyNotFound) {
|
||||
ASSERT_EQ(0, v.call_count);
|
||||
ASSERT_OK(reader.status());
|
||||
// Search for a key with an independent hash value.
|
||||
std::string not_found_user_key2 = "keys" + std::to_string(num_items + 101);
|
||||
std::string not_found_key2;
|
||||
std::string not_found_user_key2 = "key" + NumToStr(num_items + 1);
|
||||
AddHashLookups(not_found_user_key2, kNumHashFunc, kNumHashFunc);
|
||||
ParsedInternalKey ikey2(not_found_user_key2, 1000, kTypeValue);
|
||||
std::string not_found_key2;
|
||||
AppendInternalKey(¬_found_key2, ikey2);
|
||||
ASSERT_OK(reader.Get(
|
||||
ReadOptions(), Slice(not_found_key2), &v, AssertValues, nullptr));
|
||||
ASSERT_EQ(0, v.call_count);
|
||||
ASSERT_OK(reader.status());
|
||||
|
||||
// Test read with corrupted key.
|
||||
not_found_key2.pop_back();
|
||||
ASSERT_TRUE(!ParseInternalKey(not_found_key2, &ikey));
|
||||
ASSERT_TRUE(reader.Get(
|
||||
ReadOptions(), Slice(not_found_key2), &v,
|
||||
AssertValues, nullptr).IsCorruption());
|
||||
ASSERT_EQ(0, v.call_count);
|
||||
ASSERT_OK(reader.status());
|
||||
|
||||
// Test read when key is unused key.
|
||||
std::string unused_user_key = "keys10:";
|
||||
std::string unused_key =
|
||||
reader.GetTableProperties()->user_collected_properties.at(
|
||||
CuckooTablePropertyNames::kEmptyKey);
|
||||
// Add hash values that map to empty buckets.
|
||||
AddHashLookups(unused_user_key, kNumHashFunc, kNumHashFunc);
|
||||
std::string unused_key;
|
||||
ParsedInternalKey ikey3(unused_user_key, 1000, kTypeValue);
|
||||
AppendInternalKey(&unused_key, ikey3);
|
||||
AddHashLookups(ExtractUserKey(unused_key).ToString(),
|
||||
kNumHashFunc, kNumHashFunc);
|
||||
ASSERT_OK(reader.Get(
|
||||
ReadOptions(), Slice(unused_key), &v, AssertValues, nullptr));
|
||||
ASSERT_EQ(0, v.call_count);
|
||||
@@ -249,93 +382,150 @@ bool CheckValue(void* cnt_ptr, const ParsedInternalKey& k, const Slice& v) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void GetKeys(uint64_t num, std::vector<std::string>* keys) {
|
||||
IterKey k;
|
||||
k.SetInternalKey("", 0, kTypeValue);
|
||||
std::string internal_key_suffix = k.GetKey().ToString();
|
||||
ASSERT_EQ(static_cast<size_t>(8), internal_key_suffix.size());
|
||||
for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
|
||||
std::string new_key(reinterpret_cast<char*>(&key_idx), sizeof(key_idx));
|
||||
new_key += internal_key_suffix;
|
||||
keys->push_back(new_key);
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetFileName(uint64_t num) {
|
||||
if (FLAGS_file_dir.empty()) {
|
||||
FLAGS_file_dir = test::TmpDir();
|
||||
}
|
||||
return FLAGS_file_dir + "/cuckoo_read_benchmark" +
|
||||
std::to_string(num/1000000) + "Mkeys";
|
||||
}
|
||||
|
||||
// Create last level file as we are interested in measuring performance of
|
||||
// last level file only.
|
||||
void BM_CuckooRead(uint64_t num, uint32_t key_length,
|
||||
uint32_t value_length, uint64_t num_reads, double hash_ratio) {
|
||||
assert(value_length <= key_length);
|
||||
assert(8 <= key_length);
|
||||
std::vector<std::string> keys;
|
||||
void WriteFile(const std::vector<std::string>& keys,
|
||||
const uint64_t num, double hash_ratio) {
|
||||
Options options;
|
||||
options.allow_mmap_reads = true;
|
||||
Env* env = options.env;
|
||||
EnvOptions env_options = EnvOptions(options);
|
||||
uint64_t file_size;
|
||||
if (FLAGS_file_dir.empty()) {
|
||||
FLAGS_file_dir = test::TmpDir();
|
||||
}
|
||||
std::string fname = FLAGS_file_dir + "/cuckoo_read_benchmark";
|
||||
std::string fname = GetFileName(num);
|
||||
|
||||
uint64_t predicted_file_size =
|
||||
num * (key_length + value_length) / hash_ratio + 1024;
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
std::unique_ptr<WritableFile> writable_file;
|
||||
ASSERT_OK(env->NewWritableFile(fname, &writable_file, env_options));
|
||||
CuckooTableBuilder builder(
|
||||
writable_file.get(), key_length + 8, value_length, hash_ratio,
|
||||
predicted_file_size, kMaxNumHashTable, 1000, true, GetSliceMurmurHash);
|
||||
writable_file.get(), hash_ratio,
|
||||
64, 1000, test::Uint64Comparator(), 5, nullptr);
|
||||
ASSERT_OK(builder.status());
|
||||
for (uint64_t key_idx = 0; key_idx < num; ++key_idx) {
|
||||
// Value is just a part of key.
|
||||
std::string new_key(reinterpret_cast<char*>(&key_idx), sizeof(key_idx));
|
||||
new_key = std::string(key_length - new_key.size(), 'k') + new_key;
|
||||
ParsedInternalKey ikey(new_key, num, kTypeValue);
|
||||
std::string full_key;
|
||||
AppendInternalKey(&full_key, ikey);
|
||||
builder.Add(Slice(full_key), Slice(&full_key[0], value_length));
|
||||
builder.Add(Slice(keys[key_idx]), Slice(&keys[key_idx][0], 4));
|
||||
ASSERT_EQ(builder.NumEntries(), key_idx + 1);
|
||||
ASSERT_OK(builder.status());
|
||||
keys.push_back(full_key);
|
||||
}
|
||||
ASSERT_OK(builder.Finish());
|
||||
ASSERT_EQ(num, builder.NumEntries());
|
||||
file_size = builder.FileSize();
|
||||
ASSERT_OK(writable_file->Close());
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
|
||||
uint64_t file_size;
|
||||
env->GetFileSize(fname, &file_size);
|
||||
std::unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
GetSliceMurmurHash);
|
||||
options, std::move(read_file), file_size,
|
||||
test::Uint64Comparator(), nullptr);
|
||||
ASSERT_OK(reader.status());
|
||||
ReadOptions r_options;
|
||||
for (uint64_t i = 0; i < num; ++i) {
|
||||
int cnt = 0;
|
||||
ASSERT_OK(reader.Get(r_options, Slice(keys[i]), &cnt, CheckValue, nullptr));
|
||||
if (cnt != 1) {
|
||||
fprintf(stderr, "%" PRIu64 " not found.\n", i);
|
||||
ASSERT_EQ(1, cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadKeys(uint64_t num, uint32_t batch_size) {
|
||||
Options options;
|
||||
options.allow_mmap_reads = true;
|
||||
Env* env = options.env;
|
||||
EnvOptions env_options = EnvOptions(options);
|
||||
std::string fname = GetFileName(num);
|
||||
|
||||
uint64_t file_size;
|
||||
env->GetFileSize(fname, &file_size);
|
||||
std::unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
|
||||
CuckooTableReader reader(
|
||||
options, std::move(read_file), file_size, test::Uint64Comparator(),
|
||||
nullptr);
|
||||
ASSERT_OK(reader.status());
|
||||
const UserCollectedProperties user_props =
|
||||
reader.GetTableProperties()->user_collected_properties;
|
||||
const uint32_t num_hash_fun = *reinterpret_cast<const uint32_t*>(
|
||||
user_props.at(CuckooTablePropertyNames::kNumHashTable).data());
|
||||
fprintf(stderr, "With %lu items and hash table ratio %f, number of hash"
|
||||
" functions used: %u.\n", num, hash_ratio, num_hash_fun);
|
||||
user_props.at(CuckooTablePropertyNames::kNumHashFunc).data());
|
||||
const uint64_t table_size = *reinterpret_cast<const uint64_t*>(
|
||||
user_props.at(CuckooTablePropertyNames::kHashTableSize).data());
|
||||
fprintf(stderr, "With %" PRIu64 " items, utilization is %.2f%%, number of"
|
||||
" hash functions: %u.\n", num, num * 100.0 / (table_size), num_hash_fun);
|
||||
ReadOptions r_options;
|
||||
for (auto& key : keys) {
|
||||
int cnt = 0;
|
||||
ASSERT_OK(reader.Get(r_options, Slice(key), &cnt, CheckValue, nullptr));
|
||||
ASSERT_EQ(1, cnt);
|
||||
}
|
||||
// Shuffle Keys.
|
||||
std::random_shuffle(keys.begin(), keys.end());
|
||||
|
||||
uint64_t time_now = env->NowMicros();
|
||||
for (uint64_t i = 0; i < num_reads; ++i) {
|
||||
reader.Get(r_options, Slice(keys[i % num]), nullptr, DoNothing, nullptr);
|
||||
uint64_t start_time = env->NowMicros();
|
||||
if (batch_size > 0) {
|
||||
for (uint64_t i = 0; i < num; i += batch_size) {
|
||||
for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
|
||||
reader.Prepare(Slice(reinterpret_cast<char*>(&j), 16));
|
||||
}
|
||||
for (uint64_t j = i; j < i+batch_size && j < num; ++j) {
|
||||
reader.Get(r_options, Slice(reinterpret_cast<char*>(&j), 16),
|
||||
nullptr, DoNothing, nullptr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (uint64_t i = 0; i < num; i++) {
|
||||
reader.Get(r_options, Slice(reinterpret_cast<char*>(&i), 16), nullptr,
|
||||
DoNothing, nullptr);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "Time taken per op is %.3fus\n",
|
||||
(env->NowMicros() - time_now)*1.0/num_reads);
|
||||
float time_per_op = (env->NowMicros() - start_time) * 1.0 / num;
|
||||
fprintf(stderr,
|
||||
"Time taken per op is %.3fus (%.1f Mqps) with batch size of %u\n",
|
||||
time_per_op, 1.0 / time_per_op, batch_size);
|
||||
}
|
||||
} // namespace.
|
||||
|
||||
TEST(CuckooReaderTest, Performance) {
|
||||
// In all these tests, num_reads = 10*num_items.
|
||||
TEST(CuckooReaderTest, TestReadPerformance) {
|
||||
if (!FLAGS_enable_perf) {
|
||||
return;
|
||||
}
|
||||
BM_CuckooRead(100000, 8, 4, 1000000, 0.9);
|
||||
BM_CuckooRead(1000000, 8, 4, 10000000, 0.9);
|
||||
BM_CuckooRead(1000000, 8, 4, 10000000, 0.7);
|
||||
BM_CuckooRead(10000000, 8, 4, 100000000, 0.9);
|
||||
BM_CuckooRead(10000000, 8, 4, 100000000, 0.7);
|
||||
double hash_ratio = 0.95;
|
||||
// These numbers are chosen to have a hash utilizaiton % close to
|
||||
// 0.9, 0.75, 0.6 and 0.5 respectively.
|
||||
// They all create 128 M buckets.
|
||||
std::vector<uint64_t> nums = {120*1000*1000, 100*1000*1000, 80*1000*1000,
|
||||
70*1000*1000};
|
||||
#ifndef NDEBUG
|
||||
fprintf(stdout,
|
||||
"WARNING: Not compiled with DNDEBUG. Performance tests may be slow.\n");
|
||||
#endif
|
||||
std::vector<std::string> keys;
|
||||
GetKeys(*std::max_element(nums.begin(), nums.end()), &keys);
|
||||
for (uint64_t num : nums) {
|
||||
if (FLAGS_write || !Env::Default()->FileExists(GetFileName(num))) {
|
||||
WriteFile(keys, num, hash_ratio);
|
||||
}
|
||||
ReadKeys(num, 0);
|
||||
ReadKeys(num, 10);
|
||||
ReadKeys(num, 25);
|
||||
ReadKeys(num, 50);
|
||||
ReadKeys(num, 100);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+14
-15
@@ -22,10 +22,11 @@ static const size_t kFilterBaseLg = 11;
|
||||
static const size_t kFilterBase = 1 << kFilterBaseLg;
|
||||
|
||||
FilterBlockBuilder::FilterBlockBuilder(const Options& opt,
|
||||
const BlockBasedTableOptions& table_opt,
|
||||
const Comparator* internal_comparator)
|
||||
: policy_(opt.filter_policy),
|
||||
: policy_(table_opt.filter_policy.get()),
|
||||
prefix_extractor_(opt.prefix_extractor.get()),
|
||||
whole_key_filtering_(opt.whole_key_filtering),
|
||||
whole_key_filtering_(table_opt.whole_key_filtering),
|
||||
comparator_(internal_comparator) {}
|
||||
|
||||
void FilterBlockBuilder::StartBlock(uint64_t block_offset) {
|
||||
@@ -70,20 +71,14 @@ void FilterBlockBuilder::AddKey(const Slice& key) {
|
||||
}
|
||||
|
||||
// add prefix to filter if needed
|
||||
if (prefix_extractor_ && prefix_extractor_->InDomain(ExtractUserKey(key))) {
|
||||
// If prefix_extractor_, this filter_block layer assumes we only
|
||||
// operate on internal keys.
|
||||
Slice user_key = ExtractUserKey(key);
|
||||
if (prefix_extractor_ && prefix_extractor_->InDomain(key)) {
|
||||
// this assumes prefix(prefix(key)) == prefix(key), as the last
|
||||
// entry in entries_ may be either a key or prefix, and we use
|
||||
// prefix(last entry) to get the prefix of the last key.
|
||||
if (prev.size() == 0 ||
|
||||
!SamePrefix(user_key, ExtractUserKey(prev))) {
|
||||
Slice prefix = prefix_extractor_->Transform(user_key);
|
||||
InternalKey internal_prefix_tmp(prefix, 0, kTypeValue);
|
||||
Slice internal_prefix = internal_prefix_tmp.Encode();
|
||||
if (prev.size() == 0 || !SamePrefix(key, prev)) {
|
||||
Slice prefix = prefix_extractor_->Transform(key);
|
||||
start_.push_back(entries_.size());
|
||||
entries_.append(internal_prefix.data(), internal_prefix.size());
|
||||
entries_.append(prefix.data(), prefix.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +126,11 @@ void FilterBlockBuilder::GenerateFilter() {
|
||||
}
|
||||
|
||||
FilterBlockReader::FilterBlockReader(
|
||||
const Options& opt, const Slice& contents, bool delete_contents_after_use)
|
||||
: policy_(opt.filter_policy),
|
||||
const Options& opt, const BlockBasedTableOptions& table_opt,
|
||||
const Slice& contents, bool delete_contents_after_use)
|
||||
: policy_(table_opt.filter_policy.get()),
|
||||
prefix_extractor_(opt.prefix_extractor.get()),
|
||||
whole_key_filtering_(opt.whole_key_filtering),
|
||||
whole_key_filtering_(table_opt.whole_key_filtering),
|
||||
data_(nullptr),
|
||||
offset_(nullptr),
|
||||
num_(0),
|
||||
@@ -184,4 +180,7 @@ bool FilterBlockReader::MayMatch(uint64_t block_offset, const Slice& entry) {
|
||||
return true; // Errors are treated as potential matches
|
||||
}
|
||||
|
||||
size_t FilterBlockReader::ApproximateMemoryUsage() const {
|
||||
return num_ * 4 + 5 + (offset_ - data_);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user