mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
71
Commits
3.3.fb
...
rocksdb-3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91c01485d1 | ||
|
|
f04356e660 | ||
|
|
f6784766db | ||
|
|
7e8bb71dd0 | ||
|
|
1bd3431f7c | ||
|
|
f6ca226c17 | ||
|
|
40fa8a4cd5 | ||
|
|
4a8f0c957c | ||
|
|
62f9b071ff | ||
|
|
d650612c4c | ||
|
|
28b367db15 | ||
|
|
c8e70e6bf8 | ||
|
|
6480717a26 | ||
|
|
f780f35b06 | ||
|
|
0754d4cb3b | ||
|
|
07a7d870b8 | ||
|
|
4b61a3d67d | ||
|
|
10fc6c7d25 | ||
|
|
41a697256f | ||
|
|
e5f6980d99 | ||
|
|
da9274574f | ||
|
|
6296330417 | ||
|
|
b5c4c0b86b | ||
|
|
6e7e3e45ff | ||
|
|
00f56dfa28 | ||
|
|
0e1b4787ed | ||
|
|
f82d4a2498 | ||
|
|
1053358a84 | ||
|
|
0ff183a0d9 | ||
|
|
e6de02103a | ||
|
|
250f035782 | ||
|
|
25682d1596 | ||
|
|
2d3d63597a | ||
|
|
f6272e3055 | ||
|
|
d19aa25594 | ||
|
|
ae7743f226 | ||
|
|
f6b7e1ed1a | ||
|
|
bbe2e91d00 | ||
|
|
cf3da899b0 | ||
|
|
f6f1533c6f | ||
|
|
50c2dcb78f | ||
|
|
9c0d84d240 | ||
|
|
c1a90b0848 | ||
|
|
80a94d0517 | ||
|
|
37693704d6 | ||
|
|
54f4e2f188 | ||
|
|
f4c49aeade | ||
|
|
9d70cce047 | ||
|
|
92d73cbe78 | ||
|
|
052ddbe0e2 | ||
|
|
2f289dccf3 | ||
|
|
aac941b3f0 | ||
|
|
0d57e3ad7d | ||
|
|
3178510153 | ||
|
|
296e340753 | ||
|
|
bc6b2ab401 | ||
|
|
87895c62db | ||
|
|
1c9f190ae3 | ||
|
|
0418e66e2a | ||
|
|
c11d604ab3 | ||
|
|
1614284eff | ||
|
|
d916593ead | ||
|
|
0abaed2e08 | ||
|
|
20c056306b | ||
|
|
dd6c444822 | ||
|
|
d2a727c182 | ||
|
|
591c2a3b4b | ||
|
|
ee6b35e55a | ||
|
|
46f0f6ddd5 | ||
|
|
178fd6f9db | ||
|
|
3b97ee96c4 |
No files matched your search
+13
@@ -1,5 +1,18 @@
|
||||
# Rocksdb Change Log
|
||||
|
||||
### Unreleased
|
||||
### New Features
|
||||
* Support Multiple DB paths in universal style compactions
|
||||
* Add feature of storing plain table index and bloom filter in SST file.
|
||||
|
||||
### Public API changes
|
||||
* DBOptions.db_paths now is a vector of a DBPath structure which indicates both of path and target size
|
||||
* NewPlainTableFactory instead of bunch of parameters now accepts PlainTableOptions, which is defined in include/rocksdb/table.h
|
||||
* Moved include/utilities/*.h to include/rocksdb/utilities/*.h
|
||||
* 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.
|
||||
|
||||
## 3.3.0 (7/10/2014)
|
||||
### New Features
|
||||
* Added JSON API prototype.
|
||||
|
||||
@@ -49,7 +49,7 @@ else
|
||||
PLATFORM_CCFLAGS += $(JEMALLOC_INCLUDE) -DHAVE_JEMALLOC
|
||||
endif
|
||||
|
||||
WARNING_FLAGS = -Wall -Werror
|
||||
WARNING_FLAGS = -Wall -Werror -Wsign-compare
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual
|
||||
|
||||
@@ -69,6 +69,7 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
TESTS = \
|
||||
db_test \
|
||||
db_iter_test \
|
||||
block_hash_index_test \
|
||||
autovector_test \
|
||||
column_family_test \
|
||||
@@ -106,6 +107,7 @@ TESTS = \
|
||||
backupable_db_test \
|
||||
document_db_test \
|
||||
json_document_test \
|
||||
spatial_db_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
file_indexer_test \
|
||||
@@ -113,8 +115,11 @@ TESTS = \
|
||||
deletefile_test \
|
||||
table_test \
|
||||
thread_local_test \
|
||||
geodb_test \
|
||||
rate_limiter_test
|
||||
geodb_test \
|
||||
rate_limiter_test \
|
||||
cuckoo_table_builder_test \
|
||||
options_test \
|
||||
cuckoo_table_reader_test
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@@ -122,6 +127,7 @@ TOOLS = \
|
||||
db_stress \
|
||||
ldb \
|
||||
db_repl_stress \
|
||||
options_test \
|
||||
blob_store_bench
|
||||
|
||||
PROGRAMS = db_bench signal_test table_reader_bench log_and_apply_bench $(TOOLS)
|
||||
@@ -149,7 +155,7 @@ SHARED = $(SHARED1)
|
||||
else
|
||||
# Update db.h if you change these.
|
||||
SHARED_MAJOR = 3
|
||||
SHARED_MINOR = 3
|
||||
SHARED_MINOR = 4
|
||||
SHARED1 = ${LIBNAME}.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1).$(SHARED_MAJOR)
|
||||
SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
@@ -322,6 +328,9 @@ crc32c_test: util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_test: db/db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
db_iter_test: db/db_iter_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/db_iter_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
log_write_bench: util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS) -pg
|
||||
|
||||
@@ -352,6 +361,9 @@ document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARN
|
||||
json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
@@ -406,6 +418,15 @@ deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
geodb_test: utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
cuckoo_table_builder_test: table/cuckoo_table_builder_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) table/cuckoo_table_builder_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
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)
|
||||
|
||||
options_test: util/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/options_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
$(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
rm -f $@
|
||||
$(AR) -rs $@ $(MEMENVOBJECTS)
|
||||
@@ -440,18 +461,20 @@ ldb: tools/ldb.o $(LIBOBJECTS)
|
||||
|
||||
JNI_NATIVE_SOURCES = ./java/rocksjni/*.cc
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ROCKSDBJNILIB = ./java/librocksdbjni.so
|
||||
ROCKSDBJNILIB = librocksdbjni.so
|
||||
ROCKSDB_JAR = rocksdbjni.jar
|
||||
|
||||
ifeq ($(PLATFORM), OS_MACOSX)
|
||||
ROCKSDBJNILIB = ./java/librocksdbjni.jnilib
|
||||
ROCKSDBJNILIB = librocksdbjni.jnilib
|
||||
JAVA_INCLUDE = -I/System/Library/Frameworks/JavaVM.framework/Headers/
|
||||
endif
|
||||
|
||||
rocksdbjava:
|
||||
OPT="-fPIC -DNDEBUG -O2" $(MAKE) $(LIBRARY) -j32
|
||||
cd java;$(MAKE) java;
|
||||
rm -f $(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o $(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(LIBOBJECTS) $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
rm -f ./java/$(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(LIBOBJECTS) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
cd java;jar -cf $(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class HISTORY*.md $(ROCKSDBJNILIB)
|
||||
|
||||
jclean:
|
||||
cd java;$(MAKE) clean;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
# CC C Compiler path
|
||||
# CXX C++ Compiler path
|
||||
# PLATFORM_LDFLAGS Linker flags
|
||||
# JAVA_LDFLAGS Linker flags for RocksDBJava
|
||||
# PLATFORM_SHARED_EXT Extension for shared libraries
|
||||
# PLATFORM_SHARED_LDFLAGS Flags for building shared library
|
||||
# PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library
|
||||
@@ -157,6 +158,8 @@ case "$TARGET_OS" in
|
||||
exit 1
|
||||
esac
|
||||
|
||||
JAVA_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
|
||||
if test -z "$DO_NOT_RUN_BUILD_DETECT_VERSION"; then
|
||||
"$PWD/build_tools/build_detect_version"
|
||||
fi
|
||||
@@ -215,6 +218,7 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy"
|
||||
fi
|
||||
|
||||
|
||||
@@ -250,6 +254,7 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DZLIB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lz"
|
||||
fi
|
||||
|
||||
# Test whether bzip library is installed
|
||||
@@ -260,6 +265,7 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DBZIP2"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2"
|
||||
fi
|
||||
|
||||
# Test whether lz4 library is installed
|
||||
@@ -271,6 +277,7 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DLZ4"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4"
|
||||
fi
|
||||
|
||||
# Test whether numa is available
|
||||
@@ -282,6 +289,7 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DNUMA"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma"
|
||||
fi
|
||||
|
||||
# Test whether tcmalloc is available
|
||||
@@ -290,6 +298,7 @@ EOF
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -306,6 +315,7 @@ if test "$USE_HDFS"; then
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -ldl -lverify -ljava -ljvm"
|
||||
COMMON_FLAGS="$COMMON_FLAGS $HDFS_CCFLAGS"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $HDFS_LDFLAGS"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS $HDFS_LDFLAGS"
|
||||
fi
|
||||
|
||||
# if Intel SSE instruction set is supported, set USE_SSE=" -msse -msse4.2 "
|
||||
@@ -320,6 +330,7 @@ echo "CC=$CC" >> "$OUTPUT"
|
||||
echo "CXX=$CXX" >> "$OUTPUT"
|
||||
echo "PLATFORM=$PLATFORM" >> "$OUTPUT"
|
||||
echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT"
|
||||
echo "VALGRIND_VER=$VALGRIND_VER" >> "$OUTPUT"
|
||||
echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> "$OUTPUT"
|
||||
echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> "$OUTPUT"
|
||||
|
||||
@@ -1428,9 +1428,13 @@ void rocksdb_options_set_plain_table_factory(
|
||||
double hash_table_ratio, size_t index_sparseness) {
|
||||
static rocksdb::TableFactory* factory = 0;
|
||||
if (!factory) {
|
||||
factory = rocksdb::NewPlainTableFactory(
|
||||
user_key_len, bloom_bits_per_key,
|
||||
hash_table_ratio, index_sparseness);
|
||||
rocksdb::PlainTableOptions options;
|
||||
options.user_key_len = user_key_len;
|
||||
options.bloom_bits_per_key = bloom_bits_per_key;
|
||||
options.hash_table_ratio = hash_table_ratio;
|
||||
options.index_sparseness = index_sparseness;
|
||||
|
||||
factory = rocksdb::NewPlainTableFactory(options);
|
||||
}
|
||||
opt->rep.table_factory.reset(factory);
|
||||
}
|
||||
|
||||
+5
-3
@@ -222,8 +222,8 @@ ColumnFamilyData::ColumnFamilyData(const std::string& dbname, uint32_t id,
|
||||
|
||||
// if dummy_versions is nullptr, then this is a dummy column family.
|
||||
if (dummy_versions != nullptr) {
|
||||
internal_stats_.reset(new InternalStats(
|
||||
options_.num_levels, db_options->env, db_options->statistics.get()));
|
||||
internal_stats_.reset(
|
||||
new InternalStats(options_.num_levels, db_options->env, this));
|
||||
table_cache_.reset(new TableCache(&options_, storage_options, table_cache));
|
||||
if (options_.compaction_style == kCompactionStyleUniversal) {
|
||||
compaction_picker_.reset(
|
||||
@@ -352,11 +352,13 @@ Compaction* ColumnFamilyData::PickCompaction(LogBuffer* log_buffer) {
|
||||
}
|
||||
|
||||
Compaction* ColumnFamilyData::CompactRange(int input_level, int output_level,
|
||||
uint32_t output_path_id,
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
return compaction_picker_->CompactRange(current_, input_level, output_level,
|
||||
begin, end, compaction_end);
|
||||
output_path_id, begin, end,
|
||||
compaction_end);
|
||||
}
|
||||
|
||||
SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
|
||||
|
||||
+2
-1
@@ -185,7 +185,8 @@ class ColumnFamilyData {
|
||||
// See documentation in compaction_picker.h
|
||||
Compaction* PickCompaction(LogBuffer* log_buffer);
|
||||
Compaction* CompactRange(int input_level, int output_level,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
uint32_t output_path_id, const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end);
|
||||
|
||||
CompactionPicker* compaction_picker() { return compaction_picker_.get(); }
|
||||
|
||||
@@ -857,6 +857,7 @@ TEST(ColumnFamilyTest, NewIteratorsTest) {
|
||||
TEST(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
ASSERT_OK(Put(0, "a", "b"));
|
||||
ASSERT_OK(Put(1, "foo", "bla"));
|
||||
ASSERT_OK(Put(2, "foo", "blabla"));
|
||||
ASSERT_OK(Put(3, "foo", "blablabla"));
|
||||
@@ -870,6 +871,29 @@ TEST(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
ASSERT_EQ("bla", Get(1, "foo"));
|
||||
ASSERT_EQ("blablablabla", Get(2, "foo"));
|
||||
|
||||
|
||||
// test newiterators
|
||||
{
|
||||
std::vector<Iterator*> iterators;
|
||||
ASSERT_OK(db_->NewIterators(ReadOptions(), handles_, &iterators));
|
||||
for (auto it : iterators) {
|
||||
it->SeekToFirst();
|
||||
}
|
||||
ASSERT_EQ(IterStatus(iterators[0]), "a->b");
|
||||
ASSERT_EQ(IterStatus(iterators[1]), "foo->bla");
|
||||
ASSERT_EQ(IterStatus(iterators[2]), "foo->blablablabla");
|
||||
for (auto it : iterators) {
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(IterStatus(iterators[0]), "(invalid)");
|
||||
ASSERT_EQ(IterStatus(iterators[1]), "(invalid)");
|
||||
ASSERT_EQ(IterStatus(iterators[2]), "(invalid)");
|
||||
|
||||
for (auto it : iterators) {
|
||||
delete it;
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
// can't open dropped column family
|
||||
Status s = OpenReadOnly({"default", "one", "two"});
|
||||
|
||||
+53
-39
@@ -26,14 +26,14 @@ static uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
Compaction::Compaction(Version* input_version, int level, int out_level,
|
||||
Compaction::Compaction(Version* input_version, int start_level, int out_level,
|
||||
uint64_t target_file_size,
|
||||
uint64_t max_grandparent_overlap_bytes,
|
||||
uint32_t output_path_id,
|
||||
CompressionType output_compression, bool seek_compaction,
|
||||
bool deletion_compaction)
|
||||
: level_(level),
|
||||
out_level_(out_level),
|
||||
: start_level_(start_level),
|
||||
output_level_(out_level),
|
||||
max_output_file_size_(target_file_size),
|
||||
max_grandparent_overlap_bytes_(max_grandparent_overlap_bytes),
|
||||
input_version_(input_version),
|
||||
@@ -61,6 +61,12 @@ Compaction::Compaction(Version* input_version, int level, int out_level,
|
||||
for (int i = 0; i < number_levels_; i++) {
|
||||
level_ptrs_[i] = 0;
|
||||
}
|
||||
int num_levels = output_level_ - start_level_ + 1;
|
||||
input_levels_.resize(num_levels);
|
||||
inputs_.resize(num_levels);
|
||||
for (int i = 0; i < num_levels; ++i) {
|
||||
inputs_[i].level = start_level_ + i;
|
||||
}
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
@@ -75,43 +81,50 @@ Compaction::~Compaction() {
|
||||
}
|
||||
}
|
||||
|
||||
void Compaction::GenerateFileLevels() {
|
||||
input_levels_.resize(num_input_levels());
|
||||
for (int which = 0; which < num_input_levels(); which++) {
|
||||
DoGenerateFileLevel(&input_levels_[which], inputs_[which].files, &arena_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Compaction::IsTrivialMove() const {
|
||||
// Avoid a move if there is lots of overlapping grandparent data.
|
||||
// Otherwise, the move could create a parent file that will require
|
||||
// a very expensive merge later on.
|
||||
// If level_== out_level_, the purpose is to force compaction filter to be
|
||||
// applied to that level, and thus cannot be a trivia move.
|
||||
return (level_ != out_level_ &&
|
||||
// If start_level_== output_level_, the purpose is to force compaction
|
||||
// filter to be applied to that level, and thus cannot be a trivia move.
|
||||
return (start_level_ != output_level_ &&
|
||||
num_input_levels() == 2 &&
|
||||
num_input_files(0) == 1 &&
|
||||
num_input_files(1) == 0 &&
|
||||
TotalFileSize(grandparents_) <= max_grandparent_overlap_bytes_);
|
||||
}
|
||||
|
||||
bool Compaction::IsDeletionCompaction() const { return deletion_compaction_; }
|
||||
|
||||
void Compaction::AddInputDeletions(VersionEdit* edit) {
|
||||
for (int which = 0; which < 2; which++) {
|
||||
for (int which = 0; which < num_input_levels(); which++) {
|
||||
for (size_t i = 0; i < inputs_[which].size(); i++) {
|
||||
edit->DeleteFile(level_ + which, inputs_[which][i]->fd.GetNumber());
|
||||
edit->DeleteFile(level(which), inputs_[which][i]->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Compaction::IsBaseLevelForKey(const Slice& user_key) {
|
||||
bool Compaction::KeyNotExistsBeyondOutputLevel(const Slice& user_key) {
|
||||
assert(cfd_->options()->compaction_style != kCompactionStyleFIFO);
|
||||
if (cfd_->options()->compaction_style == kCompactionStyleUniversal) {
|
||||
return bottommost_level_;
|
||||
}
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = level_ + 2; lvl < number_levels_; lvl++) {
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files = input_version_->files_[lvl];
|
||||
for (; level_ptrs_[lvl] < files.size(); ) {
|
||||
FileMetaData* f = files[level_ptrs_[lvl]];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so definitely not base level
|
||||
// Key falls in this file's range, so definitely
|
||||
// exists beyond output level
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -149,19 +162,18 @@ bool Compaction::ShouldStopBefore(const Slice& internal_key) {
|
||||
}
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool value) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
std::vector<FileMetaData*> v = inputs_[i];
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (int i = 0; i < num_input_levels(); i++) {
|
||||
for (unsigned int j = 0; j < inputs_[i].size(); j++) {
|
||||
assert(value ? !inputs_[i][j]->being_compacted :
|
||||
inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = value;
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted :
|
||||
inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = mark_as_compacted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is this compaction producing files at the bottommost level?
|
||||
void Compaction::SetupBottomMostLevel(bool isManual) {
|
||||
void Compaction::SetupBottomMostLevel(bool is_manual) {
|
||||
assert(cfd_->options()->compaction_style != kCompactionStyleFIFO);
|
||||
if (cfd_->options()->compaction_style == kCompactionStyleUniversal) {
|
||||
// If universal compaction style is used and manual
|
||||
@@ -170,13 +182,14 @@ void Compaction::SetupBottomMostLevel(bool isManual) {
|
||||
// run. We can safely set bottommost_level_ = true.
|
||||
// If it is not manual compaction, then bottommost_level_
|
||||
// is already set when the Compaction was created.
|
||||
if (isManual) {
|
||||
if (is_manual) {
|
||||
bottommost_level_ = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
bottommost_level_ = true;
|
||||
for (int i = output_level() + 1; i < number_levels_; i++) {
|
||||
// checks whether there are files living beyond the output_level.
|
||||
for (int i = output_level_ + 1; i < number_levels_; i++) {
|
||||
if (input_version_->NumLevelFiles(i) > 0) {
|
||||
bottommost_level_ = false;
|
||||
break;
|
||||
@@ -202,7 +215,7 @@ void Compaction::ReleaseCompactionFiles(Status status) {
|
||||
}
|
||||
|
||||
void Compaction::ResetNextCompactionIndex() {
|
||||
input_version_->ResetNextCompactionIndex(level_);
|
||||
input_version_->ResetNextCompactionIndex(start_level_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -229,24 +242,23 @@ void Compaction::Summary(char* output, int len) {
|
||||
int write =
|
||||
snprintf(output, len, "Base version %" PRIu64
|
||||
" Base level %d, seek compaction:%d, inputs: [",
|
||||
input_version_->GetVersionNumber(), level_, seek_compaction_);
|
||||
input_version_->GetVersionNumber(),
|
||||
start_level_, seek_compaction_);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
write += InputSummary(inputs_[0], output + write, len - write);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
write += snprintf(output + write, len - write, "], [");
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
|
||||
write += InputSummary(inputs_[1], output + write, len - write);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
for (int level = 0; level < num_input_levels(); ++level) {
|
||||
if (level > 0) {
|
||||
write += snprintf(output + write, len - write, "], [");
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
write += InputSummary(inputs_[level].files, output + write, len - write);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(output + write, len - write, "]");
|
||||
@@ -259,8 +271,10 @@ uint64_t Compaction::OutputFilePreallocationSize() {
|
||||
preallocation_size =
|
||||
cfd_->compaction_picker()->MaxFileSizeForLevel(output_level());
|
||||
} else {
|
||||
for (const auto& f : inputs_[0]) {
|
||||
preallocation_size += f->fd.GetFileSize();
|
||||
for (int level = 0; level < num_input_levels(); ++level) {
|
||||
for (const auto& f : inputs_[level].files) {
|
||||
preallocation_size += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
|
||||
+106
-39
@@ -8,41 +8,90 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
#include "db/version_set.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// The structure that manages compaction input files associated
|
||||
// with the same physical level.
|
||||
struct CompactionInputFiles {
|
||||
int level;
|
||||
std::vector<FileMetaData*> files;
|
||||
inline bool empty() const { return files.empty(); }
|
||||
inline size_t size() const { return files.size(); }
|
||||
inline void clear() { files.clear(); }
|
||||
inline FileMetaData* operator[](int i) const { return files[i]; }
|
||||
};
|
||||
|
||||
class Version;
|
||||
class ColumnFamilyData;
|
||||
|
||||
// A Compaction encapsulates information about a compaction.
|
||||
class Compaction {
|
||||
public:
|
||||
// No copying allowed
|
||||
Compaction(const Compaction&) = delete;
|
||||
void operator=(const Compaction&) = delete;
|
||||
|
||||
~Compaction();
|
||||
|
||||
// Return the level that is being compacted. Inputs from "level"
|
||||
// will be merged.
|
||||
int level() const { return level_; }
|
||||
// Returns the level associated to the specified compaction input level.
|
||||
// If compaction_input_level is not specified, then input_level is set to 0.
|
||||
int level(int compaction_input_level = 0) const {
|
||||
return inputs_[compaction_input_level].level;
|
||||
}
|
||||
|
||||
// Outputs will go to this level
|
||||
int output_level() const { return out_level_; }
|
||||
int output_level() const { return output_level_; }
|
||||
|
||||
// Returns the number of input levels in this compaction.
|
||||
int num_input_levels() const { return inputs_.size(); }
|
||||
|
||||
// Return the object that holds the edits to the descriptor done
|
||||
// by this compaction.
|
||||
VersionEdit* edit() { return edit_; }
|
||||
VersionEdit* edit() const { return edit_; }
|
||||
|
||||
// "which" must be either 0 or 1
|
||||
int num_input_files(int which) const { return inputs_[which].size(); }
|
||||
// Returns the number of input files associated to the specified
|
||||
// compaction input level.
|
||||
// The function will return 0 if when "compaction_input_level" < 0
|
||||
// or "compaction_input_level" >= "num_input_levels()".
|
||||
int num_input_files(size_t compaction_input_level) const {
|
||||
if (compaction_input_level < inputs_.size()) {
|
||||
return inputs_[compaction_input_level].size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns input version of the compaction
|
||||
Version* input_version() const { return input_version_; }
|
||||
|
||||
// Returns the ColumnFamilyData associated with the compaction.
|
||||
ColumnFamilyData* column_family_data() const { return cfd_; }
|
||||
|
||||
// Return the ith input file at "level()+which" ("which" must be 0 or 1).
|
||||
FileMetaData* input(int which, int i) const { return inputs_[which][i]; }
|
||||
// Returns the file meta data of the 'i'th input file at the
|
||||
// specified compaction input level.
|
||||
// REQUIREMENT: "compaction_input_level" must be >= 0 and
|
||||
// < "input_levels()"
|
||||
FileMetaData* input(size_t compaction_input_level, int i) const {
|
||||
assert(compaction_input_level < inputs_.size());
|
||||
return inputs_[compaction_input_level][i];
|
||||
}
|
||||
|
||||
std::vector<FileMetaData*>* inputs(int which) { return &inputs_[which]; }
|
||||
// Returns the list of file meta data of the specified compaction
|
||||
// input level.
|
||||
// REQUIREMENT: "compaction_input_level" must be >= 0 and
|
||||
// < "input_levels()"
|
||||
std::vector<FileMetaData*>* const inputs(size_t compaction_input_level) {
|
||||
assert(compaction_input_level < inputs_.size());
|
||||
return &inputs_[compaction_input_level].files;
|
||||
}
|
||||
|
||||
// Returns the FileLevel of the specified compaction input level.
|
||||
FileLevel* input_levels(int compaction_input_level) {
|
||||
return &input_levels_[compaction_input_level];
|
||||
}
|
||||
|
||||
// Maximum size of files to build during this compaction.
|
||||
uint64_t MaxOutputFileSize() const { return max_output_file_size_; }
|
||||
@@ -53,20 +102,25 @@ class Compaction {
|
||||
// Whether need to write output file to second DB path.
|
||||
uint32_t GetOutputPathId() const { return output_path_id_; }
|
||||
|
||||
// Is this a trivial compaction that can be implemented by just
|
||||
// Generate input_levels_ from inputs_
|
||||
// Should be called when inputs_ is stable
|
||||
void GenerateFileLevels();
|
||||
|
||||
// Is this a trivial compaction that can be implemented by just
|
||||
// moving a single input file to the next level (no merging or splitting)
|
||||
bool IsTrivialMove() const;
|
||||
|
||||
// If true, just delete all files in inputs_[0]
|
||||
bool IsDeletionCompaction() const;
|
||||
// If true, then the comaction can be done by simply deleting input files.
|
||||
bool IsDeletionCompaction() const {
|
||||
return deletion_compaction_;
|
||||
}
|
||||
|
||||
// Add all inputs to this compaction as delete operations to *edit.
|
||||
void AddInputDeletions(VersionEdit* edit);
|
||||
|
||||
// Returns true if the information we have available guarantees that
|
||||
// the compaction is producing data in "level+1" for which no data exists
|
||||
// in levels greater than "level+1".
|
||||
bool IsBaseLevelForKey(const Slice& user_key);
|
||||
// Returns true if the available information we have guarantees that
|
||||
// the input "user_key" does not exist in any level beyond "output_level()".
|
||||
bool KeyNotExistsBeyondOutputLevel(const Slice& user_key);
|
||||
|
||||
// Returns true iff we should stop building the current output
|
||||
// before processing "internal_key".
|
||||
@@ -80,6 +134,9 @@ class Compaction {
|
||||
// Delete this compaction from the list of running compactions.
|
||||
void ReleaseCompactionFiles(Status status);
|
||||
|
||||
// Returns the summary of the compaction in "output" with maximum "len"
|
||||
// in bytes. The caller is responsible for the memory management of
|
||||
// "output".
|
||||
void Summary(char* output, int len);
|
||||
|
||||
// Return the score that was used to pick this compaction run.
|
||||
@@ -94,9 +151,9 @@ class Compaction {
|
||||
// Was this compaction triggered manually by the client?
|
||||
bool IsManualCompaction() { return is_manual_compaction_; }
|
||||
|
||||
// Returns a number of byte that the output file should be preallocated to
|
||||
// Returns the size in bytes that the output file should be preallocated to.
|
||||
// In level compaction, that is max_file_size_. In universal compaction, that
|
||||
// is the sum of all input file sizes
|
||||
// is the sum of all input file sizes.
|
||||
uint64_t OutputFilePreallocationSize();
|
||||
|
||||
private:
|
||||
@@ -105,39 +162,45 @@ class Compaction {
|
||||
friend class FIFOCompactionPicker;
|
||||
friend class LevelCompactionPicker;
|
||||
|
||||
Compaction(Version* input_version, int level, int out_level,
|
||||
Compaction(Version* input_version, int start_level, int out_level,
|
||||
uint64_t target_file_size, uint64_t max_grandparent_overlap_bytes,
|
||||
uint32_t output_path_id, CompressionType output_compression,
|
||||
bool seek_compaction = false, bool deletion_compaction = false);
|
||||
|
||||
int level_;
|
||||
int out_level_; // levels to which output files are stored
|
||||
const int start_level_; // the lowest level to be compacted
|
||||
const int output_level_; // levels to which output files are stored
|
||||
uint64_t max_output_file_size_;
|
||||
uint64_t max_grandparent_overlap_bytes_;
|
||||
Version* input_version_;
|
||||
VersionEdit* edit_;
|
||||
int number_levels_;
|
||||
ColumnFamilyData* cfd_;
|
||||
Arena arena_; // Arena used to allocate space for file_levels_
|
||||
|
||||
uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
bool seek_compaction_;
|
||||
// if true, just delete files in inputs_[0]
|
||||
// If true, then the comaction can be done by simply deleting input files.
|
||||
bool deletion_compaction_;
|
||||
|
||||
// Each compaction reads inputs from "level_" and "level_+1"
|
||||
std::vector<FileMetaData*> inputs_[2]; // The two sets of inputs
|
||||
// Compaction input files organized by level.
|
||||
autovector<CompactionInputFiles> inputs_;
|
||||
|
||||
// A copy of inputs_, organized more closely in memory
|
||||
autovector<FileLevel, 2> input_levels_;
|
||||
|
||||
// State used to check for number of of overlapping grandparent files
|
||||
// (parent == level_ + 1, grandparent == level_ + 2)
|
||||
// (grandparent == "output_level_ + 1")
|
||||
// This vector is updated by Version::GetOverlappingInputs().
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
size_t grandparent_index_; // Index in grandparent_starts_
|
||||
bool seen_key_; // Some output key has been seen
|
||||
size_t grandparent_index_; // Index in grandparent_starts_
|
||||
bool seen_key_; // Some output key has been seen
|
||||
uint64_t overlapped_bytes_; // Bytes of overlap between current output
|
||||
// and grandparent files
|
||||
int base_index_; // index of the file in files_[level_]
|
||||
int parent_index_; // index of some file with same range in files_[level_+1]
|
||||
double score_; // score that was used to pick this compaction.
|
||||
// and grandparent files
|
||||
int base_index_; // index of the file in files_[start_level_]
|
||||
int parent_index_; // index of some file with same range in
|
||||
// files_[start_level_+1]
|
||||
double score_; // score that was used to pick this compaction.
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
@@ -147,17 +210,21 @@ class Compaction {
|
||||
// Is this compaction requested by the client?
|
||||
bool is_manual_compaction_;
|
||||
|
||||
// level_ptrs_ holds indices into input_version_->levels_: our state
|
||||
// is that we are positioned at one of the file ranges for each
|
||||
// higher level than the ones involved in this compaction (i.e. for
|
||||
// all L >= level_ + 2).
|
||||
// "level_ptrs_" holds indices into "input_version_->levels_", where each
|
||||
// index remembers which file of an associated level we are currently used
|
||||
// to check KeyNotExistsBeyondOutputLevel() for deletion operation.
|
||||
// As it is for checking KeyNotExistsBeyondOutputLevel(), it only
|
||||
// records indices for all levels beyond "output_level_".
|
||||
std::vector<size_t> level_ptrs_;
|
||||
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool);
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
|
||||
// Initialize whether compaction producing files at the bottommost level
|
||||
void SetupBottomMostLevel(bool isManual);
|
||||
// Initialize whether the compaction is producing files at the
|
||||
// bottommost level.
|
||||
//
|
||||
// @see BottomMostLevel()
|
||||
void SetupBottomMostLevel(bool is_manual);
|
||||
|
||||
// In case of compaction error, reset the nextIndex that is used
|
||||
// to pick up the next file to be compacted from files_by_size_
|
||||
|
||||
+90
-39
@@ -194,14 +194,15 @@ bool CompactionPicker::ExpandWhileOverlapping(Compaction* c) {
|
||||
size_t old_size;
|
||||
do {
|
||||
old_size = c->inputs_[0].size();
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
GetRange(c->inputs_[0].files, &smallest, &largest);
|
||||
c->inputs_[0].clear();
|
||||
c->input_version_->GetOverlappingInputs(
|
||||
level, &smallest, &largest, &c->inputs_[0], hint_index, &hint_index);
|
||||
level, &smallest, &largest, &c->inputs_[0].files,
|
||||
hint_index, &hint_index);
|
||||
} while(c->inputs_[0].size() > old_size);
|
||||
|
||||
// Get the new range
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
GetRange(c->inputs_[0].files, &smallest, &largest);
|
||||
|
||||
// If, after the expansion, there are files that are already under
|
||||
// compaction, then we must drop/cancel this compaction.
|
||||
@@ -211,7 +212,7 @@ bool CompactionPicker::ExpandWhileOverlapping(Compaction* c) {
|
||||
"[%s] ExpandWhileOverlapping() failure because zero input files",
|
||||
c->column_family_data()->GetName().c_str());
|
||||
}
|
||||
if (c->inputs_[0].empty() || FilesInCompaction(c->inputs_[0]) ||
|
||||
if (c->inputs_[0].empty() || FilesInCompaction(c->inputs_[0].files) ||
|
||||
(c->level() != c->output_level() &&
|
||||
ParentRangeInCompaction(c->input_version_, &smallest, &largest, level,
|
||||
&parent_index))) {
|
||||
@@ -267,16 +268,17 @@ void CompactionPicker::SetupOtherInputs(Compaction* c) {
|
||||
InternalKey smallest, largest;
|
||||
|
||||
// Get the range one last time.
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
GetRange(c->inputs_[0].files, &smallest, &largest);
|
||||
|
||||
// Populate the set of next-level files (inputs_[1]) to include in compaction
|
||||
c->input_version_->GetOverlappingInputs(level + 1, &smallest, &largest,
|
||||
&c->inputs_[1], c->parent_index_,
|
||||
&c->parent_index_);
|
||||
c->input_version_->GetOverlappingInputs(
|
||||
level + 1, &smallest, &largest,
|
||||
&c->inputs_[1].files, c->parent_index_,
|
||||
&c->parent_index_);
|
||||
|
||||
// Get entire range covered by compaction
|
||||
InternalKey all_start, all_limit;
|
||||
GetRange(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
|
||||
GetRange(c->inputs_[0].files, c->inputs_[1].files, &all_start, &all_limit);
|
||||
|
||||
// See if we can further grow the number of inputs in "level" without
|
||||
// changing the number of "level+1" files we pick up. We also choose NOT
|
||||
@@ -287,8 +289,8 @@ void CompactionPicker::SetupOtherInputs(Compaction* c) {
|
||||
std::vector<FileMetaData*> expanded0;
|
||||
c->input_version_->GetOverlappingInputs(
|
||||
level, &all_start, &all_limit, &expanded0, c->base_index_, nullptr);
|
||||
const uint64_t inputs0_size = TotalCompensatedFileSize(c->inputs_[0]);
|
||||
const uint64_t inputs1_size = TotalCompensatedFileSize(c->inputs_[1]);
|
||||
const uint64_t inputs0_size = TotalCompensatedFileSize(c->inputs_[0].files);
|
||||
const uint64_t inputs1_size = TotalCompensatedFileSize(c->inputs_[1].files);
|
||||
const uint64_t expanded0_size = TotalCompensatedFileSize(expanded0);
|
||||
uint64_t limit = ExpandedCompactionByteSizeLimit(level);
|
||||
if (expanded0.size() > c->inputs_[0].size() &&
|
||||
@@ -312,9 +314,10 @@ void CompactionPicker::SetupOtherInputs(Compaction* c) {
|
||||
inputs1_size);
|
||||
smallest = new_start;
|
||||
largest = new_limit;
|
||||
c->inputs_[0] = expanded0;
|
||||
c->inputs_[1] = expanded1;
|
||||
GetRange(c->inputs_[0], c->inputs_[1], &all_start, &all_limit);
|
||||
c->inputs_[0].files = expanded0;
|
||||
c->inputs_[1].files = expanded1;
|
||||
GetRange(c->inputs_[0].files, c->inputs_[1].files,
|
||||
&all_start, &all_limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,9 +330,9 @@ void CompactionPicker::SetupOtherInputs(Compaction* c) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Compaction* CompactionPicker::CompactRange(Version* version, int input_level,
|
||||
int output_level,
|
||||
uint32_t output_path_id,
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
@@ -369,12 +372,13 @@ Compaction* CompactionPicker::CompactRange(Version* version, int input_level,
|
||||
}
|
||||
}
|
||||
}
|
||||
Compaction* c = new Compaction(version, input_level, output_level,
|
||||
MaxFileSizeForLevel(output_level),
|
||||
MaxGrandParentOverlapBytes(input_level), 0,
|
||||
GetCompressionType(*options_, output_level));
|
||||
assert(output_path_id < static_cast<uint32_t>(options_->db_paths.size()));
|
||||
Compaction* c = new Compaction(
|
||||
version, input_level, output_level, MaxFileSizeForLevel(output_level),
|
||||
MaxGrandParentOverlapBytes(input_level), output_path_id,
|
||||
GetCompressionType(*options_, output_level));
|
||||
|
||||
c->inputs_[0] = inputs;
|
||||
c->inputs_[0].files = inputs;
|
||||
if (ExpandWhileOverlapping(c) == false) {
|
||||
delete c;
|
||||
Log(options_->info_log,
|
||||
@@ -441,18 +445,18 @@ Compaction* LevelCompactionPicker::PickCompaction(Version* version,
|
||||
if (level == 0) {
|
||||
assert(compactions_in_progress_[0].empty());
|
||||
InternalKey smallest, largest;
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
GetRange(c->inputs_[0].files, &smallest, &largest);
|
||||
// Note that the next call will discard the file we placed in
|
||||
// c->inputs_[0] earlier and replace it with an overlapping set
|
||||
// which will include the picked file.
|
||||
c->inputs_[0].clear();
|
||||
c->input_version_->GetOverlappingInputs(0, &smallest, &largest,
|
||||
&c->inputs_[0]);
|
||||
&c->inputs_[0].files);
|
||||
|
||||
// If we include more L0 files in the same compaction run it can
|
||||
// cause the 'smallest' and 'largest' key to get extended to a
|
||||
// larger range. So, re-invoke GetRange to get the new key range
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
GetRange(c->inputs_[0].files, &smallest, &largest);
|
||||
if (ParentRangeInCompaction(c->input_version_, &smallest, &largest, level,
|
||||
&c->parent_index_)) {
|
||||
delete c;
|
||||
@@ -533,7 +537,7 @@ Compaction* LevelCompactionPicker::PickCompactionBySize(Version* version,
|
||||
level, &parent_index)) {
|
||||
continue;
|
||||
}
|
||||
c->inputs_[0].push_back(f);
|
||||
c->inputs_[0].files.push_back(f);
|
||||
c->base_index_ = index;
|
||||
c->parent_index_ = parent_index;
|
||||
break;
|
||||
@@ -615,11 +619,11 @@ Compaction* UniversalCompactionPicker::PickCompaction(Version* version,
|
||||
|
||||
// Is the earliest file part of this compaction?
|
||||
FileMetaData* last_file = c->input_version_->files_[level].back();
|
||||
c->bottommost_level_ = c->inputs_[0].back() == last_file;
|
||||
c->bottommost_level_ = c->inputs_[0].files.back() == last_file;
|
||||
|
||||
// update statistics
|
||||
MeasureTime(options_->statistics.get(), NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs_[0].size());
|
||||
MeasureTime(options_->statistics.get(),
|
||||
NUM_FILES_IN_SINGLE_COMPACTION, c->inputs_[0].size());
|
||||
|
||||
// mark all the files that are being compacted
|
||||
c->MarkFilesBeingCompacted(true);
|
||||
@@ -635,6 +639,37 @@ Compaction* UniversalCompactionPicker::PickCompaction(Version* version,
|
||||
return c;
|
||||
}
|
||||
|
||||
uint32_t UniversalCompactionPicker::GetPathId(const Options& options,
|
||||
uint64_t file_size) {
|
||||
// Two conditions need to be satisfied:
|
||||
// (1) the target path needs to be able to hold the file's size
|
||||
// (2) Total size left in this and previous paths need to be not
|
||||
// smaller than expected future file size before this new file is
|
||||
// compacted, which is estimated based on size_ratio.
|
||||
// For example, if now we are compacting files of size (1, 1, 2, 4, 8),
|
||||
// we will make sure the target file, probably with size of 16, will be
|
||||
// placed in a path so that eventually when new files are generated and
|
||||
// compacted to (1, 1, 2, 4, 8, 16), all those files can be stored in or
|
||||
// before the path we chose.
|
||||
//
|
||||
// TODO(sdong): now the case of multiple column families is not
|
||||
// considered in this algorithm. So the target size can be violated in
|
||||
// that case. We need to improve it.
|
||||
uint64_t accumulated_size = 0;
|
||||
uint64_t future_size =
|
||||
file_size * (100 - options.compaction_options_universal.size_ratio) / 100;
|
||||
uint32_t p = 0;
|
||||
for (; p < options.db_paths.size() - 1; p++) {
|
||||
uint64_t target_size = options.db_paths[p].target_size;
|
||||
if (target_size > file_size &&
|
||||
accumulated_size + (target_size - file_size) > future_size) {
|
||||
return p;
|
||||
}
|
||||
accumulated_size += target_size;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
//
|
||||
// Consider compaction files based on their size differences with
|
||||
// the next file in time order.
|
||||
@@ -765,14 +800,21 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t estimated_total_size = 0;
|
||||
for (unsigned int i = 0; i < first_index_after; i++) {
|
||||
estimated_total_size += files[i]->fd.GetFileSize();
|
||||
}
|
||||
uint32_t path_id = GetPathId(*options_, estimated_total_size);
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
version, level, level, MaxFileSizeForLevel(level), LLONG_MAX, 0,
|
||||
version, level, level, MaxFileSizeForLevel(level), LLONG_MAX, path_id,
|
||||
GetCompressionType(*options_, level, enable_compression));
|
||||
c->score_ = score;
|
||||
|
||||
for (unsigned int i = start_index; i < first_index_after; i++) {
|
||||
FileMetaData* f = c->input_version_->files_[level][i];
|
||||
c->inputs_[0].push_back(f);
|
||||
c->inputs_[0].files.push_back(f);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: Picking file %s[%d] "
|
||||
"with size %" PRIu64 " (compensated size %" PRIu64 ")\n",
|
||||
@@ -865,15 +907,22 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
}
|
||||
assert(start_index >= 0 && start_index < files.size() - 1);
|
||||
|
||||
// Estimate total file size
|
||||
uint64_t estimated_total_size = 0;
|
||||
for (unsigned int loop = start_index; loop < files.size(); loop++) {
|
||||
estimated_total_size += files[loop]->fd.GetFileSize();
|
||||
}
|
||||
uint32_t path_id = GetPathId(*options_, estimated_total_size);
|
||||
|
||||
// create a compaction request
|
||||
// We always compact all the files, so always compress.
|
||||
Compaction* c =
|
||||
new Compaction(version, level, level, MaxFileSizeForLevel(level),
|
||||
LLONG_MAX, 0, GetCompressionType(*options_, level));
|
||||
LLONG_MAX, path_id, GetCompressionType(*options_, level));
|
||||
c->score_ = score;
|
||||
for (unsigned int loop = start_index; loop < files.size(); loop++) {
|
||||
f = c->input_version_->files_[level][loop];
|
||||
c->inputs_[0].push_back(f);
|
||||
c->inputs_[0].files.push_back(f);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: size amp picking file %" PRIu64 "[%d] "
|
||||
"with size %" PRIu64 " (compensated size %" PRIu64 ")",
|
||||
@@ -918,7 +967,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(Version* version,
|
||||
ritr != version->files_[0].rend(); ++ritr) {
|
||||
auto f = *ritr;
|
||||
total_size -= f->compensated_file_size;
|
||||
c->inputs_[0].push_back(f);
|
||||
c->inputs_[0].files.push_back(f);
|
||||
char tmp_fsize[16];
|
||||
AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
|
||||
LogToBuffer(log_buffer, "[%s] FIFO compaction: picking file %" PRIu64
|
||||
@@ -935,17 +984,19 @@ Compaction* FIFOCompactionPicker::PickCompaction(Version* version,
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::CompactRange(Version* version,
|
||||
int input_level,
|
||||
int output_level,
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
Compaction* FIFOCompactionPicker::CompactRange(
|
||||
Version* version, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end) {
|
||||
assert(input_level == 0);
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, options_->info_log.get());
|
||||
auto c = PickCompaction(version, &log_buffer);
|
||||
Compaction* c = PickCompaction(version, &log_buffer);
|
||||
if (c != nullptr) {
|
||||
assert(output_path_id < static_cast<uint32_t>(options_->db_paths.size()));
|
||||
c->output_path_id_ = output_path_id;
|
||||
}
|
||||
log_buffer.FlushBufferToLog();
|
||||
return c;
|
||||
}
|
||||
|
||||
+28
-2
@@ -48,10 +48,15 @@ class CompactionPicker {
|
||||
// Client is responsible for compaction_end storage -- when called,
|
||||
// *compaction_end should point to valid InternalKey!
|
||||
virtual Compaction* CompactRange(Version* version, int input_level,
|
||||
int output_level, const InternalKey* begin,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end);
|
||||
|
||||
// Given the current number of levels, returns the lowest allowed level
|
||||
// for compaction input.
|
||||
virtual int MaxInputLevel(int current_num_levels) const = 0;
|
||||
|
||||
// Free up the files that participated in a compaction
|
||||
void ReleaseCompactionFiles(Compaction* c, Status status);
|
||||
|
||||
@@ -135,6 +140,11 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
virtual Compaction* PickCompaction(Version* version,
|
||||
LogBuffer* log_buffer) override;
|
||||
|
||||
// The maxinum allowed input level. Always return 0.
|
||||
virtual int MaxInputLevel(int current_num_levels) const override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Pick Universal compaction to limit read amplification
|
||||
Compaction* PickCompactionUniversalReadAmp(Version* version, double score,
|
||||
@@ -145,6 +155,10 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
// Pick Universal compaction to limit space amplification.
|
||||
Compaction* PickCompactionUniversalSizeAmp(Version* version, double score,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its estimated file
|
||||
// size.
|
||||
static uint32_t GetPathId(const Options& options, uint64_t file_size);
|
||||
};
|
||||
|
||||
class LevelCompactionPicker : public CompactionPicker {
|
||||
@@ -155,6 +169,12 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
virtual Compaction* PickCompaction(Version* version,
|
||||
LogBuffer* log_buffer) override;
|
||||
|
||||
// Returns current_num_levels - 2, meaning the last level cannot be
|
||||
// compaction input level.
|
||||
virtual int MaxInputLevel(int current_num_levels) const override {
|
||||
return current_num_levels - 2;
|
||||
}
|
||||
|
||||
private:
|
||||
// For the specfied level, pick a compaction.
|
||||
// Returns nullptr if there is no compaction to be done.
|
||||
@@ -173,9 +193,15 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
LogBuffer* log_buffer) override;
|
||||
|
||||
virtual Compaction* CompactRange(Version* version, int input_level,
|
||||
int output_level, const InternalKey* begin,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
InternalKey** compaction_end) override;
|
||||
|
||||
// The maxinum allowed input level. Always return 0.
|
||||
virtual int MaxInputLevel(int current_num_levels) const override {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+24
-2
@@ -242,6 +242,10 @@ DEFINE_int64(cache_size, -1, "Number of bytes to use as a cache of uncompressed"
|
||||
DEFINE_int32(block_size, rocksdb::Options().block_size,
|
||||
"Number of bytes in a block.");
|
||||
|
||||
DEFINE_int32(block_restart_interval, rocksdb::Options().block_restart_interval,
|
||||
"Number of keys between restart points "
|
||||
"for delta encoding of keys.");
|
||||
|
||||
DEFINE_int64(compressed_cache_size, -1,
|
||||
"Number of bytes to use as a cache of compressed data.");
|
||||
|
||||
@@ -538,6 +542,9 @@ 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_hash_search, false, "if use kHashSearch "
|
||||
"instead of kBinarySearch. "
|
||||
"This is valid if only we use BlockTable");
|
||||
|
||||
DEFINE_string(merge_operator, "", "The merge operator to use with the database."
|
||||
"If a new merge operator is specified, be sure to use fresh"
|
||||
@@ -1623,8 +1630,9 @@ class Benchmark {
|
||||
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_use_plain_table) {
|
||||
if (FLAGS_prefix_size != 0) {
|
||||
options.prefix_extractor.reset(
|
||||
NewFixedPrefixTransform(FLAGS_prefix_size));
|
||||
}
|
||||
@@ -1685,8 +1693,22 @@ class Benchmark {
|
||||
if (bloom_bits_per_key < 0) {
|
||||
bloom_bits_per_key = 0;
|
||||
}
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = FLAGS_key_size;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits_per_key;
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
options.table_factory = std::shared_ptr<TableFactory>(
|
||||
NewPlainTableFactory(FLAGS_key_size, bloom_bits_per_key, 0.75));
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
} else {
|
||||
BlockBasedTableOptions block_based_options;
|
||||
if (FLAGS_use_hash_search) {
|
||||
block_based_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
} else {
|
||||
block_based_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
||||
}
|
||||
options.table_factory.reset(
|
||||
NewBlockBasedTableFactory(block_based_options));
|
||||
}
|
||||
if (FLAGS_max_bytes_for_level_multiplier_additional_v.size() > 0) {
|
||||
if (FLAGS_max_bytes_for_level_multiplier_additional_v.size() !=
|
||||
|
||||
+159
-133
@@ -276,6 +276,7 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
DBOptions result = src;
|
||||
|
||||
// result.max_open_files means an "infinite" open files.
|
||||
if (result.max_open_files != -1) {
|
||||
ClipToRange(&result.max_open_files, 20, 1000000);
|
||||
@@ -299,7 +300,7 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
}
|
||||
|
||||
if (result.db_paths.size() == 0) {
|
||||
result.db_paths.push_back(dbname);
|
||||
result.db_paths.emplace_back(dbname, std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -334,6 +335,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
: env_(options.env),
|
||||
dbname_(dbname),
|
||||
options_(SanitizeOptions(dbname, options)),
|
||||
stats_(options_.statistics.get()),
|
||||
db_lock_(nullptr),
|
||||
mutex_(options.use_adaptive_mutex),
|
||||
shutting_down_(nullptr),
|
||||
@@ -348,9 +350,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
bg_compaction_scheduled_(0),
|
||||
bg_manual_only_(0),
|
||||
bg_flush_scheduled_(0),
|
||||
bg_logstats_scheduled_(false),
|
||||
manual_compaction_(nullptr),
|
||||
logger_(nullptr),
|
||||
disable_delete_obsolete_files_(0),
|
||||
delete_obsolete_files_last_run_(options.env->NowMicros()),
|
||||
purge_wal_files_last_run_(0),
|
||||
@@ -381,16 +381,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname)
|
||||
DumpLeveldbBuildVersion(options_.info_log.get());
|
||||
options_.Dump(options_.info_log.get());
|
||||
|
||||
char name[100];
|
||||
Status s = env_->GetHostName(name, 100L);
|
||||
if (s.ok()) {
|
||||
host_name_ = name;
|
||||
} else {
|
||||
Log(options_.info_log, "Can't get hostname, use localhost as host name.");
|
||||
host_name_ = "localhost";
|
||||
}
|
||||
last_log_ts = 0;
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
}
|
||||
|
||||
@@ -411,9 +401,7 @@ DBImpl::~DBImpl() {
|
||||
|
||||
// Wait for background work to finish
|
||||
shutting_down_.Release_Store(this); // Any non-nullptr value is ok
|
||||
while (bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ ||
|
||||
bg_logstats_scheduled_) {
|
||||
while (bg_compaction_scheduled_ || bg_flush_scheduled_) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
@@ -524,9 +512,22 @@ void DBImpl::MaybeDumpStats() {
|
||||
// atomically. We could see more than one dump during one dump
|
||||
// period in rare cases.
|
||||
last_stats_dump_time_microsec_ = now_micros;
|
||||
|
||||
DBPropertyType cf_property_type = GetPropertyType("rocksdb.cfstats");
|
||||
DBPropertyType db_property_type = GetPropertyType("rocksdb.dbstats");
|
||||
std::string stats;
|
||||
GetProperty("rocksdb.stats", &stats);
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->internal_stats()->GetProperty(
|
||||
cf_property_type, "rocksdb.cfstats", &stats);
|
||||
}
|
||||
default_cf_internal_stats_->GetProperty(
|
||||
db_property_type, "rocksdb.dbstats", &stats);
|
||||
}
|
||||
Log(options_.info_log, "------- DUMPING STATS -------");
|
||||
Log(options_.info_log, "%s", stats.c_str());
|
||||
|
||||
PrintStatistics();
|
||||
}
|
||||
}
|
||||
@@ -1119,8 +1120,8 @@ Status DBImpl::Recover(
|
||||
return s;
|
||||
}
|
||||
|
||||
for (auto db_path : options_.db_paths) {
|
||||
s = env_->CreateDirIfMissing(db_path);
|
||||
for (auto& db_path : options_.db_paths) {
|
||||
s = env_->CreateDirIfMissing(db_path.path);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1171,6 +1172,7 @@ Status DBImpl::Recover(
|
||||
SequenceNumber max_sequence(0);
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
@@ -1220,8 +1222,7 @@ Status DBImpl::Recover(
|
||||
versions_->MarkFileNumberUsed(log);
|
||||
s = RecoverLogFile(log, &max_sequence, read_only);
|
||||
}
|
||||
SetTickerCount(options_.statistics.get(), SEQUENCE_NUMBER,
|
||||
versions_->LastSequence());
|
||||
SetTickerCount(stats_, SEQUENCE_NUMBER, versions_->LastSequence());
|
||||
}
|
||||
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
@@ -1428,13 +1429,14 @@ Status DBImpl::WriteLevel0TableForRecovery(ColumnFamilyData* cfd, MemTable* mem,
|
||||
meta.smallest_seqno, meta.largest_seqno);
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats;
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.files_out_levelnp1 = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES,
|
||||
meta.fd.GetFileSize());
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED, meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1478,7 +1480,7 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
delete iter;
|
||||
Log(options_.info_log,
|
||||
"[%s] Level-0 flush table #%" PRIu64 ": %" PRIu64 " bytes %s",
|
||||
cfd->GetName().c_str(), meta.fd.GetFileSize(), meta.fd.GetFileSize(),
|
||||
cfd->GetName().c_str(), meta.fd.GetNumber(), meta.fd.GetFileSize(),
|
||||
s.ToString().c_str());
|
||||
|
||||
if (!options_.disableDataSync) {
|
||||
@@ -1519,12 +1521,13 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
meta.smallest_seqno, meta.largest_seqno);
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats;
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES,
|
||||
meta.fd.GetFileSize());
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED, meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1585,8 +1588,6 @@ Status DBImpl::FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
LogToBuffer(log_buffer, "[%s] Level summary: %s\n", cfd->GetName().c_str(),
|
||||
cfd->current()->LevelSummary(&tmp));
|
||||
|
||||
MaybeScheduleLogDBDeployStats();
|
||||
|
||||
if (disable_delete_obsolete_files_ == 0) {
|
||||
// add to deletion state
|
||||
while (alive_log_files_.size() &&
|
||||
@@ -1611,7 +1612,12 @@ Status DBImpl::FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
|
||||
Status DBImpl::CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level, int target_level) {
|
||||
bool reduce_level, int target_level,
|
||||
uint32_t target_path_id) {
|
||||
if (target_path_id >= options_.db_paths.size()) {
|
||||
return Status::InvalidArgument("Invalid target path ID");
|
||||
}
|
||||
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
@@ -1637,9 +1643,10 @@ Status DBImpl::CompactRange(ColumnFamilyHandle* column_family,
|
||||
if (cfd->options()->compaction_style == kCompactionStyleUniversal ||
|
||||
cfd->options()->compaction_style == kCompactionStyleFIFO ||
|
||||
level == max_level_with_files) {
|
||||
s = RunManualCompaction(cfd, level, level, begin, end);
|
||||
s = RunManualCompaction(cfd, level, level, target_path_id, begin, end);
|
||||
} else {
|
||||
s = RunManualCompaction(cfd, level, level + 1, begin, end);
|
||||
s = RunManualCompaction(cfd, level, level + 1, target_path_id, begin,
|
||||
end);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
LogFlush(options_.info_log);
|
||||
@@ -1772,8 +1779,8 @@ SequenceNumber DBImpl::GetLatestSequenceNumber() const {
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, const Slice* begin,
|
||||
const Slice* end) {
|
||||
int output_level, uint32_t output_path_id,
|
||||
const Slice* begin, const Slice* end) {
|
||||
assert(input_level >= 0);
|
||||
|
||||
InternalKey begin_storage, end_storage;
|
||||
@@ -1782,6 +1789,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
manual.cfd = cfd;
|
||||
manual.input_level = input_level;
|
||||
manual.output_level = output_level;
|
||||
manual.output_path_id = output_path_id;
|
||||
manual.done = false;
|
||||
manual.in_progress = false;
|
||||
// For universal compaction, we enforce every manual compaction to compact
|
||||
@@ -1923,17 +1931,14 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
}
|
||||
|
||||
void DBImpl::RecordFlushIOStats() {
|
||||
RecordTick(options_.statistics.get(), FLUSH_WRITE_BYTES,
|
||||
iostats_context.bytes_written);
|
||||
RecordTick(stats_, FLUSH_WRITE_BYTES, iostats_context.bytes_written);
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
void DBImpl::RecordCompactionIOStats() {
|
||||
RecordTick(options_.statistics.get(), COMPACT_READ_BYTES,
|
||||
IOSTATS(bytes_read));
|
||||
RecordTick(stats_, COMPACT_READ_BYTES, IOSTATS(bytes_read));
|
||||
IOSTATS_RESET(bytes_read);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES,
|
||||
IOSTATS(bytes_written));
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, IOSTATS(bytes_written));
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
@@ -1995,9 +2000,8 @@ void DBImpl::BackgroundCallFlush() {
|
||||
// case this is an environmental problem and we do not want to
|
||||
// chew up resources for failed compactions for the duration of
|
||||
// the problem.
|
||||
uint64_t error_cnt = default_cf_handle_->cfd()
|
||||
->internal_stats()
|
||||
->BumpAndGetBackgroundErrorCount();
|
||||
uint64_t error_cnt =
|
||||
default_cf_internal_stats_->BumpAndGetBackgroundErrorCount();
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
Log(options_.info_log,
|
||||
@@ -2063,9 +2067,8 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
// case this is an environmental problem and we do not want to
|
||||
// chew up resources for failed compactions for the duration of
|
||||
// the problem.
|
||||
uint64_t error_cnt = default_cf_handle_->cfd()
|
||||
->internal_stats()
|
||||
->BumpAndGetBackgroundErrorCount();
|
||||
uint64_t error_cnt =
|
||||
default_cf_internal_stats_->BumpAndGetBackgroundErrorCount();
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
log_buffer.FlushBufferToLog();
|
||||
@@ -2102,8 +2105,6 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
|
||||
bg_compaction_scheduled_--;
|
||||
|
||||
MaybeScheduleLogDBDeployStats();
|
||||
|
||||
versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
|
||||
|
||||
// Previous compaction may have produced too many files in a level,
|
||||
@@ -2178,8 +2179,9 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
if (is_manual) {
|
||||
ManualCompaction* m = manual_compaction_;
|
||||
assert(m->in_progress);
|
||||
c.reset(m->cfd->CompactRange(m->input_level, m->output_level, m->begin,
|
||||
m->end, &manual_end));
|
||||
c.reset(m->cfd->CompactRange(m->input_level, m->output_level,
|
||||
m->output_path_id, m->begin, m->end,
|
||||
&manual_end));
|
||||
if (!c) {
|
||||
m->done = true;
|
||||
}
|
||||
@@ -2199,7 +2201,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
c.reset(cfd->PickCompaction(log_buffer));
|
||||
if (c != nullptr) {
|
||||
// update statistics
|
||||
MeasureTime(options_.statistics.get(), NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
break;
|
||||
}
|
||||
@@ -2424,12 +2426,10 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState* compact,
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !options_.disableDataSync) {
|
||||
if (options_.use_fsync) {
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
COMPACTION_OUTFILE_SYNC_MICROS, false);
|
||||
StopWatch sw(env_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
s = compact->outfile->Fsync();
|
||||
} else {
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
COMPACTION_OUTFILE_SYNC_MICROS, false);
|
||||
StopWatch sw(env_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
s = compact->outfile->Sync();
|
||||
}
|
||||
}
|
||||
@@ -2661,7 +2661,7 @@ Status DBImpl::ProcessKeyValueCompaction(
|
||||
ParseInternalKey(key, &ikey);
|
||||
// no value associated with delete
|
||||
value.clear();
|
||||
RecordTick(options_.statistics.get(), COMPACTION_KEY_DROP_USER);
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER);
|
||||
} else if (value_changed) {
|
||||
value = compaction_filter_value;
|
||||
}
|
||||
@@ -2685,10 +2685,10 @@ Status DBImpl::ProcessKeyValueCompaction(
|
||||
// TODO: why not > ?
|
||||
assert(last_sequence_for_key >= ikey.sequence);
|
||||
drop = true; // (A)
|
||||
RecordTick(options_.statistics.get(), COMPACTION_KEY_DROP_NEWER_ENTRY);
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY);
|
||||
} else if (ikey.type == kTypeDeletion &&
|
||||
ikey.sequence <= earliest_snapshot &&
|
||||
compact->compaction->IsBaseLevelForKey(ikey.user_key)) {
|
||||
compact->compaction->KeyNotExistsBeyondOutputLevel(ikey.user_key)) {
|
||||
// For this user key:
|
||||
// (1) there is no data in higher levels
|
||||
// (2) data in lower levels will have larger sequence numbers
|
||||
@@ -2697,7 +2697,7 @@ Status DBImpl::ProcessKeyValueCompaction(
|
||||
// few iterations of this loop (by rule (A) above).
|
||||
// Therefore this deletion marker is obsolete and can be dropped.
|
||||
drop = true;
|
||||
RecordTick(options_.statistics.get(), COMPACTION_KEY_DROP_OBSOLETE);
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE);
|
||||
} else if (ikey.type == kTypeMerge) {
|
||||
// We know the merge type entry is not hidden, otherwise we would
|
||||
// have hit (A)
|
||||
@@ -2887,7 +2887,7 @@ void DBImpl::CallCompactionFilterV2(CompactionState* compact,
|
||||
|
||||
// no value associated with delete
|
||||
compact->existing_value_buf_[i].clear();
|
||||
RecordTick(options_.statistics.get(), COMPACTION_KEY_DROP_USER);
|
||||
RecordTick(stats_, COMPACTION_KEY_DROP_USER);
|
||||
} else if (compact->value_changed_buf_[i]) {
|
||||
compact->existing_value_buf_[i] =
|
||||
Slice(compact->new_value_buf_[new_value_idx++]);
|
||||
@@ -2903,6 +2903,8 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
compact->CleanupMergedBuffer();
|
||||
bool prefix_initialized = false;
|
||||
|
||||
// Generate file_levels_ for compaction berfore making Iterator
|
||||
compact->compaction->GenerateFileLevels();
|
||||
int64_t imm_micros = 0; // Micros spent doing imm_ compactions
|
||||
ColumnFamilyData* cfd = compact->compaction->column_family_data();
|
||||
LogToBuffer(
|
||||
@@ -3132,11 +3134,11 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
db_directory_->Fsync();
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats;
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = env_->NowMicros() - start_micros - imm_micros;
|
||||
MeasureTime(options_.statistics.get(), COMPACTION_TIME, stats.micros);
|
||||
stats.files_in_leveln = compact->compaction->num_input_files(0);
|
||||
stats.files_in_levelnp1 = compact->compaction->num_input_files(1);
|
||||
MeasureTime(stats_, COMPACTION_TIME, stats.micros);
|
||||
|
||||
int num_output_files = compact->outputs.size();
|
||||
if (compact->builder != nullptr) {
|
||||
@@ -3162,8 +3164,8 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
cfd->internal_stats()->AddCompactionStats(compact->compaction->output_level(),
|
||||
stats);
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact->compaction->output_level(), stats);
|
||||
|
||||
// if there were any unused file number (mostly in case of
|
||||
// compaction error), free up the entry from pending_putputs
|
||||
@@ -3297,7 +3299,7 @@ void DBImpl::InstallSuperVersion(ColumnFamilyData* cfd,
|
||||
Status DBImpl::GetImpl(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value, bool* value_found) {
|
||||
StopWatch sw(env_, options_.statistics.get(), DB_GET, false);
|
||||
StopWatch sw(env_, stats_, DB_GET);
|
||||
PERF_TIMER_AUTO(get_snapshot_time);
|
||||
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
@@ -3332,16 +3334,16 @@ Status DBImpl::GetImpl(const ReadOptions& options,
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
if (sv->mem->Get(lkey, value, &s, merge_context, *cfd->options())) {
|
||||
// Done
|
||||
RecordTick(options_.statistics.get(), MEMTABLE_HIT);
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if (sv->imm->Get(lkey, value, &s, merge_context, *cfd->options())) {
|
||||
// Done
|
||||
RecordTick(options_.statistics.get(), MEMTABLE_HIT);
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else {
|
||||
PERF_TIMER_START(get_from_output_files_time);
|
||||
|
||||
sv->current->Get(options, lkey, value, &s, &merge_context, value_found);
|
||||
PERF_TIMER_STOP(get_from_output_files_time);
|
||||
RecordTick(options_.statistics.get(), MEMTABLE_MISS);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
|
||||
PERF_TIMER_START(get_post_process_time);
|
||||
@@ -3358,13 +3360,13 @@ Status DBImpl::GetImpl(const ReadOptions& options,
|
||||
sv->Cleanup();
|
||||
mutex_.Unlock();
|
||||
delete sv;
|
||||
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_CLEANUPS);
|
||||
RecordTick(stats_, NUMBER_SUPERVERSION_CLEANUPS);
|
||||
}
|
||||
RecordTick(options_.statistics.get(), NUMBER_SUPERVERSION_RELEASES);
|
||||
RecordTick(stats_, NUMBER_SUPERVERSION_RELEASES);
|
||||
}
|
||||
|
||||
RecordTick(options_.statistics.get(), NUMBER_KEYS_READ);
|
||||
RecordTick(options_.statistics.get(), BYTES_READ, value->size());
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
RecordTick(stats_, BYTES_READ, value->size());
|
||||
PERF_TIMER_STOP(get_post_process_time);
|
||||
return s;
|
||||
}
|
||||
@@ -3374,7 +3376,7 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
|
||||
StopWatch sw(env_, options_.statistics.get(), DB_MULTIGET, false);
|
||||
StopWatch sw(env_, stats_, DB_MULTIGET);
|
||||
PERF_TIMER_AUTO(get_snapshot_time);
|
||||
|
||||
SequenceNumber snapshot;
|
||||
@@ -3472,9 +3474,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
delete mgd.second;
|
||||
}
|
||||
|
||||
RecordTick(options_.statistics.get(), NUMBER_MULTIGET_CALLS);
|
||||
RecordTick(options_.statistics.get(), NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(options_.statistics.get(), NUMBER_MULTIGET_BYTES_READ, bytes_read);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_CALLS);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
|
||||
PERF_TIMER_STOP(get_post_process_time);
|
||||
|
||||
return stat_list;
|
||||
@@ -3789,12 +3791,16 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
}
|
||||
|
||||
if (!options.disableWAL) {
|
||||
RecordTick(options_.statistics.get(), WRITE_WITH_WAL, 1);
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::WRITE_WITH_WAL, 1);
|
||||
}
|
||||
|
||||
if (w.done) {
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::WRITE_DONE_BY_OTHER, 1);
|
||||
mutex_.Unlock();
|
||||
RecordTick(options_.statistics.get(), WRITE_DONE_BY_OTHER, 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER);
|
||||
return w.status;
|
||||
} else if (timed_out) {
|
||||
#ifndef NDEBUG
|
||||
@@ -3819,10 +3825,12 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
writers_.front()->cv.Signal();
|
||||
}
|
||||
mutex_.Unlock();
|
||||
RecordTick(options_.statistics.get(), WRITE_TIMEDOUT, 1);
|
||||
RecordTick(stats_, WRITE_TIMEDOUT);
|
||||
return Status::TimedOut();
|
||||
} else {
|
||||
RecordTick(options_.statistics.get(), WRITE_DONE_BY_SELF, 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_SELF);
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::WRITE_DONE_BY_SELF, 1);
|
||||
}
|
||||
|
||||
// Once reaches this point, the current writer "w" will try to do its write
|
||||
@@ -3908,17 +3916,16 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
WriteBatchInternal::SetSequence(updates, current_sequence);
|
||||
int my_batch_count = WriteBatchInternal::Count(updates);
|
||||
last_sequence += my_batch_count;
|
||||
const uint64_t batch_size = WriteBatchInternal::ByteSize(updates);
|
||||
// Record statistics
|
||||
RecordTick(options_.statistics.get(),
|
||||
NUMBER_KEYS_WRITTEN, my_batch_count);
|
||||
RecordTick(options_.statistics.get(),
|
||||
BYTES_WRITTEN,
|
||||
WriteBatchInternal::ByteSize(updates));
|
||||
RecordTick(stats_, NUMBER_KEYS_WRITTEN, my_batch_count);
|
||||
RecordTick(stats_, BYTES_WRITTEN, WriteBatchInternal::ByteSize(updates));
|
||||
if (options.disableWAL) {
|
||||
flush_on_destroy_ = true;
|
||||
}
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
uint64_t log_size = 0;
|
||||
if (!options.disableWAL) {
|
||||
PERF_TIMER_START(write_wal_time);
|
||||
Slice log_entry = WriteBatchInternal::Contents(updates);
|
||||
@@ -3926,14 +3933,15 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
total_log_size_ += log_entry.size();
|
||||
alive_log_files_.back().AddSize(log_entry.size());
|
||||
log_empty_ = false;
|
||||
RecordTick(options_.statistics.get(), WAL_FILE_SYNCED, 1);
|
||||
RecordTick(options_.statistics.get(), WAL_FILE_BYTES, log_entry.size());
|
||||
log_size = log_entry.size();
|
||||
RecordTick(stats_, WAL_FILE_SYNCED);
|
||||
RecordTick(stats_, WAL_FILE_BYTES, log_size);
|
||||
if (status.ok() && options.sync) {
|
||||
if (options_.use_fsync) {
|
||||
StopWatch(env_, options_.statistics.get(), WAL_FILE_SYNC_MICROS);
|
||||
StopWatch(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
status = log_->file()->Fsync();
|
||||
} else {
|
||||
StopWatch(env_, options_.statistics.get(), WAL_FILE_SYNC_MICROS);
|
||||
StopWatch(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
status = log_->file()->Sync();
|
||||
}
|
||||
}
|
||||
@@ -3954,12 +3962,22 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
|
||||
PERF_TIMER_STOP(write_memtable_time);
|
||||
|
||||
SetTickerCount(options_.statistics.get(), SEQUENCE_NUMBER,
|
||||
last_sequence);
|
||||
SetTickerCount(stats_, SEQUENCE_NUMBER, last_sequence);
|
||||
}
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
if (updates == &tmp_batch_) tmp_batch_.Clear();
|
||||
if (updates == &tmp_batch_) {
|
||||
tmp_batch_.Clear();
|
||||
}
|
||||
mutex_.Lock();
|
||||
// internal stats
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::BYTES_WRITTEN, batch_size);
|
||||
if (!options.disableWAL) {
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::WAL_FILE_SYNCED, 1);
|
||||
default_cf_internal_stats_->AddDBStats(
|
||||
InternalStats::WAL_FILE_BYTES, log_size);
|
||||
}
|
||||
if (status.ok()) {
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
@@ -3990,7 +4008,7 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
mutex_.Unlock();
|
||||
|
||||
if (status.IsTimedOut()) {
|
||||
RecordTick(options_.statistics.get(), WRITE_TIMEDOUT, 1);
|
||||
RecordTick(stats_, WRITE_TIMEDOUT);
|
||||
}
|
||||
|
||||
for (auto& sv : superversions_to_free) {
|
||||
@@ -4137,15 +4155,14 @@ Status DBImpl::MakeRoomForWrite(
|
||||
mutex_.Unlock();
|
||||
uint64_t delayed;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(), STALL_L0_SLOWDOWN_COUNT);
|
||||
StopWatch sw(env_, stats_, STALL_L0_SLOWDOWN_COUNT, &delayed);
|
||||
env_->SleepForMicroseconds(slowdown);
|
||||
delayed = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics.get(), STALL_L0_SLOWDOWN_MICROS, delayed);
|
||||
cfd->internal_stats()->RecordWriteStall(InternalStats::LEVEL0_SLOWDOWN,
|
||||
delayed);
|
||||
RecordTick(stats_, STALL_L0_SLOWDOWN_MICROS, delayed);
|
||||
allow_delay = false; // Do not delay a single write more than once
|
||||
mutex_.Lock();
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::LEVEL0_SLOWDOWN, delayed);
|
||||
delayed_writes_++;
|
||||
} else if (!force && !cfd->mem()->ShouldFlush()) {
|
||||
// There is room in current memtable
|
||||
@@ -4165,18 +4182,15 @@ Status DBImpl::MakeRoomForWrite(
|
||||
}
|
||||
uint64_t stall;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
STALL_MEMTABLE_COMPACTION_COUNT);
|
||||
StopWatch sw(env_, stats_, STALL_MEMTABLE_COMPACTION_COUNT, &stall);
|
||||
if (!has_timeout) {
|
||||
bg_cv_.Wait();
|
||||
} else {
|
||||
bg_cv_.TimedWait(expiration_time);
|
||||
}
|
||||
stall = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics.get(),
|
||||
STALL_MEMTABLE_COMPACTION_MICROS, stall);
|
||||
cfd->internal_stats()->RecordWriteStall(
|
||||
RecordTick(stats_, STALL_MEMTABLE_COMPACTION_MICROS, stall);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::MEMTABLE_COMPACTION, stall);
|
||||
} else if (cfd->NeedWaitForNumLevel0Files()) {
|
||||
DelayLoggingAndReset();
|
||||
@@ -4184,59 +4198,54 @@ Status DBImpl::MakeRoomForWrite(
|
||||
cfd->GetName().c_str());
|
||||
uint64_t stall;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
STALL_L0_NUM_FILES_COUNT);
|
||||
StopWatch sw(env_, stats_, STALL_L0_NUM_FILES_COUNT, &stall);
|
||||
if (!has_timeout) {
|
||||
bg_cv_.Wait();
|
||||
} else {
|
||||
bg_cv_.TimedWait(expiration_time);
|
||||
}
|
||||
stall = sw.ElapsedMicros();
|
||||
}
|
||||
RecordTick(options_.statistics.get(),
|
||||
STALL_L0_NUM_FILES_MICROS, stall);
|
||||
cfd->internal_stats()->RecordWriteStall(InternalStats::LEVEL0_NUM_FILES,
|
||||
stall);
|
||||
RecordTick(stats_, STALL_L0_NUM_FILES_MICROS, stall);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::LEVEL0_NUM_FILES, stall);
|
||||
} else if (allow_hard_rate_limit_delay && cfd->ExceedsHardRateLimit()) {
|
||||
// Delay a write when the compaction score for any level is too large.
|
||||
int max_level = cfd->current()->MaxCompactionScoreLevel();
|
||||
const int max_level = cfd->current()->MaxCompactionScoreLevel();
|
||||
score = cfd->current()->MaxCompactionScore();
|
||||
mutex_.Unlock();
|
||||
uint64_t delayed;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
HARD_RATE_LIMIT_DELAY_COUNT);
|
||||
StopWatch sw(env_, stats_, HARD_RATE_LIMIT_DELAY_COUNT, &delayed);
|
||||
env_->SleepForMicroseconds(1000);
|
||||
delayed = sw.ElapsedMicros();
|
||||
}
|
||||
cfd->internal_stats()->RecordLevelNSlowdown(max_level, delayed);
|
||||
// Make sure the following value doesn't round to zero.
|
||||
uint64_t rate_limit = std::max((delayed / 1000), (uint64_t) 1);
|
||||
rate_limit_delay_millis += rate_limit;
|
||||
RecordTick(options_.statistics.get(),
|
||||
RATE_LIMIT_DELAY_MILLIS, rate_limit);
|
||||
RecordTick(stats_, RATE_LIMIT_DELAY_MILLIS, rate_limit);
|
||||
if (cfd->options()->rate_limit_delay_max_milliseconds > 0 &&
|
||||
rate_limit_delay_millis >=
|
||||
(unsigned)cfd->options()->rate_limit_delay_max_milliseconds) {
|
||||
allow_hard_rate_limit_delay = false;
|
||||
}
|
||||
mutex_.Lock();
|
||||
cfd->internal_stats()->RecordLevelNSlowdown(max_level, delayed, false);
|
||||
} else if (allow_soft_rate_limit_delay && cfd->ExceedsSoftRateLimit()) {
|
||||
const int max_level = cfd->current()->MaxCompactionScoreLevel();
|
||||
score = cfd->current()->MaxCompactionScore();
|
||||
// Delay a write when the compaction score for any level is too large.
|
||||
// TODO: add statistics
|
||||
uint64_t slowdown = SlowdownAmount(score, cfd->options()->soft_rate_limit,
|
||||
cfd->options()->hard_rate_limit);
|
||||
uint64_t elapsed = 0;
|
||||
mutex_.Unlock();
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
SOFT_RATE_LIMIT_DELAY_COUNT);
|
||||
StopWatch sw(env_, stats_, SOFT_RATE_LIMIT_DELAY_COUNT, &elapsed);
|
||||
env_->SleepForMicroseconds(slowdown);
|
||||
slowdown = sw.ElapsedMicros();
|
||||
rate_limit_delay_millis += slowdown;
|
||||
}
|
||||
allow_soft_rate_limit_delay = false;
|
||||
mutex_.Lock();
|
||||
cfd->internal_stats()->RecordLevelNSlowdown(max_level, elapsed, true);
|
||||
} else {
|
||||
unique_ptr<WritableFile> lfile;
|
||||
log::Writer* new_log = nullptr;
|
||||
@@ -4360,8 +4369,16 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family,
|
||||
auto cfd = cfh->cfd();
|
||||
DBPropertyType property_type = GetPropertyType(property);
|
||||
MutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetProperty(property_type, property, value,
|
||||
cfd);
|
||||
return cfd->internal_stats()->GetProperty(property_type, property, value);
|
||||
}
|
||||
|
||||
bool DBImpl::GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
DBPropertyType property_type = GetPropertyType(property);
|
||||
MutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetIntProperty(property_type, property, value);
|
||||
}
|
||||
|
||||
void DBImpl::GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
@@ -4403,7 +4420,7 @@ Status DBImpl::GetUpdatesSince(
|
||||
SequenceNumber seq, unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) {
|
||||
|
||||
RecordTick(options_.statistics.get(), GET_UPDATES_SINCE_CALLS);
|
||||
RecordTick(stats_, GET_UPDATES_SINCE_CALLS);
|
||||
if (seq > versions_->LastSequence()) {
|
||||
return Status::NotFound("Requested sequence not yet written in the db");
|
||||
}
|
||||
@@ -4625,8 +4642,18 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
if (db_options.db_paths.size() > 1) {
|
||||
return Status::NotSupported(
|
||||
"More than one DB paths are not supported yet. ");
|
||||
for (auto& cfd : column_families) {
|
||||
if (cfd.options.compaction_style != kCompactionStyleUniversal) {
|
||||
return Status::NotSupported(
|
||||
"More than one DB paths are only supported in "
|
||||
"universal compaction style. ");
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options.db_paths.size() > 4) {
|
||||
return Status::NotSupported(
|
||||
"More than four DB paths are not supported yet. ");
|
||||
}
|
||||
}
|
||||
|
||||
*dbptr = nullptr;
|
||||
@@ -4645,8 +4672,8 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
DBImpl* impl = new DBImpl(db_options, dbname);
|
||||
Status s = impl->env_->CreateDirIfMissing(impl->options_.wal_dir);
|
||||
if (s.ok()) {
|
||||
for (auto path : impl->options_.db_paths) {
|
||||
s = impl->env_->CreateDirIfMissing(path);
|
||||
for (auto db_path : impl->options_.db_paths) {
|
||||
s = impl->env_->CreateDirIfMissing(db_path.path);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -4712,7 +4739,6 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
DBImpl::LogFileNumberSize(impl->logfile_number_));
|
||||
impl->DeleteObsoleteFiles();
|
||||
impl->MaybeScheduleFlushOrCompaction();
|
||||
impl->MaybeScheduleLogDBDeployStats();
|
||||
s = impl->db_directory_->Fsync();
|
||||
}
|
||||
}
|
||||
@@ -4815,14 +4841,14 @@ Status DestroyDB(const std::string& dbname, const Options& options) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto db_path : options.db_paths) {
|
||||
env->GetChildren(db_path, &filenames);
|
||||
for (auto& db_path : options.db_paths) {
|
||||
env->GetChildren(db_path.path, &filenames);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) &&
|
||||
type == kTableFile) { // Lock file will be deleted at end
|
||||
Status del = env->DeleteFile(db_path + "/" + filenames[i]);
|
||||
Status del = env->DeleteFile(db_path.path + "/" + filenames[i]);
|
||||
if (result.ok() && !del.ok()) {
|
||||
result = del;
|
||||
}
|
||||
|
||||
+10
-21
@@ -28,7 +28,6 @@
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/stats_logger.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/thread_local.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -97,13 +96,17 @@ class DBImpl : public DB {
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value);
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override;
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes);
|
||||
using DB::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);
|
||||
|
||||
using DB::NumberLevels;
|
||||
virtual int NumberLevels(ColumnFamilyHandle* column_family);
|
||||
@@ -146,8 +149,8 @@ class DBImpl : public DB {
|
||||
virtual Status GetDbIdentity(std::string& identity);
|
||||
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, const Slice* begin,
|
||||
const Slice* end);
|
||||
int output_level, uint32_t output_path_id,
|
||||
const Slice* begin, const Slice* end);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Extra methods (for testing) that are not in the public DB interface
|
||||
@@ -290,6 +293,7 @@ class DBImpl : public DB {
|
||||
const std::string dbname_;
|
||||
unique_ptr<VersionSet> versions_;
|
||||
const DBOptions options_;
|
||||
Statistics* stats_;
|
||||
|
||||
Iterator* NewInternalIterator(const ReadOptions&, ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version,
|
||||
@@ -362,13 +366,6 @@ class DBImpl : public DB {
|
||||
void RecordFlushIOStats();
|
||||
void RecordCompactionIOStats();
|
||||
|
||||
void MaybeScheduleLogDBDeployStats();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static void BGLogDBDeployStats(void* db);
|
||||
void LogDBDeployStats();
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
static void BGWorkCompaction(void* db);
|
||||
static void BGWorkFlush(void* db);
|
||||
@@ -478,12 +475,12 @@ class DBImpl : public DB {
|
||||
// * whenever bg_flush_scheduled_ value decreases (i.e. whenever a flush is
|
||||
// done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background flush or compaction
|
||||
// * whenever bg_logstats_scheduled_ turns to false
|
||||
port::CondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
unique_ptr<log::Writer> log_;
|
||||
bool log_empty_;
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number)
|
||||
@@ -502,8 +499,6 @@ class DBImpl : public DB {
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
std::string host_name_;
|
||||
|
||||
std::unique_ptr<Directory> db_directory_;
|
||||
|
||||
// Queue of writers.
|
||||
@@ -536,14 +531,12 @@ class DBImpl : public DB {
|
||||
// number of background memtable flush jobs, submitted to the HIGH pool
|
||||
int bg_flush_scheduled_;
|
||||
|
||||
// Has a background stats log thread scheduled?
|
||||
bool bg_logstats_scheduled_;
|
||||
|
||||
// Information for a manual compaction
|
||||
struct ManualCompaction {
|
||||
ColumnFamilyData* cfd;
|
||||
int input_level;
|
||||
int output_level;
|
||||
uint32_t output_path_id;
|
||||
bool done;
|
||||
Status status;
|
||||
bool in_progress; // compaction request being processed?
|
||||
@@ -556,10 +549,6 @@ class DBImpl : public DB {
|
||||
// Have we encountered a background error in paranoid mode?
|
||||
Status bg_error_;
|
||||
|
||||
std::unique_ptr<StatsLogger> logger_;
|
||||
|
||||
int64_t volatile last_log_ts;
|
||||
|
||||
// shall we disable deletion of obsolete files
|
||||
// if 0 the deletion is enabled.
|
||||
// if non-zero, files will not be getting deleted
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
cfd->options()->compaction_style == kCompactionStyleFIFO)
|
||||
? level
|
||||
: level + 1;
|
||||
return RunManualCompaction(cfd, level, output_level, begin, end);
|
||||
return RunManualCompaction(cfd, level, output_level, 0, begin, end);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait) {
|
||||
|
||||
+33
-3
@@ -76,12 +76,42 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& options,
|
||||
auto cfd = cfh->cfd();
|
||||
SuperVersion* super_version = cfd->GetSuperVersion()->Ref();
|
||||
SequenceNumber latest_snapshot = versions_->LastSequence();
|
||||
Iterator* internal_iter = NewInternalIterator(options, cfd, super_version);
|
||||
return NewDBIterator(
|
||||
env_, *cfd->options(), cfd->user_comparator(), internal_iter,
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, *cfd->options(), cfd->user_comparator(),
|
||||
(options.snapshot != nullptr
|
||||
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
|
||||
: latest_snapshot));
|
||||
auto internal_iter =
|
||||
NewInternalIterator(options, cfd, super_version, db_iter->GetArena());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
Status DBImplReadOnly::NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
}
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
SequenceNumber latest_snapshot = versions_->LastSequence();
|
||||
|
||||
for (auto cfh : column_families) {
|
||||
auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, *cfd->options(), cfd->user_comparator(),
|
||||
options.snapshot != nullptr
|
||||
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
|
||||
: latest_snapshot);
|
||||
auto internal_iter = NewInternalIterator(
|
||||
options, cfd, cfd->GetSuperVersion()->Ref(), db_iter->GetArena());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
iterators->push_back(db_iter);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DB::OpenForReadOnly(const Options& options, const std::string& dbname,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/stats_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -43,11 +42,8 @@ class DBImplReadOnly : public DBImpl {
|
||||
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
// TODO
|
||||
return Status::NotSupported("Not supported yet.");
|
||||
}
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators);
|
||||
|
||||
using DBImpl::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
|
||||
+228
-111
@@ -10,6 +10,7 @@
|
||||
#include "db/db_iter.h"
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -69,7 +70,7 @@ class DBIter: public Iterator {
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(options.statistics.get()) {
|
||||
RecordTick(statistics_, NO_ITERATORS, 1);
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
max_skip_ = options.max_sequential_skip_in_iterations;
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
@@ -109,9 +110,14 @@ class DBIter: public Iterator {
|
||||
virtual void SeekToLast();
|
||||
|
||||
private:
|
||||
void PrevInternal();
|
||||
void FindParseableKey(ParsedInternalKey* ikey, Direction direction);
|
||||
bool FindValueForCurrentKey();
|
||||
bool FindValueForCurrentKeyUsingSeek();
|
||||
void FindPrevUserKey();
|
||||
void FindNextUserKey();
|
||||
inline void FindNextUserEntry(bool skipping);
|
||||
void FindNextUserEntryInternal(bool skipping);
|
||||
void FindPrevUserEntry();
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
void MergeValuesNewToOld();
|
||||
|
||||
@@ -133,8 +139,8 @@ class DBIter: public Iterator {
|
||||
SequenceNumber const sequence_;
|
||||
|
||||
Status status_;
|
||||
IterKey saved_key_; // == current key when direction_==kReverse
|
||||
std::string saved_value_; // == current raw value when direction_==kReverse
|
||||
IterKey saved_key_;
|
||||
std::string saved_value_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
@@ -160,20 +166,11 @@ inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
void DBIter::Next() {
|
||||
assert(valid_);
|
||||
|
||||
if (direction_ == kReverse) { // Switch directions?
|
||||
if (direction_ == kReverse) {
|
||||
FindNextUserKey();
|
||||
direction_ = kForward;
|
||||
// iter_ is pointing just before the entries for this->key(),
|
||||
// so advance into the range of entries for this->key() and then
|
||||
// use the normal skipping code below.
|
||||
if (!iter_->Valid()) {
|
||||
iter_->SeekToFirst();
|
||||
} else {
|
||||
iter_->Next();
|
||||
}
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
saved_key_.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +182,6 @@ void DBIter::Next() {
|
||||
FindNextUserEntry(true /* skipping the current user key */);
|
||||
}
|
||||
|
||||
|
||||
// PRE: saved_key_ has the current user key if skipping
|
||||
// POST: saved_key_ should have the next user key if valid_,
|
||||
// if the current entry is a result of merge
|
||||
@@ -327,100 +323,228 @@ void DBIter::MergeValuesNewToOld() {
|
||||
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
|
||||
// Throw an exception now if merge_operator is provided
|
||||
// TODO: support backward iteration
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "Prev not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::Prev backward iteration not supported"
|
||||
" if merge_operator is provided");
|
||||
}
|
||||
|
||||
if (direction_ == kForward) { // Switch directions?
|
||||
// iter_ is pointing at the current entry. Scan backwards until
|
||||
// the key changes so we can use the normal reverse scanning code.
|
||||
assert(iter_->Valid()); // Otherwise valid_ would have been false
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
while (true) {
|
||||
iter_->Prev();
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
saved_key_.Clear();
|
||||
ClearSavedValue();
|
||||
return;
|
||||
}
|
||||
if (user_comparator_->Compare(ExtractUserKey(iter_->key()),
|
||||
saved_key_.GetKey()) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (direction_ == kForward) {
|
||||
FindPrevUserKey();
|
||||
direction_ = kReverse;
|
||||
}
|
||||
|
||||
FindPrevUserEntry();
|
||||
PrevInternal();
|
||||
}
|
||||
|
||||
void DBIter::FindPrevUserEntry() {
|
||||
assert(direction_ == kReverse);
|
||||
uint64_t num_skipped = 0;
|
||||
|
||||
ValueType value_type = kTypeDeletion;
|
||||
bool saved_key_valid = true;
|
||||
if (iter_->Valid()) {
|
||||
do {
|
||||
ParsedInternalKey ikey;
|
||||
if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
|
||||
if ((value_type != kTypeDeletion) &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) < 0) {
|
||||
// We encountered a non-deleted value in entries for previous keys,
|
||||
break;
|
||||
}
|
||||
value_type = ikey.type;
|
||||
if (value_type == kTypeDeletion) {
|
||||
saved_key_.Clear();
|
||||
ClearSavedValue();
|
||||
saved_key_valid = false;
|
||||
} else {
|
||||
Slice raw_value = iter_->value();
|
||||
if (saved_value_.capacity() > raw_value.size() + 1048576) {
|
||||
std::string empty;
|
||||
swap(empty, saved_value_);
|
||||
}
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
saved_value_.assign(raw_value.data(), raw_value.size());
|
||||
}
|
||||
} else {
|
||||
// In the case of ikey.sequence > sequence_, we might have already
|
||||
// iterated to a different user key.
|
||||
saved_key_valid = false;
|
||||
}
|
||||
num_skipped++;
|
||||
// If we have sequentially iterated via numerous keys and still not
|
||||
// found the prev user-key, then it is better to seek so that we can
|
||||
// avoid too many key comparisons. We seek to the first occurence of
|
||||
// our current key by looking for max sequence number.
|
||||
if (saved_key_valid && num_skipped > max_skip_) {
|
||||
num_skipped = 0;
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(),
|
||||
kMaxSequenceNumber,
|
||||
kValueTypeForSeek));
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
} else {
|
||||
iter_->Prev();
|
||||
}
|
||||
} while (iter_->Valid());
|
||||
void DBIter::PrevInternal() {
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value_type == kTypeDeletion) {
|
||||
// End
|
||||
ParsedInternalKey ikey;
|
||||
|
||||
while (iter_->Valid()) {
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
if (FindValueForCurrentKey()) {
|
||||
valid_ = true;
|
||||
if (!iter_->Valid()) {
|
||||
return;
|
||||
}
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0) {
|
||||
FindPrevUserKey();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!iter_->Valid()) {
|
||||
break;
|
||||
}
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0) {
|
||||
|
||||
FindPrevUserKey();
|
||||
}
|
||||
}
|
||||
// We haven't found any key - iterator is not valid
|
||||
assert(!iter_->Valid());
|
||||
valid_ = false;
|
||||
}
|
||||
|
||||
// This function checks, if the entry with biggest sequence_number <= sequence_
|
||||
// is non kTypeDeletion. If it's not, we save value in saved_value_
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
// Contains operands for merge operator.
|
||||
std::deque<std::string> operands;
|
||||
// last entry before merge (could be kTypeDeletion or kTypeValue)
|
||||
ValueType last_not_merge_type = kTypeDeletion;
|
||||
ValueType last_key_entry_type = kTypeDeletion;
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid() && ikey.sequence <= sequence_ &&
|
||||
(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0)) {
|
||||
// We iterate too much: let's use Seek() to avoid too much key comparisons
|
||||
if (num_skipped >= max_skip_) {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
operands.clear();
|
||||
saved_value_ = iter_->value().ToString();
|
||||
last_not_merge_type = kTypeValue;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
operands.clear();
|
||||
last_not_merge_type = kTypeDeletion;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
assert(user_merge_operator_ != nullptr);
|
||||
operands.push_back(iter_->value().ToString());
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0);
|
||||
iter_->Prev();
|
||||
++num_skipped;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
valid_ = false;
|
||||
return false;
|
||||
case kTypeMerge:
|
||||
if (last_not_merge_type == kTypeDeletion) {
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
||||
&saved_value_, logger_);
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
std::string last_put_value = saved_value_;
|
||||
Slice temp_slice(last_put_value);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &temp_slice,
|
||||
operands, &saved_value_, logger_);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
// do nothing - we've already has value in saved_value_
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function is used in FindValueForCurrentKey.
|
||||
// We use Seek() function instead of Prev() to find necessary value
|
||||
bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
|
||||
// assume there is at least one parseable key for this user key
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeDeletion) {
|
||||
if (ikey.type == kTypeValue) {
|
||||
saved_value_ = iter_->value().ToString();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
valid_ = false;
|
||||
saved_key_.Clear();
|
||||
ClearSavedValue();
|
||||
direction_ = kForward;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// kTypeMerge. We need to collect all kTypeMerge values and save them
|
||||
// in operands
|
||||
std::deque<std::string> operands;
|
||||
while (iter_->Valid() &&
|
||||
(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0) &&
|
||||
ikey.type == kTypeMerge) {
|
||||
operands.push_front(iter_->value().ToString());
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
|
||||
if (!iter_->Valid() ||
|
||||
(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) != 0) ||
|
||||
ikey.type == kTypeDeletion) {
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
||||
&saved_value_, logger_);
|
||||
|
||||
// Make iter_ valid and point to saved_key_
|
||||
if (!iter_->Valid() ||
|
||||
(user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) != 0)) {
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const Slice& value = iter_->value();
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &value, operands,
|
||||
&saved_value_, logger_);
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Used in Next to change directions
|
||||
// Go to next user key
|
||||
// Don't use Seek(),
|
||||
// because next user key will be very close
|
||||
void DBIter::FindNextUserKey() {
|
||||
if (!iter_->Valid()) {
|
||||
return;
|
||||
}
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) != 0) {
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
}
|
||||
|
||||
// Go to previous user_key
|
||||
void DBIter::FindPrevUserKey() {
|
||||
if (!iter_->Valid()) {
|
||||
return;
|
||||
}
|
||||
size_t num_skipped = 0;
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) == 0) {
|
||||
if (num_skipped >= max_skip_) {
|
||||
num_skipped = 0;
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
iter_->Seek(last_key.GetKey());
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
|
||||
iter_->Prev();
|
||||
++num_skipped;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip all unparseable keys
|
||||
void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) {
|
||||
while (iter_->Valid() && !ParseKey(ikey)) {
|
||||
if (direction == kReverse) {
|
||||
iter_->Prev();
|
||||
} else {
|
||||
iter_->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,20 +578,13 @@ void DBIter::SeekToFirst() {
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
// Throw an exception for now if merge_operator is provided
|
||||
// TODO: support backward iteration
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "SeekToLast not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::SeekToLast: backward iteration not"
|
||||
" supported if merge_operator is provided");
|
||||
}
|
||||
|
||||
direction_ = kReverse;
|
||||
ClearSavedValue();
|
||||
PERF_TIMER_AUTO(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
PERF_TIMER_STOP(seek_internal_seek_time);
|
||||
FindPrevUserEntry();
|
||||
|
||||
PrevInternal();
|
||||
}
|
||||
|
||||
Iterator* NewDBIterator(Env* env, const Options& options,
|
||||
|
||||
+1334
File diff suppressed because it is too large.
Load diff
@@ -1,95 +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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
void DBImpl::MaybeScheduleLogDBDeployStats() {
|
||||
// we did say maybe
|
||||
#ifndef ROCKSDB_LITE
|
||||
// There is a lock in the actual logger.
|
||||
if (!logger_ || options_.db_stats_log_interval < 0
|
||||
|| host_name_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bg_logstats_scheduled_ || shutting_down_.Acquire_Load()) {
|
||||
// Already scheduled
|
||||
} else {
|
||||
int64_t current_ts = 0;
|
||||
Status st = env_->GetCurrentTime(¤t_ts);
|
||||
if (!st.ok()) {
|
||||
return;
|
||||
}
|
||||
if ((current_ts - last_log_ts) < options_.db_stats_log_interval) {
|
||||
return;
|
||||
}
|
||||
last_log_ts = current_ts;
|
||||
bg_logstats_scheduled_ = true;
|
||||
env_->Schedule(&DBImpl::BGLogDBDeployStats, this);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::BGLogDBDeployStats(void* db) {
|
||||
DBImpl* db_inst = reinterpret_cast<DBImpl*>(db);
|
||||
db_inst->LogDBDeployStats();
|
||||
}
|
||||
|
||||
void DBImpl::LogDBDeployStats() {
|
||||
mutex_.Lock();
|
||||
|
||||
if (shutting_down_.Acquire_Load()) {
|
||||
bg_logstats_scheduled_ = false;
|
||||
bg_cv_.SignalAll();
|
||||
mutex_.Unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
char tmp_ver[100];
|
||||
sprintf(tmp_ver, "%d.%d", kMajorVersion, kMinorVersion);
|
||||
std::string version_info(tmp_ver);
|
||||
|
||||
uint64_t file_total_size = 0;
|
||||
uint32_t file_total_num = 0;
|
||||
Version* current = default_cf_handle_->cfd()->current();
|
||||
for (int i = 0; i < current->NumberLevels(); i++) {
|
||||
file_total_num += current->NumLevelFiles(i);
|
||||
file_total_size += current->NumLevelBytes(i);
|
||||
}
|
||||
|
||||
Version::LevelSummaryStorage scratch;
|
||||
const char* file_num_summary = current->LevelSummary(&scratch);
|
||||
std::string file_num_per_level(file_num_summary);
|
||||
std::string data_size_per_level(file_num_summary);
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
int64_t unix_ts;
|
||||
env_->GetCurrentTime(&unix_ts);
|
||||
|
||||
logger_->Log_Deploy_Stats(version_info, host_name_,
|
||||
db_absolute_path_, file_total_size, file_total_num, file_num_per_level,
|
||||
data_size_per_level, unix_ts);
|
||||
|
||||
mutex_.Lock();
|
||||
bg_logstats_scheduled_ = false;
|
||||
bg_cv_.SignalAll();
|
||||
mutex_.Unlock();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+317
-11
@@ -12,6 +12,7 @@
|
||||
#include <set>
|
||||
#include <unistd.h>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
@@ -370,8 +371,10 @@ class DBTest {
|
||||
~DBTest() {
|
||||
Close();
|
||||
Options options;
|
||||
options.db_paths.push_back(dbname_);
|
||||
options.db_paths.push_back(dbname_ + "_2");
|
||||
options.db_paths.emplace_back(dbname_, 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 0);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 0);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
delete env_;
|
||||
delete filter_policy_;
|
||||
@@ -2483,6 +2486,7 @@ TEST(DBTest, GetProperty) {
|
||||
|
||||
std::string big_value(1000000 * 2, 'x');
|
||||
std::string num;
|
||||
uint64_t int_num;
|
||||
SetPerfLevel(kEnableTime);
|
||||
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, "k1", big_value));
|
||||
@@ -2492,11 +2496,14 @@ TEST(DBTest, GetProperty) {
|
||||
ASSERT_EQ(num, "0");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
||||
ASSERT_EQ(num, "0");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ(num, "1");
|
||||
perf_context.Reset();
|
||||
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, "k2", big_value));
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
||||
ASSERT_EQ(num, "1");
|
||||
ASSERT_OK(dbfull()->Delete(writeOpt, "k-non-existing"));
|
||||
ASSERT_OK(dbfull()->Put(writeOpt, "k3", big_value));
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.num-immutable-mem-table", &num));
|
||||
ASSERT_EQ(num, "2");
|
||||
@@ -2504,6 +2511,19 @@ TEST(DBTest, GetProperty) {
|
||||
ASSERT_EQ(num, "1");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
||||
ASSERT_EQ(num, "0");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ(num, "4");
|
||||
// Verify the same set of properties through GetIntProperty
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.num-immutable-mem-table", &int_num));
|
||||
ASSERT_EQ(int_num, 2U);
|
||||
ASSERT_TRUE(
|
||||
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_TRUE(dbfull()->GetIntProperty("rocksdb.estimate-num-keys", &int_num));
|
||||
ASSERT_EQ(int_num, 4);
|
||||
|
||||
sleeping_task_high.WakeUp();
|
||||
sleeping_task_high.WaitUntilDone();
|
||||
@@ -2516,6 +2536,8 @@ TEST(DBTest, GetProperty) {
|
||||
ASSERT_EQ(num, "0");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.compaction-pending", &num));
|
||||
ASSERT_EQ(num, "1");
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ(num, "4");
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
}
|
||||
@@ -3474,10 +3496,206 @@ TEST(DBTest, UniversalCompactionCompressRatio2) {
|
||||
|
||||
TEST(DBTest, FailMoreDbPaths) {
|
||||
Options options;
|
||||
options.db_paths.push_back(dbname_);
|
||||
options.db_paths.push_back(dbname_ + "_2");
|
||||
options.db_paths.emplace_back(dbname_, 10000000);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1000000);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 1000000);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 1000000);
|
||||
options.db_paths.emplace_back(dbname_ + "_5", 1000000);
|
||||
ASSERT_TRUE(TryReopen(&options).IsNotSupported());
|
||||
}
|
||||
|
||||
TEST(DBTest, UniversalCompactionSecondPathRatio) {
|
||||
Options options;
|
||||
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options = CurrentOptions(options);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(options.db_paths[1].path, &filenames);
|
||||
// Delete archival files.
|
||||
for (size_t i = 0; i < filenames.size(); ++i) {
|
||||
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
||||
}
|
||||
env_->DeleteDir(options.db_paths[1].path);
|
||||
Reopen(&options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
// First three 110KB files are not going to second path.
|
||||
// After that, (100K, 200K)
|
||||
for (int num = 0; num < 3; num++) {
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
}
|
||||
|
||||
// Another 110KB triggers a compaction to 400K file to second path
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// (1, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1,1,4) -> (2, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 4) -> (8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 8) -> (2, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 8) -> (4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
ASSERT_NE(v, "NOT_FOUND");
|
||||
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
||||
}
|
||||
|
||||
Reopen(&options);
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
ASSERT_NE(v, "NOT_FOUND");
|
||||
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
||||
}
|
||||
|
||||
Destroy(&options);
|
||||
}
|
||||
|
||||
TEST(DBTest, UniversalCompactionFourPaths) {
|
||||
Options options;
|
||||
options.db_paths.emplace_back(dbname_, 300 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_4", 1024 * 1024 * 1024);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options = CurrentOptions(options);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(options.db_paths[1].path, &filenames);
|
||||
// Delete archival files.
|
||||
for (size_t i = 0; i < filenames.size(); ++i) {
|
||||
env_->DeleteFile(options.db_paths[1].path + "/" + filenames[i]);
|
||||
}
|
||||
env_->DeleteDir(options.db_paths[1].path);
|
||||
Reopen(&options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
// First three 110KB files are not going to second path.
|
||||
// After that, (100K, 200K)
|
||||
for (int num = 0; num < 3; num++) {
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
}
|
||||
|
||||
// Another 110KB triggers a compaction to 400K file to second path
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
|
||||
// (1, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1,1,4) -> (2, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 4) -> (8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
|
||||
// (1, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 8) -> (2, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// (1, 2, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 8) -> (4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
|
||||
// (1, 4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
ASSERT_NE(v, "NOT_FOUND");
|
||||
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
||||
}
|
||||
|
||||
Reopen(&options);
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
ASSERT_NE(v, "NOT_FOUND");
|
||||
ASSERT_TRUE(v.size() == 1 || v.size() == 10000);
|
||||
}
|
||||
|
||||
Destroy(&options);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(DBTest, ConvertCompactionStyle) {
|
||||
@@ -5053,6 +5271,54 @@ TEST(DBTest, ManualCompaction) {
|
||||
|
||||
}
|
||||
|
||||
TEST(DBTest, ManualCompactionOutputPathId) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_paths.emplace_back(dbname_, 1000000000);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1000000000);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.level0_file_num_compaction_trigger = 10;
|
||||
Destroy(&options);
|
||||
DestroyAndReopen(&options);
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
MakeTables(3, "p", "q", 1);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("3", FilesPerLevel(1));
|
||||
ASSERT_EQ(3, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// Full compaction to DB path 0
|
||||
db_->CompactRange(handles_[1], nullptr, nullptr, false, -1, 1);
|
||||
ASSERT_EQ("1", FilesPerLevel(1));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, &options);
|
||||
ASSERT_EQ("1", FilesPerLevel(1));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
MakeTables(1, "p", "q", 1);
|
||||
ASSERT_EQ("2", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, &options);
|
||||
ASSERT_EQ("2", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// Full compaction to DB path 0
|
||||
db_->CompactRange(handles_[1], nullptr, nullptr, false, -1, 0);
|
||||
ASSERT_EQ("1", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[0].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// Fail when compacting to an invalid path ID
|
||||
ASSERT_TRUE(db_->CompactRange(handles_[1], nullptr, nullptr, false, -1, 2)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST(DBTest, DBOpen_Options) {
|
||||
std::string dbname = test::TmpDir() + "/db_options_test";
|
||||
ASSERT_OK(DestroyDB(dbname, Options()));
|
||||
@@ -6350,6 +6616,11 @@ class ModelDB: public DB {
|
||||
const Slice& property, std::string* value) {
|
||||
return false;
|
||||
}
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes) {
|
||||
@@ -6360,7 +6631,8 @@ class ModelDB: public DB {
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* start, const Slice* end,
|
||||
bool reduce_level, int target_level) {
|
||||
bool reduce_level, int target_level,
|
||||
uint32_t output_path_id) {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
@@ -6939,6 +7211,41 @@ TEST(DBTest, TailingIteratorIncomplete) {
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST(DBTest, TailingIteratorSeekToSame) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 1000;
|
||||
CreateAndReopenWithCF({"pikachu"}, &options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
const int NROWS = 10000;
|
||||
// Write rows with keys 00000, 00002, 00004 etc.
|
||||
for (int i = 0; i < NROWS; ++i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%05d", 2*i);
|
||||
std::string key(buf);
|
||||
std::string value("value");
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// Seek to 00001. We expect to find 00002.
|
||||
std::string start_key = "00001";
|
||||
iter->Seek(start_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
std::string found = iter->key().ToString();
|
||||
ASSERT_EQ("00002", found);
|
||||
|
||||
// Now seek to the same key. The iterator should remain in the same
|
||||
// position.
|
||||
iter->Seek(found);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(found, iter->key().ToString());
|
||||
}
|
||||
|
||||
TEST(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
// create a DB with block prefix index
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -7159,7 +7466,7 @@ TEST(DBTest, MTRandomTimeoutTest) {
|
||||
|
||||
/*
|
||||
* This test is not reliable enough as it heavily depends on disk behavior.
|
||||
*
|
||||
*/
|
||||
TEST(DBTest, RateLimitingTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 1 << 20; // 1MB
|
||||
@@ -7190,7 +7497,7 @@ TEST(DBTest, RateLimitingTest) {
|
||||
|
||||
// # rate limiting with 0.7 x threshold
|
||||
options.rate_limiter.reset(
|
||||
NewRateLimiter(static_cast<int64_t>(0.7 * raw_rate)));
|
||||
NewGenericRateLimiter(static_cast<int64_t>(0.7 * raw_rate)));
|
||||
env_->bytes_written_ = 0;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -7206,11 +7513,11 @@ TEST(DBTest, RateLimitingTest) {
|
||||
env_->bytes_written_);
|
||||
double ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate;
|
||||
fprintf(stderr, "write rate ratio = %.2lf, expected 0.7\n", ratio);
|
||||
ASSERT_TRUE(ratio > 0.6 && ratio < 0.8);
|
||||
ASSERT_TRUE(ratio < 0.8);
|
||||
|
||||
// # rate limiting with half of the raw_rate
|
||||
options.rate_limiter.reset(
|
||||
NewRateLimiter(static_cast<int64_t>(raw_rate / 2)));
|
||||
NewGenericRateLimiter(static_cast<int64_t>(raw_rate / 2)));
|
||||
env_->bytes_written_ = 0;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -7226,9 +7533,8 @@ TEST(DBTest, RateLimitingTest) {
|
||||
env_->bytes_written_);
|
||||
ratio = env_->bytes_written_ * 1000000 / elapsed / raw_rate;
|
||||
fprintf(stderr, "write rate ratio = %.2lf, expected 0.5\n", ratio);
|
||||
ASSERT_TRUE(ratio > 0.4 && ratio < 0.6);
|
||||
ASSERT_TRUE(ratio < 0.6);
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
@@ -254,8 +255,38 @@ class IterKey {
|
||||
|
||||
Slice GetKey() const { return Slice(key_, key_size_); }
|
||||
|
||||
const size_t Size() { return key_size_; }
|
||||
|
||||
void Clear() { key_size_ = 0; }
|
||||
|
||||
// Append "non_shared_data" to its back, from "shared_len"
|
||||
// This function is used in Block::Iter::ParseNextKey
|
||||
// shared_len: bytes in [0, shard_len-1] would be remained
|
||||
// non_shared_data: data to be append, its length must be >= non_shared_len
|
||||
void TrimAppend(const size_t shared_len, const char* non_shared_data,
|
||||
const size_t non_shared_len) {
|
||||
assert(shared_len <= key_size_);
|
||||
|
||||
size_t total_size = shared_len + non_shared_len;
|
||||
if (total_size <= buf_size_) {
|
||||
key_size_ = total_size;
|
||||
} else {
|
||||
// Need to allocate space, delete previous space
|
||||
char* p = new char[total_size];
|
||||
memcpy(p, key_, shared_len);
|
||||
|
||||
if (key_ != nullptr && key_ != space_) {
|
||||
delete[] key_;
|
||||
}
|
||||
|
||||
key_ = p;
|
||||
key_size_ = total_size;
|
||||
buf_size_ = total_size;
|
||||
}
|
||||
|
||||
memcpy(key_ + shared_len, non_shared_data, non_shared_len);
|
||||
}
|
||||
|
||||
void SetKey(const Slice& key) {
|
||||
size_t size = key.size();
|
||||
EnlargeBufferIfNeeded(size);
|
||||
|
||||
@@ -110,6 +110,45 @@ TEST(FormatTest, InternalKeyShortestSuccessor) {
|
||||
ShortSuccessor(IKey("\xff\xff", 100, kTypeValue)));
|
||||
}
|
||||
|
||||
TEST(FormatTest, IterKeyOperation) {
|
||||
IterKey k;
|
||||
const char p[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
const char q[] = "0123456789";
|
||||
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string(""));
|
||||
|
||||
k.TrimAppend(0, p, 3);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abc"));
|
||||
|
||||
k.TrimAppend(1, p, 3);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("aabc"));
|
||||
|
||||
k.TrimAppend(0, p, 26);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abcdefghijklmnopqrstuvwxyz"));
|
||||
|
||||
k.TrimAppend(26, q, 10);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abcdefghijklmnopqrstuvwxyz0123456789"));
|
||||
|
||||
k.TrimAppend(36, q, 1);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abcdefghijklmnopqrstuvwxyz01234567890"));
|
||||
|
||||
k.TrimAppend(26, q, 1);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abcdefghijklmnopqrstuvwxyz0"));
|
||||
|
||||
// Size going up, memory allocation is triggered
|
||||
k.TrimAppend(27, p, 26);
|
||||
ASSERT_EQ(std::string(k.GetKey().data(), k.GetKey().size()),
|
||||
std::string("abcdefghijklmnopqrstuvwxyz0"
|
||||
"abcdefghijklmnopqrstuvwxyz"));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+61
-53
@@ -14,21 +14,15 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
FileIndexer::FileIndexer(const uint32_t num_levels,
|
||||
const Comparator* ucmp)
|
||||
: num_levels_(num_levels),
|
||||
ucmp_(ucmp),
|
||||
next_level_index_(num_levels),
|
||||
level_rb_(num_levels, -1) {
|
||||
}
|
||||
|
||||
FileIndexer::FileIndexer(const Comparator* ucmp)
|
||||
: num_levels_(0), ucmp_(ucmp), level_rb_(nullptr) {}
|
||||
|
||||
uint32_t FileIndexer::NumLevelIndex() {
|
||||
return next_level_index_.size();
|
||||
}
|
||||
|
||||
uint32_t FileIndexer::LevelIndexSize(uint32_t level) {
|
||||
return next_level_index_[level].size();
|
||||
return next_level_index_[level].num_index;
|
||||
}
|
||||
|
||||
void FileIndexer::GetNextLevelIndex(
|
||||
@@ -46,11 +40,13 @@ void FileIndexer::GetNextLevelIndex(
|
||||
assert(level < num_levels_ - 1);
|
||||
assert(static_cast<int32_t>(file_index) <= level_rb_[level]);
|
||||
|
||||
const auto& index = next_level_index_[level][file_index];
|
||||
const IndexUnit* index_units = next_level_index_[level].index_units;
|
||||
const auto& index = index_units[file_index];
|
||||
|
||||
if (cmp_smallest < 0) {
|
||||
*left_bound = (level > 0 && file_index > 0) ?
|
||||
next_level_index_[level][file_index - 1].largest_lb : 0;
|
||||
*left_bound = (level > 0 && file_index > 0)
|
||||
? index_units[file_index - 1].largest_lb
|
||||
: 0;
|
||||
*right_bound = index.smallest_rb;
|
||||
} else if (cmp_smallest == 0) {
|
||||
*left_bound = index.smallest_lb;
|
||||
@@ -73,16 +69,25 @@ void FileIndexer::GetNextLevelIndex(
|
||||
assert(*right_bound <= level_rb_[level + 1]);
|
||||
}
|
||||
|
||||
void FileIndexer::ClearIndex() {
|
||||
for (uint32_t level = 1; level < num_levels_; ++level) {
|
||||
next_level_index_[level].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void FileIndexer::UpdateIndex(std::vector<FileMetaData*>* const files) {
|
||||
void FileIndexer::UpdateIndex(Arena* arena, const uint32_t num_levels,
|
||||
std::vector<FileMetaData*>* const files) {
|
||||
if (files == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (num_levels == 0) { // uint_32 0-1 would cause bad behavior
|
||||
num_levels_ = num_levels;
|
||||
return;
|
||||
}
|
||||
assert(level_rb_ == nullptr); // level_rb_ should be init here
|
||||
|
||||
num_levels_ = num_levels;
|
||||
next_level_index_.resize(num_levels);
|
||||
|
||||
char* mem = arena->AllocateAligned(num_levels_ * sizeof(int32_t));
|
||||
level_rb_ = new (mem) int32_t[num_levels_];
|
||||
for (size_t i = 0; i < num_levels_; i++) {
|
||||
level_rb_[i] = -1;
|
||||
}
|
||||
|
||||
// L1 - Ln-1
|
||||
for (uint32_t level = 1; level < num_levels_ - 1; ++level) {
|
||||
@@ -93,55 +98,56 @@ void FileIndexer::UpdateIndex(std::vector<FileMetaData*>* const files) {
|
||||
if (upper_size == 0) {
|
||||
continue;
|
||||
}
|
||||
auto& index = next_level_index_[level];
|
||||
index.resize(upper_size);
|
||||
IndexLevel& index_level = next_level_index_[level];
|
||||
index_level.num_index = upper_size;
|
||||
char* mem = arena->AllocateAligned(upper_size * sizeof(IndexUnit));
|
||||
index_level.index_units = new (mem) IndexUnit[upper_size];
|
||||
|
||||
CalculateLB(upper_files, lower_files, &index,
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
CalculateLB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->smallest.user_key(), b->largest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) {
|
||||
index->smallest_lb = f_idx;
|
||||
});
|
||||
CalculateLB(upper_files, lower_files, &index,
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
[](IndexUnit* index, int32_t f_idx) { index->smallest_lb = f_idx; });
|
||||
CalculateLB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->largest.user_key(), b->largest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) {
|
||||
index->largest_lb = f_idx;
|
||||
});
|
||||
CalculateRB(upper_files, lower_files, &index,
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
[](IndexUnit* index, int32_t f_idx) { index->largest_lb = f_idx; });
|
||||
CalculateRB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->smallest.user_key(), b->smallest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) {
|
||||
index->smallest_rb = f_idx;
|
||||
});
|
||||
CalculateRB(upper_files, lower_files, &index,
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
[](IndexUnit* index, int32_t f_idx) { index->smallest_rb = f_idx; });
|
||||
CalculateRB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->largest.user_key(), b->smallest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) {
|
||||
index->largest_rb = f_idx;
|
||||
});
|
||||
[](IndexUnit* index, int32_t f_idx) { index->largest_rb = f_idx; });
|
||||
}
|
||||
|
||||
level_rb_[num_levels_ - 1] = files[num_levels_ - 1].size() - 1;
|
||||
}
|
||||
|
||||
void FileIndexer::CalculateLB(const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files,
|
||||
std::vector<IndexUnit>* index,
|
||||
void FileIndexer::CalculateLB(
|
||||
const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index) {
|
||||
const int32_t upper_size = upper_files.size();
|
||||
const int32_t lower_size = lower_files.size();
|
||||
int32_t upper_idx = 0;
|
||||
int32_t lower_idx = 0;
|
||||
|
||||
IndexUnit* index = index_level->index_units;
|
||||
while (upper_idx < upper_size && lower_idx < lower_size) {
|
||||
int cmp = cmp_op(upper_files[upper_idx], lower_files[lower_idx]);
|
||||
|
||||
if (cmp == 0) {
|
||||
set_index(&(*index)[upper_idx], lower_idx);
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
++upper_idx;
|
||||
++lower_idx;
|
||||
} else if (cmp > 0) {
|
||||
@@ -151,7 +157,7 @@ void FileIndexer::CalculateLB(const std::vector<FileMetaData*>& upper_files,
|
||||
} else {
|
||||
// Lower level's file becomes larger, update the index, and
|
||||
// move to the next upper file
|
||||
set_index(&(*index)[upper_idx], lower_idx);
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
++upper_idx;
|
||||
}
|
||||
}
|
||||
@@ -159,25 +165,27 @@ void FileIndexer::CalculateLB(const std::vector<FileMetaData*>& upper_files,
|
||||
while (upper_idx < upper_size) {
|
||||
// Lower files are exhausted, that means the remaining upper files are
|
||||
// greater than any lower files. Set the index to be the lower level size.
|
||||
set_index(&(*index)[upper_idx], lower_size);
|
||||
set_index(&index[upper_idx], lower_size);
|
||||
++upper_idx;
|
||||
}
|
||||
}
|
||||
|
||||
void FileIndexer::CalculateRB(const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files,
|
||||
std::vector<IndexUnit>* index,
|
||||
void FileIndexer::CalculateRB(
|
||||
const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index) {
|
||||
const int32_t upper_size = upper_files.size();
|
||||
const int32_t lower_size = lower_files.size();
|
||||
int32_t upper_idx = upper_size - 1;
|
||||
int32_t lower_idx = lower_size - 1;
|
||||
|
||||
IndexUnit* index = index_level->index_units;
|
||||
while (upper_idx >= 0 && lower_idx >= 0) {
|
||||
int cmp = cmp_op(upper_files[upper_idx], lower_files[lower_idx]);
|
||||
|
||||
if (cmp == 0) {
|
||||
set_index(&(*index)[upper_idx], lower_idx);
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
--upper_idx;
|
||||
--lower_idx;
|
||||
} else if (cmp < 0) {
|
||||
@@ -187,14 +195,14 @@ void FileIndexer::CalculateRB(const std::vector<FileMetaData*>& upper_files,
|
||||
} else {
|
||||
// Lower level's file becomes smaller, update the index, and move to
|
||||
// the next the upper file
|
||||
set_index(&(*index)[upper_idx], lower_idx);
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
--upper_idx;
|
||||
}
|
||||
}
|
||||
while (upper_idx >= 0) {
|
||||
// Lower files are exhausted, that means the remaining upper files are
|
||||
// smaller than any lower files. Set it to -1.
|
||||
set_index(&(*index)[upper_idx], -1);
|
||||
set_index(&index[upper_idx], -1);
|
||||
--upper_idx;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-17
@@ -12,11 +12,15 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Comparator;
|
||||
struct FileMetaData;
|
||||
struct FdWithKeyRange;
|
||||
struct FileLevel;
|
||||
|
||||
// The file tree structure in Version is prebuilt and the range of each file
|
||||
// is known. On Version::Get(), it uses binary search to find a potential file
|
||||
@@ -36,7 +40,7 @@ struct FileMetaData;
|
||||
// naive approach.
|
||||
class FileIndexer {
|
||||
public:
|
||||
FileIndexer(const uint32_t num_levels, const Comparator* ucmp);
|
||||
explicit FileIndexer(const Comparator* ucmp);
|
||||
|
||||
uint32_t NumLevelIndex();
|
||||
|
||||
@@ -50,16 +54,15 @@ class FileIndexer {
|
||||
const uint32_t level, const uint32_t file_index, const int cmp_smallest,
|
||||
const int cmp_largest, int32_t* left_bound, int32_t* right_bound);
|
||||
|
||||
void ClearIndex();
|
||||
|
||||
void UpdateIndex(std::vector<FileMetaData*>* const files);
|
||||
void UpdateIndex(Arena* arena, const uint32_t num_levels,
|
||||
std::vector<FileMetaData*>* const files);
|
||||
|
||||
enum {
|
||||
kLevelMaxIndex = std::numeric_limits<int32_t>::max()
|
||||
};
|
||||
|
||||
private:
|
||||
const uint32_t num_levels_;
|
||||
uint32_t num_levels_;
|
||||
const Comparator* ucmp_;
|
||||
|
||||
struct IndexUnit {
|
||||
@@ -110,20 +113,28 @@ class FileIndexer {
|
||||
int32_t largest_rb;
|
||||
};
|
||||
|
||||
void CalculateLB(const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files,
|
||||
std::vector<IndexUnit>* index,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index);
|
||||
// Data structure to store IndexUnits in a whole level
|
||||
struct IndexLevel {
|
||||
size_t num_index;
|
||||
IndexUnit* index_units;
|
||||
|
||||
void CalculateRB(const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files,
|
||||
std::vector<IndexUnit>* index,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index);
|
||||
IndexLevel() : num_index(0), index_units(nullptr) {}
|
||||
};
|
||||
|
||||
std::vector<std::vector<IndexUnit>> next_level_index_;
|
||||
std::vector<int32_t> level_rb_;
|
||||
void CalculateLB(
|
||||
const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index);
|
||||
|
||||
void CalculateRB(
|
||||
const std::vector<FileMetaData*>& upper_files,
|
||||
const std::vector<FileMetaData*>& lower_files, IndexLevel* index_level,
|
||||
std::function<int(const FileMetaData*, const FileMetaData*)> cmp_op,
|
||||
std::function<void(IndexUnit*, int32_t)> set_index);
|
||||
|
||||
autovector<IndexLevel> next_level_index_;
|
||||
int32_t* level_rb_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+50
-36
@@ -35,16 +35,13 @@ class IntComparator : public Comparator {
|
||||
void FindShortSuccessor(std::string* key) const {}
|
||||
};
|
||||
|
||||
|
||||
struct FileIndexerTest {
|
||||
public:
|
||||
FileIndexerTest() :
|
||||
kNumLevels(4), indexer(kNumLevels, &ucmp),
|
||||
files(new std::vector<FileMetaData*>[kNumLevels]) {
|
||||
}
|
||||
FileIndexerTest()
|
||||
: kNumLevels(4), files(new std::vector<FileMetaData*>[kNumLevels]) {}
|
||||
|
||||
~FileIndexerTest() {
|
||||
Reset();
|
||||
ClearFiles();
|
||||
delete[] files;
|
||||
}
|
||||
|
||||
@@ -59,14 +56,13 @@ struct FileIndexerTest {
|
||||
return InternalKey(Slice(reinterpret_cast<char*>(&v), 8), 0, kTypeValue);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
void ClearFiles() {
|
||||
for (uint32_t i = 0; i < kNumLevels; ++i) {
|
||||
for (auto* f : files[i]) {
|
||||
delete f;
|
||||
}
|
||||
files[i].clear();
|
||||
}
|
||||
indexer.ClearIndex();
|
||||
}
|
||||
|
||||
void GetNextLevelIndex(const uint32_t level, const uint32_t file_index,
|
||||
@@ -74,23 +70,32 @@ struct FileIndexerTest {
|
||||
int32_t* right_index) {
|
||||
*left_index = 100;
|
||||
*right_index = 100;
|
||||
indexer.GetNextLevelIndex(level, file_index, cmp_smallest, cmp_largest,
|
||||
left_index, right_index);
|
||||
indexer->GetNextLevelIndex(level, file_index, cmp_smallest, cmp_largest,
|
||||
left_index, right_index);
|
||||
}
|
||||
|
||||
int32_t left = 100;
|
||||
int32_t right = 100;
|
||||
const uint32_t kNumLevels;
|
||||
IntComparator ucmp;
|
||||
FileIndexer indexer;
|
||||
FileIndexer* indexer;
|
||||
|
||||
std::vector<FileMetaData*>* files;
|
||||
};
|
||||
|
||||
TEST(FileIndexerTest, next_level_hint) {
|
||||
for (uint32_t i = 0; i < kNumLevels; ++i) {
|
||||
ASSERT_EQ(0U, indexer.LevelIndexSize(i));
|
||||
}
|
||||
// Case 0: Empty
|
||||
TEST(FileIndexerTest, Empty) {
|
||||
Arena arena;
|
||||
indexer = new FileIndexer(&ucmp);
|
||||
indexer->UpdateIndex(&arena, 0, files);
|
||||
delete indexer;
|
||||
}
|
||||
|
||||
// Case 1: no overlap, files are on the left of next level files
|
||||
// Case 1: no overlap, files are on the left of next level files
|
||||
TEST(FileIndexerTest, no_overlap_left) {
|
||||
Arena arena;
|
||||
uint32_t kNumLevels = 4;
|
||||
indexer = new FileIndexer(&ucmp);
|
||||
// level 1
|
||||
AddFile(1, 100, 200);
|
||||
AddFile(1, 300, 400);
|
||||
@@ -103,9 +108,7 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
AddFile(3, 2500, 2600);
|
||||
AddFile(3, 2601, 2699);
|
||||
AddFile(3, 2700, 2800);
|
||||
indexer.UpdateIndex(files);
|
||||
int32_t left = 100;
|
||||
int32_t right = 100;
|
||||
indexer->UpdateIndex(&arena, kNumLevels, files);
|
||||
for (uint32_t level = 1; level < 3; ++level) {
|
||||
for (uint32_t f = 0; f < 3; ++f) {
|
||||
GetNextLevelIndex(level, f, -1, -1, &left, &right);
|
||||
@@ -125,12 +128,15 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
ASSERT_EQ(2, right);
|
||||
}
|
||||
}
|
||||
delete indexer;
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
// Case 2: no overlap, files are on the right of next level files
|
||||
Reset();
|
||||
for (uint32_t i = 1; i < kNumLevels; ++i) {
|
||||
ASSERT_EQ(0U, indexer.LevelIndexSize(i));
|
||||
}
|
||||
// Case 2: no overlap, files are on the right of next level files
|
||||
TEST(FileIndexerTest, no_overlap_right) {
|
||||
Arena arena;
|
||||
uint32_t kNumLevels = 4;
|
||||
indexer = new FileIndexer(&ucmp);
|
||||
// level 1
|
||||
AddFile(1, 2100, 2200);
|
||||
AddFile(1, 2300, 2400);
|
||||
@@ -143,7 +149,7 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
AddFile(3, 500, 600);
|
||||
AddFile(3, 501, 699);
|
||||
AddFile(3, 700, 800);
|
||||
indexer.UpdateIndex(files);
|
||||
indexer->UpdateIndex(&arena, kNumLevels, files);
|
||||
for (uint32_t level = 1; level < 3; ++level) {
|
||||
for (uint32_t f = 0; f < 3; ++f) {
|
||||
GetNextLevelIndex(level, f, -1, -1, &left, &right);
|
||||
@@ -166,11 +172,16 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
ASSERT_EQ(2, right);
|
||||
}
|
||||
}
|
||||
delete indexer;
|
||||
}
|
||||
|
||||
// Case 3: empty L2
|
||||
Reset();
|
||||
// Case 3: empty L2
|
||||
TEST(FileIndexerTest, empty_L2) {
|
||||
Arena arena;
|
||||
uint32_t kNumLevels = 4;
|
||||
indexer = new FileIndexer(&ucmp);
|
||||
for (uint32_t i = 1; i < kNumLevels; ++i) {
|
||||
ASSERT_EQ(0U, indexer.LevelIndexSize(i));
|
||||
ASSERT_EQ(0U, indexer->LevelIndexSize(i));
|
||||
}
|
||||
// level 1
|
||||
AddFile(1, 2100, 2200);
|
||||
@@ -180,7 +191,7 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
AddFile(3, 500, 600);
|
||||
AddFile(3, 501, 699);
|
||||
AddFile(3, 700, 800);
|
||||
indexer.UpdateIndex(files);
|
||||
indexer->UpdateIndex(&arena, kNumLevels, files);
|
||||
for (uint32_t f = 0; f < 3; ++f) {
|
||||
GetNextLevelIndex(1, f, -1, -1, &left, &right);
|
||||
ASSERT_EQ(0, left);
|
||||
@@ -201,13 +212,14 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
ASSERT_EQ(0, left);
|
||||
ASSERT_EQ(-1, right);
|
||||
}
|
||||
delete indexer;
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
|
||||
// Case 4: mixed
|
||||
Reset();
|
||||
for (uint32_t i = 1; i < kNumLevels; ++i) {
|
||||
ASSERT_EQ(0U, indexer.LevelIndexSize(i));
|
||||
}
|
||||
// Case 4: mixed
|
||||
TEST(FileIndexerTest, mixed) {
|
||||
Arena arena;
|
||||
indexer = new FileIndexer(&ucmp);
|
||||
// level 1
|
||||
AddFile(1, 100, 200);
|
||||
AddFile(1, 250, 400);
|
||||
@@ -222,7 +234,7 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
AddFile(3, 0, 50);
|
||||
AddFile(3, 100, 200);
|
||||
AddFile(3, 201, 250);
|
||||
indexer.UpdateIndex(files);
|
||||
indexer->UpdateIndex(&arena, kNumLevels, files);
|
||||
// level 1, 0
|
||||
GetNextLevelIndex(1, 0, -1, -1, &left, &right);
|
||||
ASSERT_EQ(0, left);
|
||||
@@ -321,6 +333,8 @@ TEST(FileIndexerTest, next_level_hint) {
|
||||
ASSERT_EQ(3, left);
|
||||
ASSERT_EQ(2, right);
|
||||
}
|
||||
delete indexer;
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+4
-4
@@ -71,14 +71,14 @@ std::string MakeTableFileName(const std::string& path, uint64_t number) {
|
||||
return MakeFileName(path, number, "sst");
|
||||
}
|
||||
|
||||
std::string TableFileName(const std::vector<std::string> db_paths,
|
||||
uint64_t number, uint32_t path_id) {
|
||||
std::string TableFileName(const std::vector<DbPath>& db_paths, uint64_t number,
|
||||
uint32_t path_id) {
|
||||
assert(number > 0);
|
||||
std::string path;
|
||||
if (path_id >= db_paths.size()) {
|
||||
path = db_paths.back();
|
||||
path = db_paths.back().path;
|
||||
} else {
|
||||
path = db_paths[path_id];
|
||||
path = db_paths[path_id].path;
|
||||
}
|
||||
return MakeTableFileName(path, number);
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ extern std::string MakeTableFileName(const std::string& name, uint64_t number);
|
||||
// Return the name of the sstable with the specified number
|
||||
// in the db named by "dbname". The result will be prefixed with
|
||||
// "dbname".
|
||||
extern std::string TableFileName(const std::vector<std::string> db_paths,
|
||||
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);
|
||||
|
||||
+3
-2
@@ -108,8 +108,9 @@ TEST(FileNameTest, Construction) {
|
||||
ASSERT_EQ(192U, number);
|
||||
ASSERT_EQ(kLogFile, type);
|
||||
|
||||
fname = TableFileName({"bar"}, 200, 0);
|
||||
std::string fname1 = TableFileName({"foo", "bar"}, 200, 1);
|
||||
fname = TableFileName({DbPath("bar", 0)}, 200, 0);
|
||||
std::string fname1 =
|
||||
TableFileName({DbPath("foo", 0), DbPath("bar", 0)}, 200, 1);
|
||||
ASSERT_EQ(fname, fname1);
|
||||
ASSERT_EQ("bar/", std::string(fname.data(), 4));
|
||||
ASSERT_TRUE(ParseFileName(fname.c_str() + 4, &number, &type));
|
||||
|
||||
@@ -292,6 +292,9 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
prev_key_.SetKey(internal_key);
|
||||
is_prev_set_ = true;
|
||||
}
|
||||
} else if (current_ && current_ != mutable_iter_) {
|
||||
// current_ is one of immutable iterators, push it back to the heap
|
||||
immutable_min_heap_.push(current_);
|
||||
}
|
||||
|
||||
UpdateCurrent();
|
||||
|
||||
+359
-278
@@ -7,12 +7,85 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/internal_stats.h"
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
#include "db/column_family.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
const double kMB = 1048576.0;
|
||||
const double kGB = kMB * 1024;
|
||||
|
||||
void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
snprintf(
|
||||
buf, len,
|
||||
"\n** Compaction Stats [%s] **\n"
|
||||
"Level Files Size(MB) Score Read(GB) Rn(GB) Rnp1(GB) "
|
||||
"Write(GB) Wnew(GB) RW-Amp W-Amp Rd(MB/s) Wr(MB/s) Rn(cnt) "
|
||||
"Rnp1(cnt) Wnp1(cnt) Wnew(cnt) Comp(sec) Comp(cnt) Avg(sec) "
|
||||
"Stall(sec) Stall(cnt) Avg(ms)\n"
|
||||
"--------------------------------------------------------------------"
|
||||
"--------------------------------------------------------------------"
|
||||
"--------------------------------------------------------------------\n",
|
||||
cf_name.c_str());
|
||||
}
|
||||
|
||||
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
||||
int num_files, int being_compacted, double total_file_size, double score,
|
||||
double rw_amp, double w_amp, double stall_us, uint64_t stalls,
|
||||
const InternalStats::CompactionStats& stats) {
|
||||
uint64_t bytes_read = stats.bytes_readn + stats.bytes_readnp1;
|
||||
uint64_t bytes_new = stats.bytes_written - stats.bytes_readnp1;
|
||||
double elapsed = (stats.micros + 1) / 1000000.0;
|
||||
|
||||
snprintf(buf, len,
|
||||
"%4s %5d/%-3d %8.0f %5.1f " /* Level, Files, Size(MB), Score */
|
||||
"%8.1f " /* Read(GB) */
|
||||
"%7.1f " /* Rn(GB) */
|
||||
"%8.1f " /* Rnp1(GB) */
|
||||
"%9.1f " /* Write(GB) */
|
||||
"%8.1f " /* Wnew(GB) */
|
||||
"%6.1f " /* RW-Amp */
|
||||
"%5.1f " /* W-Amp */
|
||||
"%8.1f " /* Rd(MB/s) */
|
||||
"%8.1f " /* Wr(MB/s) */
|
||||
"%8d " /* Rn(cnt) */
|
||||
"%9d " /* Rnp1(cnt) */
|
||||
"%9d " /* Wnp1(cnt) */
|
||||
"%9d " /* Wnew(cnt) */
|
||||
"%10.0f " /* Comp(sec) */
|
||||
"%9d " /* Comp(cnt) */
|
||||
"%8.3f " /* Avg(sec) */
|
||||
"%10.2f " /* Stall(sec) */
|
||||
"%10" PRIu64 " " /* Stall(cnt) */
|
||||
"%7.2f\n" /* Avg(ms) */,
|
||||
name.c_str(), num_files, being_compacted, total_file_size / kMB, score,
|
||||
bytes_read / kGB,
|
||||
stats.bytes_readn / kGB,
|
||||
stats.bytes_readnp1 / kGB,
|
||||
stats.bytes_written / kGB,
|
||||
bytes_new / kGB,
|
||||
rw_amp,
|
||||
w_amp,
|
||||
bytes_read / kMB / elapsed,
|
||||
stats.bytes_written / kMB / elapsed,
|
||||
stats.files_in_leveln,
|
||||
stats.files_in_levelnp1,
|
||||
stats.files_out_levelnp1,
|
||||
stats.files_out_levelnp1 - stats.files_in_levelnp1,
|
||||
stats.micros / 1000000.0,
|
||||
stats.count,
|
||||
stats.count == 0 ? 0 : stats.micros / 1000000.0 / stats.count,
|
||||
stall_us / 1000000.0,
|
||||
stalls,
|
||||
stalls == 0 ? 0 : stall_us / 1000.0 / stalls);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
DBPropertyType GetPropertyType(const Slice& property) {
|
||||
Slice in = property;
|
||||
Slice prefix("rocksdb.");
|
||||
@@ -25,6 +98,10 @@ DBPropertyType GetPropertyType(const Slice& property) {
|
||||
return kLevelStats;
|
||||
} else if (in == "stats") {
|
||||
return kStats;
|
||||
} else if (in == "cfstats") {
|
||||
return kCFStats;
|
||||
} else if (in == "dbstats") {
|
||||
return kDBStats;
|
||||
} else if (in == "sstables") {
|
||||
return kSsTables;
|
||||
} else if (in == "num-immutable-mem-table") {
|
||||
@@ -41,14 +118,24 @@ DBPropertyType GetPropertyType(const Slice& property) {
|
||||
return kNumEntriesInMutableMemtable;
|
||||
} else if (in == "num-entries-imm-mem-tables") {
|
||||
return kNumEntriesInImmutableMemtable;
|
||||
} else if (in == "estimate-num-keys") {
|
||||
return kEstimatedNumKeys;
|
||||
}
|
||||
return kUnknown;
|
||||
}
|
||||
|
||||
bool InternalStats::GetProperty(DBPropertyType property_type,
|
||||
const Slice& property, std::string* value,
|
||||
ColumnFamilyData* cfd) {
|
||||
Version* current = cfd->current();
|
||||
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;
|
||||
}
|
||||
|
||||
Version* current = cfd_->current();
|
||||
Slice in = property;
|
||||
|
||||
switch (property_type) {
|
||||
@@ -76,294 +163,288 @@ bool InternalStats::GetProperty(DBPropertyType property_type,
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
snprintf(buf, sizeof(buf), "%3d %8d %8.0f\n", level,
|
||||
current->NumLevelFiles(level),
|
||||
current->NumLevelBytes(level) / 1048576.0);
|
||||
current->NumLevelBytes(level) / kMB);
|
||||
value->append(buf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case kStats: {
|
||||
char buf[1000];
|
||||
|
||||
uint64_t wal_bytes = 0;
|
||||
uint64_t wal_synced = 0;
|
||||
uint64_t user_bytes_written = 0;
|
||||
uint64_t write_other = 0;
|
||||
uint64_t write_self = 0;
|
||||
uint64_t write_with_wal = 0;
|
||||
uint64_t total_bytes_written = 0;
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t micros_up = env_->NowMicros() - started_at_;
|
||||
// Add "+1" to make sure seconds_up is > 0 and avoid NaN later
|
||||
double seconds_up = (micros_up + 1) / 1000000.0;
|
||||
uint64_t total_slowdown = 0;
|
||||
uint64_t total_slowdown_count = 0;
|
||||
uint64_t interval_bytes_written = 0;
|
||||
uint64_t interval_bytes_read = 0;
|
||||
uint64_t interval_bytes_new = 0;
|
||||
double interval_seconds_up = 0;
|
||||
|
||||
if (statistics_) {
|
||||
wal_bytes = statistics_->getTickerCount(WAL_FILE_BYTES);
|
||||
wal_synced = statistics_->getTickerCount(WAL_FILE_SYNCED);
|
||||
user_bytes_written = statistics_->getTickerCount(BYTES_WRITTEN);
|
||||
write_other = statistics_->getTickerCount(WRITE_DONE_BY_OTHER);
|
||||
write_self = statistics_->getTickerCount(WRITE_DONE_BY_SELF);
|
||||
write_with_wal = statistics_->getTickerCount(WRITE_WITH_WAL);
|
||||
if (!GetProperty(kCFStats, "rocksdb.cfstats", value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
" Compactions\n"
|
||||
"Level Files Size(MB) Score Time(sec) Read(MB) Write(MB) Rn(MB) "
|
||||
" "
|
||||
"Rnp1(MB) Wnew(MB) RW-Amplify Read(MB/s) Write(MB/s) Rn "
|
||||
"Rnp1 "
|
||||
" Wnp1 NewW Count msComp msStall Ln-stall Stall-cnt\n"
|
||||
"--------------------------------------------------------------------"
|
||||
"--"
|
||||
"--------------------------------------------------------------------"
|
||||
"--"
|
||||
"----------------------------------------------------------------\n");
|
||||
value->append(buf);
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
int files = current->NumLevelFiles(level);
|
||||
if (compaction_stats_[level].micros > 0 || files > 0) {
|
||||
int64_t bytes_read = compaction_stats_[level].bytes_readn +
|
||||
compaction_stats_[level].bytes_readnp1;
|
||||
int64_t bytes_new = compaction_stats_[level].bytes_written -
|
||||
compaction_stats_[level].bytes_readnp1;
|
||||
double amplify =
|
||||
(compaction_stats_[level].bytes_readn == 0)
|
||||
? 0.0
|
||||
: (compaction_stats_[level].bytes_written +
|
||||
compaction_stats_[level].bytes_readnp1 +
|
||||
compaction_stats_[level].bytes_readn) /
|
||||
(double)compaction_stats_[level].bytes_readn;
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
total_bytes_written += compaction_stats_[level].bytes_written;
|
||||
|
||||
uint64_t stalls = level == 0 ? (stall_counts_[LEVEL0_SLOWDOWN] +
|
||||
stall_counts_[LEVEL0_NUM_FILES] +
|
||||
stall_counts_[MEMTABLE_COMPACTION])
|
||||
: stall_leveln_slowdown_count_[level];
|
||||
|
||||
double stall_us = level == 0 ? (stall_micros_[LEVEL0_SLOWDOWN] +
|
||||
stall_micros_[LEVEL0_NUM_FILES] +
|
||||
stall_micros_[MEMTABLE_COMPACTION])
|
||||
: stall_leveln_slowdown_[level];
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%3d %8d %8.0f %5.1f %9.0f %9.0f %9.0f %9.0f %9.0f %9.0f "
|
||||
"%10.1f %9.1f %11.1f %8d %8d %8d %8d %8d %8d %9.1f %9.1f "
|
||||
"%9lu\n",
|
||||
level, files, current->NumLevelBytes(level) / 1048576.0,
|
||||
current->NumLevelBytes(level) /
|
||||
cfd->compaction_picker()->MaxBytesForLevel(level),
|
||||
compaction_stats_[level].micros / 1e6,
|
||||
bytes_read / 1048576.0,
|
||||
compaction_stats_[level].bytes_written / 1048576.0,
|
||||
compaction_stats_[level].bytes_readn / 1048576.0,
|
||||
compaction_stats_[level].bytes_readnp1 / 1048576.0,
|
||||
bytes_new / 1048576.0, amplify,
|
||||
// +1 to avoid division by 0
|
||||
(bytes_read / 1048576.0) /
|
||||
((compaction_stats_[level].micros + 1) / 1000000.0),
|
||||
(compaction_stats_[level].bytes_written / 1048576.0) /
|
||||
((compaction_stats_[level].micros + 1) / 1000000.0),
|
||||
compaction_stats_[level].files_in_leveln,
|
||||
compaction_stats_[level].files_in_levelnp1,
|
||||
compaction_stats_[level].files_out_levelnp1,
|
||||
compaction_stats_[level].files_out_levelnp1 -
|
||||
compaction_stats_[level].files_in_levelnp1,
|
||||
compaction_stats_[level].count,
|
||||
(int)((double)compaction_stats_[level].micros / 1000.0 /
|
||||
(compaction_stats_[level].count + 1)),
|
||||
(double)stall_us / 1000.0 / (stalls + 1),
|
||||
stall_us / 1000000.0, (unsigned long)stalls);
|
||||
total_slowdown += stall_leveln_slowdown_[level];
|
||||
total_slowdown_count += stall_leveln_slowdown_count_[level];
|
||||
value->append(buf);
|
||||
}
|
||||
if (!GetProperty(kDBStats, "rocksdb.dbstats", value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
interval_bytes_new = user_bytes_written - last_stats_.ingest_bytes_;
|
||||
interval_bytes_read =
|
||||
total_bytes_read - last_stats_.compaction_bytes_read_;
|
||||
interval_bytes_written =
|
||||
total_bytes_written - last_stats_.compaction_bytes_written_;
|
||||
interval_seconds_up = seconds_up - last_stats_.seconds_up_;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Writes cumulative: %llu total, %llu batches, "
|
||||
"%.1f per batch, %.2f ingest GB\n",
|
||||
(unsigned long long)(write_other + write_self),
|
||||
(unsigned long long)write_self,
|
||||
(write_other + write_self) / (double)(write_self + 1),
|
||||
user_bytes_written / (1048576.0 * 1024));
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"WAL cumulative: %llu WAL writes, %llu WAL syncs, "
|
||||
"%.2f writes per sync, %.2f GB written\n",
|
||||
(unsigned long long)write_with_wal,
|
||||
(unsigned long long)wal_synced,
|
||||
write_with_wal / (double)(wal_synced + 1),
|
||||
wal_bytes / (1048576.0 * 1024));
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Compaction IO cumulative (GB): "
|
||||
"%.2f new, %.2f read, %.2f write, %.2f read+write\n",
|
||||
user_bytes_written / (1048576.0 * 1024),
|
||||
total_bytes_read / (1048576.0 * 1024),
|
||||
total_bytes_written / (1048576.0 * 1024),
|
||||
(total_bytes_read + total_bytes_written) / (1048576.0 * 1024));
|
||||
value->append(buf);
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Compaction IO cumulative (MB/sec): "
|
||||
"%.1f new, %.1f read, %.1f write, %.1f read+write\n",
|
||||
user_bytes_written / 1048576.0 / seconds_up,
|
||||
total_bytes_read / 1048576.0 / seconds_up,
|
||||
total_bytes_written / 1048576.0 / seconds_up,
|
||||
(total_bytes_read + total_bytes_written) / 1048576.0 / seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
// +1 to avoid divide by 0 and NaN
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Amplification cumulative: %.1f write, %.1f compaction\n",
|
||||
(double)(total_bytes_written + wal_bytes) / (user_bytes_written + 1),
|
||||
(double)(total_bytes_written + total_bytes_read + wal_bytes) /
|
||||
(user_bytes_written + 1));
|
||||
value->append(buf);
|
||||
|
||||
uint64_t interval_write_other = write_other - last_stats_.write_other_;
|
||||
uint64_t interval_write_self = write_self - last_stats_.write_self_;
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Writes interval: %llu total, %llu batches, "
|
||||
"%.1f per batch, %.1f ingest MB\n",
|
||||
(unsigned long long)(interval_write_other + interval_write_self),
|
||||
(unsigned long long)interval_write_self,
|
||||
(double)(interval_write_other + interval_write_self) /
|
||||
(interval_write_self + 1),
|
||||
(user_bytes_written - last_stats_.ingest_bytes_) / 1048576.0);
|
||||
value->append(buf);
|
||||
|
||||
uint64_t interval_write_with_wal =
|
||||
write_with_wal - last_stats_.write_with_wal_;
|
||||
|
||||
uint64_t interval_wal_synced = wal_synced - last_stats_.wal_synced_;
|
||||
uint64_t interval_wal_bytes = wal_bytes - last_stats_.wal_bytes_;
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"WAL interval: %llu WAL writes, %llu WAL syncs, "
|
||||
"%.2f writes per sync, %.2f MB written\n",
|
||||
(unsigned long long)interval_write_with_wal,
|
||||
(unsigned long long)interval_wal_synced,
|
||||
interval_write_with_wal / (double)(interval_wal_synced + 1),
|
||||
interval_wal_bytes / (1048576.0 * 1024));
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Compaction IO interval (MB): "
|
||||
"%.2f new, %.2f read, %.2f write, %.2f read+write\n",
|
||||
interval_bytes_new / 1048576.0, interval_bytes_read / 1048576.0,
|
||||
interval_bytes_written / 1048576.0,
|
||||
(interval_bytes_read + interval_bytes_written) / 1048576.0);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Compaction IO interval (MB/sec): "
|
||||
"%.1f new, %.1f read, %.1f write, %.1f read+write\n",
|
||||
interval_bytes_new / 1048576.0 / interval_seconds_up,
|
||||
interval_bytes_read / 1048576.0 / interval_seconds_up,
|
||||
interval_bytes_written / 1048576.0 / interval_seconds_up,
|
||||
(interval_bytes_read + interval_bytes_written) / 1048576.0 /
|
||||
interval_seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
// +1 to avoid divide by 0 and NaN
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Amplification interval: %.1f write, %.1f compaction\n",
|
||||
(double)(interval_bytes_written + interval_wal_bytes) /
|
||||
(interval_bytes_new + 1),
|
||||
(double)(interval_bytes_written + interval_bytes_read + interval_wal_bytes) /
|
||||
(interval_bytes_new + 1));
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Stalls(secs): %.3f level0_slowdown, %.3f level0_numfiles, "
|
||||
"%.3f memtable_compaction, %.3f leveln_slowdown\n",
|
||||
stall_micros_[LEVEL0_SLOWDOWN] / 1000000.0,
|
||||
stall_micros_[LEVEL0_NUM_FILES] / 1000000.0,
|
||||
stall_micros_[MEMTABLE_COMPACTION] / 1000000.0,
|
||||
total_slowdown / 1000000.0);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Stalls(count): %lu level0_slowdown, %lu level0_numfiles, "
|
||||
"%lu memtable_compaction, %lu leveln_slowdown\n",
|
||||
(unsigned long)stall_counts_[LEVEL0_SLOWDOWN],
|
||||
(unsigned long)stall_counts_[LEVEL0_NUM_FILES],
|
||||
(unsigned long)stall_counts_[MEMTABLE_COMPACTION],
|
||||
(unsigned long)total_slowdown_count);
|
||||
value->append(buf);
|
||||
|
||||
last_stats_.compaction_bytes_read_ = total_bytes_read;
|
||||
last_stats_.compaction_bytes_written_ = total_bytes_written;
|
||||
last_stats_.ingest_bytes_ = user_bytes_written;
|
||||
last_stats_.seconds_up_ = seconds_up;
|
||||
last_stats_.wal_bytes_ = wal_bytes;
|
||||
last_stats_.wal_synced_ = wal_synced;
|
||||
last_stats_.write_with_wal_ = write_with_wal;
|
||||
last_stats_.write_other_ = write_other;
|
||||
last_stats_.write_self_ = write_self;
|
||||
|
||||
return true;
|
||||
}
|
||||
case kCFStats: {
|
||||
DumpCFStats(value);
|
||||
return true;
|
||||
}
|
||||
case kDBStats: {
|
||||
DumpDBStats(value);
|
||||
return true;
|
||||
}
|
||||
case kSsTables:
|
||||
*value = current->DebugString();
|
||||
return true;
|
||||
case kNumImmutableMemTable:
|
||||
*value = std::to_string(cfd->imm()->size());
|
||||
return true;
|
||||
case kMemtableFlushPending:
|
||||
// Return number of mem tables that are ready to flush (made immutable)
|
||||
*value = std::to_string(cfd->imm()->IsFlushPending() ? 1 : 0);
|
||||
return true;
|
||||
case kCompactionPending:
|
||||
// 1 if the system already determines at least one compacdtion is needed.
|
||||
// 0 otherwise,
|
||||
*value = std::to_string(current->NeedsCompaction() ? 1 : 0);
|
||||
return true;
|
||||
case kBackgroundErrors:
|
||||
// Accumulated number of errors in background flushes or compactions.
|
||||
*value = std::to_string(GetBackgroundErrorCount());
|
||||
return true;
|
||||
case kCurSizeActiveMemTable:
|
||||
// Current size of the active memtable
|
||||
*value = std::to_string(cfd->mem()->ApproximateMemoryUsage());
|
||||
return true;
|
||||
case kNumEntriesInMutableMemtable:
|
||||
// Current size of the active memtable
|
||||
*value = std::to_string(cfd->mem()->GetNumEntries());
|
||||
return true;
|
||||
case kNumEntriesInImmutableMemtable:
|
||||
// Current size of the active memtable
|
||||
*value = std::to_string(cfd->imm()->current()->GetTotalNumEntries());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalStats::GetIntProperty(DBPropertyType property_type,
|
||||
const Slice& property,
|
||||
uint64_t* value) const {
|
||||
Version* current = cfd_->current();
|
||||
|
||||
switch (property_type) {
|
||||
case kNumImmutableMemTable:
|
||||
*value = cfd_->imm()->size();
|
||||
return true;
|
||||
case kMemtableFlushPending:
|
||||
// Return number of mem tables that are ready to flush (made immutable)
|
||||
*value = (cfd_->imm()->IsFlushPending() ? 1 : 0);
|
||||
return true;
|
||||
case kCompactionPending:
|
||||
// 1 if the system already determines at least one compacdtion is needed.
|
||||
// 0 otherwise,
|
||||
*value = (current->NeedsCompaction() ? 1 : 0);
|
||||
return true;
|
||||
case kBackgroundErrors:
|
||||
// Accumulated number of errors in background flushes or compactions.
|
||||
*value = GetBackgroundErrorCount();
|
||||
return true;
|
||||
case kCurSizeActiveMemTable:
|
||||
// Current size of the active memtable
|
||||
*value = cfd_->mem()->ApproximateMemoryUsage();
|
||||
return true;
|
||||
case kNumEntriesInMutableMemtable:
|
||||
// Current size of the active memtable
|
||||
*value = cfd_->mem()->GetNumEntries();
|
||||
return true;
|
||||
case kNumEntriesInImmutableMemtable:
|
||||
// Current size of the active memtable
|
||||
*value = cfd_->imm()->current()->GetTotalNumEntries();
|
||||
return true;
|
||||
case kEstimatedNumKeys:
|
||||
// Estimate number of entries in the column family:
|
||||
// Use estimated entries in tables + total entries in memtables.
|
||||
*value = cfd_->mem()->GetNumEntries() +
|
||||
cfd_->imm()->current()->GetTotalNumEntries() +
|
||||
current->GetEstimatedActiveKeys();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void InternalStats::DumpDBStats(std::string* value) {
|
||||
char buf[1000];
|
||||
// DB-level stats, only available from default column family
|
||||
double seconds_up = (env_->NowMicros() - started_at_ + 1) / 1000000.0;
|
||||
double interval_seconds_up = seconds_up - db_stats_snapshot_.seconds_up;
|
||||
snprintf(buf, sizeof(buf),
|
||||
"\n** DB Stats **\nUptime(secs): %.1f total, %.1f interval\n",
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
// Cumulative
|
||||
uint64_t user_bytes_written = db_stats_[InternalStats::BYTES_WRITTEN];
|
||||
uint64_t write_other = db_stats_[InternalStats::WRITE_DONE_BY_OTHER];
|
||||
uint64_t write_self = db_stats_[InternalStats::WRITE_DONE_BY_SELF];
|
||||
uint64_t wal_bytes = db_stats_[InternalStats::WAL_FILE_BYTES];
|
||||
uint64_t wal_synced = db_stats_[InternalStats::WAL_FILE_SYNCED];
|
||||
uint64_t write_with_wal = db_stats_[InternalStats::WRITE_WITH_WAL];
|
||||
// Data
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative writes: %" PRIu64 " writes, %" PRIu64 " batches, "
|
||||
"%.1f writes per batch, %.2f GB user ingest\n",
|
||||
write_other + write_self, write_self,
|
||||
(write_other + write_self) / static_cast<double>(write_self + 1),
|
||||
user_bytes_written / kGB);
|
||||
value->append(buf);
|
||||
// WAL
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative WAL: %" PRIu64 " writes, %" PRIu64 " syncs, "
|
||||
"%.2f writes per sync, %.2f GB written\n",
|
||||
write_with_wal, wal_synced,
|
||||
write_with_wal / static_cast<double>(wal_synced + 1),
|
||||
wal_bytes / kGB);
|
||||
value->append(buf);
|
||||
|
||||
// Interval
|
||||
uint64_t interval_write_other = write_other - db_stats_snapshot_.write_other;
|
||||
uint64_t interval_write_self = write_self - db_stats_snapshot_.write_self;
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Interval writes: %" PRIu64 " writes, %" PRIu64 " batches, "
|
||||
"%.1f writes per batch, %.1f MB user ingest\n",
|
||||
interval_write_other + interval_write_self,
|
||||
interval_write_self,
|
||||
static_cast<double>(interval_write_other + interval_write_self) /
|
||||
(interval_write_self + 1),
|
||||
(user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB);
|
||||
value->append(buf);
|
||||
|
||||
uint64_t interval_write_with_wal =
|
||||
write_with_wal - db_stats_snapshot_.write_with_wal;
|
||||
uint64_t interval_wal_synced = wal_synced - db_stats_snapshot_.wal_synced;
|
||||
uint64_t interval_wal_bytes = wal_bytes - db_stats_snapshot_.wal_bytes;
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Interval WAL: %" PRIu64 " writes, %" PRIu64 " syncs, "
|
||||
"%.2f writes per sync, %.2f MB written\n",
|
||||
interval_write_with_wal,
|
||||
interval_wal_synced,
|
||||
interval_write_with_wal /
|
||||
static_cast<double>(interval_wal_synced + 1),
|
||||
interval_wal_bytes / kGB);
|
||||
value->append(buf);
|
||||
|
||||
db_stats_snapshot_.seconds_up = seconds_up;
|
||||
db_stats_snapshot_.ingest_bytes = user_bytes_written;
|
||||
db_stats_snapshot_.write_other = write_other;
|
||||
db_stats_snapshot_.write_self = write_self;
|
||||
db_stats_snapshot_.wal_bytes = wal_bytes;
|
||||
db_stats_snapshot_.wal_synced = wal_synced;
|
||||
db_stats_snapshot_.write_with_wal = write_with_wal;
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFStats(std::string* value) {
|
||||
Version* current = cfd_->current();
|
||||
|
||||
int num_levels_to_check =
|
||||
(cfd_->options()->compaction_style != kCompactionStyleUniversal &&
|
||||
cfd_->options()->compaction_style != kCompactionStyleFIFO)
|
||||
? current->NumberLevels() - 1
|
||||
: 1;
|
||||
// Compaction scores are sorted base on its value. Restore them to the
|
||||
// level order
|
||||
std::vector<double> compaction_score(number_levels_, 0);
|
||||
for (int i = 0; i < num_levels_to_check; ++i) {
|
||||
compaction_score[current->compaction_level_[i]] =
|
||||
current->compaction_score_[i];
|
||||
}
|
||||
// Count # of files being compacted for each level
|
||||
std::vector<int> files_being_compacted(number_levels_, 0);
|
||||
for (int level = 0; level < num_levels_to_check; ++level) {
|
||||
for (auto* f : current->files_[level]) {
|
||||
if (f->being_compacted) {
|
||||
++files_being_compacted[level];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char buf[1000];
|
||||
// Per-ColumnFamily stats
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName());
|
||||
value->append(buf);
|
||||
|
||||
CompactionStats stats_sum(0);
|
||||
int total_files = 0;
|
||||
int total_files_being_compacted = 0;
|
||||
double total_file_size = 0;
|
||||
uint64_t total_slowdown_soft = 0;
|
||||
uint64_t total_slowdown_count_soft = 0;
|
||||
uint64_t total_slowdown_hard = 0;
|
||||
uint64_t total_slowdown_count_hard = 0;
|
||||
uint64_t total_stall_count = 0;
|
||||
double total_stall_us = 0;
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
int files = current->NumLevelFiles(level);
|
||||
total_files += files;
|
||||
total_files_being_compacted += files_being_compacted[level];
|
||||
if (comp_stats_[level].micros > 0 || files > 0) {
|
||||
uint64_t stalls = level == 0 ?
|
||||
(cf_stats_count_[LEVEL0_SLOWDOWN] +
|
||||
cf_stats_count_[LEVEL0_NUM_FILES] +
|
||||
cf_stats_count_[MEMTABLE_COMPACTION])
|
||||
: (stall_leveln_slowdown_count_soft_[level] +
|
||||
stall_leveln_slowdown_count_hard_[level]);
|
||||
|
||||
double stall_us = level == 0 ?
|
||||
(cf_stats_value_[LEVEL0_SLOWDOWN] +
|
||||
cf_stats_value_[LEVEL0_NUM_FILES] +
|
||||
cf_stats_value_[MEMTABLE_COMPACTION])
|
||||
: (stall_leveln_slowdown_soft_[level] +
|
||||
stall_leveln_slowdown_hard_[level]);
|
||||
|
||||
stats_sum.Add(comp_stats_[level]);
|
||||
total_file_size += current->NumLevelBytes(level);
|
||||
total_stall_us += stall_us;
|
||||
total_stall_count += stalls;
|
||||
total_slowdown_soft += stall_leveln_slowdown_soft_[level];
|
||||
total_slowdown_count_soft += stall_leveln_slowdown_count_soft_[level];
|
||||
total_slowdown_hard += stall_leveln_slowdown_hard_[level];
|
||||
total_slowdown_count_hard += stall_leveln_slowdown_count_hard_[level];
|
||||
int64_t bytes_read = comp_stats_[level].bytes_readn +
|
||||
comp_stats_[level].bytes_readnp1;
|
||||
double rw_amp = (comp_stats_[level].bytes_readn == 0) ? 0.0
|
||||
: (comp_stats_[level].bytes_written + bytes_read) /
|
||||
static_cast<double>(comp_stats_[level].bytes_readn);
|
||||
double w_amp = (comp_stats_[level].bytes_readn == 0) ? 0.0
|
||||
: comp_stats_[level].bytes_written /
|
||||
static_cast<double>(comp_stats_[level].bytes_readn);
|
||||
PrintLevelStats(buf, sizeof(buf), "L" + std::to_string(level),
|
||||
files, files_being_compacted[level], current->NumLevelBytes(level),
|
||||
compaction_score[level], rw_amp, w_amp, stall_us, stalls,
|
||||
comp_stats_[level]);
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
uint64_t curr_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
// Cumulative summary
|
||||
double rw_amp = (stats_sum.bytes_written + stats_sum.bytes_readn +
|
||||
stats_sum.bytes_readnp1) / static_cast<double>(curr_ingest + 1);
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
// Stats summary across levels
|
||||
PrintLevelStats(buf, sizeof(buf), "Sum", total_files,
|
||||
total_files_being_compacted, total_file_size, 0, rw_amp, w_amp,
|
||||
total_stall_us, total_stall_count, stats_sum);
|
||||
value->append(buf);
|
||||
// Interval summary
|
||||
uint64_t interval_ingest =
|
||||
curr_ingest - cf_stats_snapshot_.ingest_bytes + 1;
|
||||
CompactionStats interval_stats(stats_sum);
|
||||
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
||||
rw_amp = (interval_stats.bytes_written +
|
||||
interval_stats.bytes_readn + interval_stats.bytes_readnp1) /
|
||||
static_cast<double>(interval_ingest);
|
||||
w_amp = interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0,
|
||||
rw_amp, w_amp, total_stall_us - cf_stats_snapshot_.stall_us,
|
||||
total_stall_count - cf_stats_snapshot_.stall_count, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Flush(GB): accumulative %.3f, interval %.3f\n",
|
||||
curr_ingest / kGB, interval_ingest / kGB);
|
||||
value->append(buf);
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Stalls(secs): %.3f level0_slowdown, %.3f level0_numfiles, "
|
||||
"%.3f memtable_compaction, %.3f leveln_slowdown_soft, "
|
||||
"%.3f leveln_slowdown_hard\n",
|
||||
cf_stats_value_[LEVEL0_SLOWDOWN] / 1000000.0,
|
||||
cf_stats_value_[LEVEL0_NUM_FILES] / 1000000.0,
|
||||
cf_stats_value_[MEMTABLE_COMPACTION] / 1000000.0,
|
||||
total_slowdown_soft / 1000000.0,
|
||||
total_slowdown_hard / 1000000.0);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Stalls(count): %" PRIu64 " level0_slowdown, "
|
||||
"%" PRIu64 " level0_numfiles, %" PRIu64 " memtable_compaction, "
|
||||
"%" PRIu64 " leveln_slowdown_soft, "
|
||||
"%" PRIu64 " leveln_slowdown_hard\n",
|
||||
cf_stats_count_[LEVEL0_SLOWDOWN],
|
||||
cf_stats_count_[LEVEL0_NUM_FILES],
|
||||
cf_stats_count_[MEMTABLE_COMPACTION],
|
||||
total_slowdown_count_soft, total_slowdown_count_hard);
|
||||
value->append(buf);
|
||||
|
||||
cf_stats_snapshot_.ingest_bytes = curr_ingest;
|
||||
cf_stats_snapshot_.comp_stats = stats_sum;
|
||||
cf_stats_snapshot_.stall_us = total_stall_us;
|
||||
cf_stats_snapshot_.stall_count = total_stall_count;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+143
-63
@@ -9,8 +9,6 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "util/statistics.h"
|
||||
#include "db/version_set.h"
|
||||
|
||||
#include <vector>
|
||||
@@ -23,11 +21,15 @@ namespace rocksdb {
|
||||
class MemTableList;
|
||||
class DBImpl;
|
||||
|
||||
enum DBPropertyType {
|
||||
enum DBPropertyType : uint32_t {
|
||||
kUnknown,
|
||||
kNumFilesAtLevel, // Number of files at a specific level
|
||||
kLevelStats, // Return number of files and total sizes of each level
|
||||
kStats, // Return general statitistics of DB
|
||||
kCFStats, // Return general statitistics of CF
|
||||
kDBStats, // Return general statitistics of DB
|
||||
kStats, // Return general statitistics of both DB and CF
|
||||
kSsTables, // Return a human readable string of current SST files
|
||||
kStartIntTypes, // ---- Dummy value to indicate the start of integer values
|
||||
kNumImmutableMemTable, // Return number of immutable mem tables
|
||||
kMemtableFlushPending, // Return 1 if mem table flushing is pending,
|
||||
// otherwise 0.
|
||||
@@ -38,45 +40,74 @@ enum DBPropertyType {
|
||||
// memtable.
|
||||
kNumEntriesInImmutableMemtable, // Return sum of number of entries in all
|
||||
// the immutable mem tables.
|
||||
kUnknown,
|
||||
kEstimatedNumKeys, // Estimated total number of keys in the database.
|
||||
};
|
||||
|
||||
extern DBPropertyType GetPropertyType(const Slice& property);
|
||||
|
||||
class InternalStats {
|
||||
public:
|
||||
enum WriteStallType {
|
||||
enum InternalCFStatsType {
|
||||
LEVEL0_SLOWDOWN,
|
||||
MEMTABLE_COMPACTION,
|
||||
LEVEL0_NUM_FILES,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
INTERNAL_CF_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
InternalStats(int num_levels, Env* env, Statistics* statistics)
|
||||
: compaction_stats_(num_levels),
|
||||
stall_micros_(WRITE_STALLS_ENUM_MAX, 0),
|
||||
stall_counts_(WRITE_STALLS_ENUM_MAX, 0),
|
||||
stall_leveln_slowdown_(num_levels, 0),
|
||||
stall_leveln_slowdown_count_(num_levels, 0),
|
||||
enum InternalDBStatsType {
|
||||
WAL_FILE_BYTES,
|
||||
WAL_FILE_SYNCED,
|
||||
BYTES_WRITTEN,
|
||||
WRITE_DONE_BY_OTHER,
|
||||
WRITE_DONE_BY_SELF,
|
||||
WRITE_WITH_WAL,
|
||||
INTERNAL_DB_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
InternalStats(int num_levels, Env* env, ColumnFamilyData* cfd)
|
||||
: db_stats_(INTERNAL_DB_STATS_ENUM_MAX),
|
||||
cf_stats_value_(INTERNAL_CF_STATS_ENUM_MAX),
|
||||
cf_stats_count_(INTERNAL_CF_STATS_ENUM_MAX),
|
||||
comp_stats_(num_levels),
|
||||
stall_leveln_slowdown_hard_(num_levels),
|
||||
stall_leveln_slowdown_count_hard_(num_levels),
|
||||
stall_leveln_slowdown_soft_(num_levels),
|
||||
stall_leveln_slowdown_count_soft_(num_levels),
|
||||
bg_error_count_(0),
|
||||
number_levels_(num_levels),
|
||||
statistics_(statistics),
|
||||
env_(env),
|
||||
started_at_(env->NowMicros()) {}
|
||||
cfd_(cfd),
|
||||
started_at_(env->NowMicros()) {
|
||||
for (int i = 0; i< INTERNAL_DB_STATS_ENUM_MAX; ++i) {
|
||||
db_stats_[i] = 0;
|
||||
}
|
||||
for (int i = 0; i< INTERNAL_CF_STATS_ENUM_MAX; ++i) {
|
||||
cf_stats_value_[i] = 0;
|
||||
cf_stats_count_[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < num_levels; ++i) {
|
||||
stall_leveln_slowdown_hard_[i] = 0;
|
||||
stall_leveln_slowdown_count_hard_[i] = 0;
|
||||
stall_leveln_slowdown_soft_[i] = 0;
|
||||
stall_leveln_slowdown_count_soft_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Per level compaction stats. compaction_stats_[level] stores the stats for
|
||||
// Per level compaction stats. comp_stats_[level] stores the stats for
|
||||
// compactions that produced data for the specified "level".
|
||||
struct CompactionStats {
|
||||
uint64_t micros;
|
||||
|
||||
// Bytes read from level N during compaction between levels N and N+1
|
||||
int64_t bytes_readn;
|
||||
uint64_t bytes_readn;
|
||||
|
||||
// Bytes read from level N+1 during compaction between levels N and N+1
|
||||
int64_t bytes_readnp1;
|
||||
uint64_t bytes_readnp1;
|
||||
|
||||
// Total bytes written during compaction between levels N and N+1
|
||||
int64_t bytes_written;
|
||||
uint64_t bytes_written;
|
||||
|
||||
// Files read from level N during compaction between levels N and N+1
|
||||
int files_in_leveln;
|
||||
@@ -90,7 +121,7 @@ class InternalStats {
|
||||
// Number of compactions done
|
||||
int count;
|
||||
|
||||
CompactionStats()
|
||||
explicit CompactionStats(int count = 0)
|
||||
: micros(0),
|
||||
bytes_readn(0),
|
||||
bytes_readnp1(0),
|
||||
@@ -98,7 +129,17 @@ class InternalStats {
|
||||
files_in_leveln(0),
|
||||
files_in_levelnp1(0),
|
||||
files_out_levelnp1(0),
|
||||
count(0) {}
|
||||
count(count) {}
|
||||
|
||||
explicit CompactionStats(const CompactionStats& c)
|
||||
: micros(c.micros),
|
||||
bytes_readn(c.bytes_readn),
|
||||
bytes_readnp1(c.bytes_readnp1),
|
||||
bytes_written(c.bytes_written),
|
||||
files_in_leveln(c.files_in_leveln),
|
||||
files_in_levelnp1(c.files_in_levelnp1),
|
||||
files_out_levelnp1(c.files_out_levelnp1),
|
||||
count(c.count) {}
|
||||
|
||||
void Add(const CompactionStats& c) {
|
||||
this->micros += c.micros;
|
||||
@@ -108,22 +149,42 @@ class InternalStats {
|
||||
this->files_in_leveln += c.files_in_leveln;
|
||||
this->files_in_levelnp1 += c.files_in_levelnp1;
|
||||
this->files_out_levelnp1 += c.files_out_levelnp1;
|
||||
this->count += 1;
|
||||
this->count += c.count;
|
||||
}
|
||||
|
||||
void Subtract(const CompactionStats& c) {
|
||||
this->micros -= c.micros;
|
||||
this->bytes_readn -= c.bytes_readn;
|
||||
this->bytes_readnp1 -= c.bytes_readnp1;
|
||||
this->bytes_written -= c.bytes_written;
|
||||
this->files_in_leveln -= c.files_in_leveln;
|
||||
this->files_in_levelnp1 -= c.files_in_levelnp1;
|
||||
this->files_out_levelnp1 -= c.files_out_levelnp1;
|
||||
this->count -= c.count;
|
||||
}
|
||||
};
|
||||
|
||||
void AddCompactionStats(int level, const CompactionStats& stats) {
|
||||
compaction_stats_[level].Add(stats);
|
||||
comp_stats_[level].Add(stats);
|
||||
}
|
||||
|
||||
void RecordWriteStall(WriteStallType write_stall_type, uint64_t micros) {
|
||||
stall_micros_[write_stall_type] += micros;
|
||||
++stall_counts_[write_stall_type];
|
||||
void RecordLevelNSlowdown(int level, uint64_t micros, bool soft) {
|
||||
if (soft) {
|
||||
stall_leveln_slowdown_soft_[level] += micros;
|
||||
++stall_leveln_slowdown_count_soft_[level];
|
||||
} else {
|
||||
stall_leveln_slowdown_hard_[level] += micros;
|
||||
++stall_leveln_slowdown_count_hard_[level];
|
||||
}
|
||||
}
|
||||
|
||||
void RecordLevelNSlowdown(int level, uint64_t micros) {
|
||||
stall_leveln_slowdown_[level] += micros;
|
||||
++stall_leveln_slowdown_count_[level];
|
||||
void AddCFStats(InternalCFStatsType type, uint64_t value) {
|
||||
cf_stats_value_[type] += value;
|
||||
++cf_stats_count_[type];
|
||||
}
|
||||
|
||||
void AddDBStats(InternalDBStatsType type, uint64_t value) {
|
||||
db_stats_[type] += value;
|
||||
}
|
||||
|
||||
uint64_t GetBackgroundErrorCount() const { return bg_error_count_; }
|
||||
@@ -131,45 +192,64 @@ class InternalStats {
|
||||
uint64_t BumpAndGetBackgroundErrorCount() { return ++bg_error_count_; }
|
||||
|
||||
bool GetProperty(DBPropertyType property_type, const Slice& property,
|
||||
std::string* value, ColumnFamilyData* cfd);
|
||||
std::string* value);
|
||||
|
||||
bool GetIntProperty(DBPropertyType property_type, const Slice& property,
|
||||
uint64_t* value) const;
|
||||
|
||||
private:
|
||||
std::vector<CompactionStats> compaction_stats_;
|
||||
void DumpDBStats(std::string* value);
|
||||
void DumpCFStats(std::string* value);
|
||||
|
||||
// Per-DB stats
|
||||
std::vector<uint64_t> db_stats_;
|
||||
// Per-ColumnFamily stats
|
||||
std::vector<uint64_t> cf_stats_value_;
|
||||
std::vector<uint64_t> cf_stats_count_;
|
||||
// Per-ColumnFamily/level compaction stats
|
||||
std::vector<CompactionStats> comp_stats_;
|
||||
// These count the number of microseconds for which MakeRoomForWrite stalls.
|
||||
std::vector<uint64_t> stall_leveln_slowdown_hard_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_hard_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_soft_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_soft_;
|
||||
|
||||
// Used to compute per-interval statistics
|
||||
struct StatsSnapshot {
|
||||
uint64_t compaction_bytes_read_; // Bytes read by compaction
|
||||
uint64_t compaction_bytes_written_; // Bytes written by compaction
|
||||
uint64_t ingest_bytes_; // Bytes written by user
|
||||
uint64_t wal_bytes_; // Bytes written to WAL
|
||||
uint64_t wal_synced_; // Number of times WAL is synced
|
||||
uint64_t write_with_wal_; // Number of writes that request WAL
|
||||
struct CFStatsSnapshot {
|
||||
// ColumnFamily-level stats
|
||||
CompactionStats comp_stats;
|
||||
uint64_t ingest_bytes; // Bytes written to L0
|
||||
uint64_t stall_us; // Stall time in micro-seconds
|
||||
uint64_t stall_count; // Stall count
|
||||
|
||||
CFStatsSnapshot()
|
||||
: comp_stats(0),
|
||||
ingest_bytes(0),
|
||||
stall_us(0),
|
||||
stall_count(0) {}
|
||||
} cf_stats_snapshot_;
|
||||
|
||||
struct DBStatsSnapshot {
|
||||
// DB-level stats
|
||||
uint64_t ingest_bytes; // Bytes written by user
|
||||
uint64_t wal_bytes; // Bytes written to WAL
|
||||
uint64_t wal_synced; // Number of times WAL is synced
|
||||
uint64_t write_with_wal; // Number of writes that request WAL
|
||||
// These count the number of writes processed by the calling thread or
|
||||
// another thread.
|
||||
uint64_t write_other_;
|
||||
uint64_t write_self_;
|
||||
double seconds_up_;
|
||||
uint64_t write_other;
|
||||
uint64_t write_self;
|
||||
double seconds_up;
|
||||
|
||||
StatsSnapshot()
|
||||
: compaction_bytes_read_(0),
|
||||
compaction_bytes_written_(0),
|
||||
ingest_bytes_(0),
|
||||
wal_bytes_(0),
|
||||
wal_synced_(0),
|
||||
write_with_wal_(0),
|
||||
write_other_(0),
|
||||
write_self_(0),
|
||||
seconds_up_(0) {}
|
||||
};
|
||||
|
||||
// Counters from the previous time per-interval stats were computed
|
||||
StatsSnapshot last_stats_;
|
||||
|
||||
// These count the number of microseconds for which MakeRoomForWrite stalls.
|
||||
std::vector<uint64_t> stall_micros_;
|
||||
std::vector<uint64_t> stall_counts_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_;
|
||||
std::vector<uint64_t> stall_leveln_slowdown_count_;
|
||||
DBStatsSnapshot()
|
||||
: ingest_bytes(0),
|
||||
wal_bytes(0),
|
||||
wal_synced(0),
|
||||
write_with_wal(0),
|
||||
write_other(0),
|
||||
write_self(0),
|
||||
seconds_up(0) {}
|
||||
} db_stats_snapshot_;
|
||||
|
||||
// Total number of background errors encountered. Every time a flush task
|
||||
// or compaction task fails, this counter is incremented. The failure can
|
||||
@@ -178,10 +258,10 @@ class InternalStats {
|
||||
// or compaction will cause the counter to increase too.
|
||||
uint64_t bg_error_count_;
|
||||
|
||||
int number_levels_;
|
||||
Statistics* statistics_;
|
||||
const int number_levels_;
|
||||
Env* env_;
|
||||
uint64_t started_at_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const uint64_t started_at_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+1
-1
@@ -12,12 +12,12 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "util/testharness.h"
|
||||
#include "utilities/db_ttl.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace rocksdb;
|
||||
|
||||
+211
-65
@@ -23,6 +23,7 @@
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/bloom_block.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "table/plain_table_reader.h"
|
||||
#include "util/hash.h"
|
||||
@@ -61,8 +62,20 @@ class PlainTableDBTest {
|
||||
// Return the current option configuration.
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
options.table_factory.reset(NewPlainTableFactory(0, 2, 0.8, 3, 0, kPrefix));
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true, 3));
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 0;
|
||||
plain_table_options.bloom_bits_per_key = 2;
|
||||
plain_table_options.hash_table_ratio = 0.8;
|
||||
plain_table_options.index_sparseness = 3;
|
||||
plain_table_options.huge_page_tlb_size = 0;
|
||||
plain_table_options.encoding_type = kPrefix;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = false;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
options.allow_mmap_reads = true;
|
||||
return options;
|
||||
@@ -175,6 +188,8 @@ TEST(PlainTableDBTest, Empty) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("0000000000000foo"));
|
||||
}
|
||||
|
||||
extern const uint64_t kPlainTableMagicNumber;
|
||||
|
||||
class TestPlainTableReader : public PlainTableReader {
|
||||
public:
|
||||
TestPlainTableReader(const EnvOptions& storage_options,
|
||||
@@ -184,7 +199,8 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
size_t index_sparseness,
|
||||
const TableProperties* table_properties,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
const Options& options, bool* expect_bloom_not_match)
|
||||
const Options& options, bool* expect_bloom_not_match,
|
||||
bool store_index_in_file)
|
||||
: PlainTableReader(options, std::move(file), storage_options, icomparator,
|
||||
encoding_type, file_size, table_properties),
|
||||
expect_bloom_not_match_(expect_bloom_not_match) {
|
||||
@@ -195,6 +211,19 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
bloom_bits_per_key, hash_table_ratio, index_sparseness,
|
||||
2 * 1024 * 1024);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
TableProperties* props = const_cast<TableProperties*>(table_properties);
|
||||
if (store_index_in_file) {
|
||||
auto bloom_version_ptr = props->user_collected_properties.find(
|
||||
PlainTablePropertyNames::kBloomVersion);
|
||||
ASSERT_TRUE(bloom_version_ptr != props->user_collected_properties.end());
|
||||
ASSERT_EQ(bloom_version_ptr->second, std::string("1"));
|
||||
if (options.bloom_locality > 0) {
|
||||
auto num_blocks_ptr = props->user_collected_properties.find(
|
||||
PlainTablePropertyNames::kNumBloomBlocks);
|
||||
ASSERT_TRUE(num_blocks_ptr != props->user_collected_properties.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~TestPlainTableReader() {}
|
||||
@@ -202,7 +231,11 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
private:
|
||||
virtual bool MatchBloom(uint32_t hash) const override {
|
||||
bool ret = PlainTableReader::MatchBloom(hash);
|
||||
ASSERT_TRUE(!*expect_bloom_not_match_ || !ret);
|
||||
if (*expect_bloom_not_match_) {
|
||||
ASSERT_TRUE(!ret);
|
||||
} else {
|
||||
ASSERT_TRUE(ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
bool* expect_bloom_not_match_;
|
||||
@@ -212,16 +245,12 @@ extern const uint64_t kPlainTableMagicNumber;
|
||||
class TestPlainTableFactory : public PlainTableFactory {
|
||||
public:
|
||||
explicit TestPlainTableFactory(bool* expect_bloom_not_match,
|
||||
uint32_t user_key_len, int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
size_t huge_page_tlb_size,
|
||||
EncodingType encoding_type)
|
||||
: PlainTableFactory(user_key_len, bloom_bits_per_key, hash_table_ratio,
|
||||
index_sparseness, huge_page_tlb_size, encoding_type),
|
||||
bloom_bits_per_key_(bloom_bits_per_key),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
index_sparseness_(index_sparseness),
|
||||
const PlainTableOptions& options)
|
||||
: PlainTableFactory(options),
|
||||
bloom_bits_per_key_(options.bloom_bits_per_key),
|
||||
hash_table_ratio_(options.hash_table_ratio),
|
||||
index_sparseness_(options.index_sparseness),
|
||||
store_index_in_file_(options.store_index_in_file),
|
||||
expect_bloom_not_match_(expect_bloom_not_match) {}
|
||||
|
||||
Status NewTableReader(const Options& options, const EnvOptions& soptions,
|
||||
@@ -233,6 +262,20 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
options.env, options.info_log.get(), &props);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
if (store_index_in_file_) {
|
||||
BlockHandle bloom_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
options.env, BloomBlockBuilder::kBloomBlock,
|
||||
&bloom_block_handle);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
BlockHandle index_block_handle;
|
||||
s = FindMetaBlock(
|
||||
file.get(), file_size, kPlainTableMagicNumber, options.env,
|
||||
PlainTableIndexBuilder::kPlainTableIndexBlock, &index_block_handle);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto encoding_type_prop =
|
||||
user_props.find(PlainTablePropertyNames::kEncodingType);
|
||||
@@ -243,7 +286,8 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
std::unique_ptr<PlainTableReader> new_reader(new TestPlainTableReader(
|
||||
soptions, internal_comparator, encoding_type, file_size,
|
||||
bloom_bits_per_key_, hash_table_ratio_, index_sparseness_, props,
|
||||
std::move(file), options, expect_bloom_not_match_));
|
||||
std::move(file), options, expect_bloom_not_match_,
|
||||
store_index_in_file_));
|
||||
|
||||
*table = std::move(new_reader);
|
||||
return s;
|
||||
@@ -253,6 +297,7 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
int bloom_bits_per_key_;
|
||||
double hash_table_ratio_;
|
||||
size_t index_sparseness_;
|
||||
bool store_index_in_file_;
|
||||
bool* expect_bloom_not_match_;
|
||||
};
|
||||
|
||||
@@ -262,40 +307,75 @@ TEST(PlainTableDBTest, Flush) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.prefix_extractor.reset();
|
||||
options.table_factory.reset(NewPlainTableFactory(
|
||||
0, bloom_bits, 0, 2, huge_page_tlb_size, encoding_type));
|
||||
} else {
|
||||
options.table_factory.reset(NewPlainTableFactory(
|
||||
0, bloom_bits, 0.75, 16, huge_page_tlb_size, encoding_type));
|
||||
for (int store_index_in_file = 0; store_index_in_file <= 1;
|
||||
++store_index_in_file) {
|
||||
if (!bloom_bits && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.prefix_extractor.reset();
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 0;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 2;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
} else {
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 0;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
plain_table_options.index_sparseness = 16;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.full_scan_mode = false;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(plain_table_options));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
auto tp = row->second;
|
||||
|
||||
if (!store_index_in_file) {
|
||||
ASSERT_EQ(total_order ? "4" : "12",
|
||||
(tp->user_collected_properties)
|
||||
.at("plain_table_hash_table_size"));
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_sub_index_size"));
|
||||
} else {
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_hash_table_size"));
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_sub_index_size"));
|
||||
}
|
||||
ASSERT_EQ("v3", Get("1000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
auto tp = row->second;
|
||||
ASSERT_EQ(total_order ? "4" : "12", (tp->user_collected_properties).at(
|
||||
"plain_table_hash_table_size"));
|
||||
ASSERT_EQ("0", (tp->user_collected_properties)
|
||||
.at("plain_table_sub_index_size"));
|
||||
|
||||
ASSERT_EQ("v3", Get("1000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +385,15 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
if (encoding_type == kPrefix && total_order == 1) {
|
||||
for (int store_index_in_file = 0; store_index_in_file <= 1;
|
||||
++store_index_in_file) {
|
||||
if (encoding_type == kPrefix && total_order) {
|
||||
continue;
|
||||
}
|
||||
if (!bloom_bits && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
if (total_order && store_index_in_file) {
|
||||
continue;
|
||||
}
|
||||
bool expect_bloom_not_match = false;
|
||||
@@ -313,16 +401,23 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
PlainTableOptions plain_table_options;
|
||||
if (total_order) {
|
||||
options.prefix_extractor = nullptr;
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 0, bloom_bits, 0, 2, huge_page_tlb_size,
|
||||
encoding_type));
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 2;
|
||||
} else {
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 0, bloom_bits, 0.75, 16,
|
||||
huge_page_tlb_size, encoding_type));
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
plain_table_options.index_sparseness = 16;
|
||||
}
|
||||
plain_table_options.user_key_len = kPlainTableVariableLength;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
plain_table_options.store_index_in_file = store_index_in_file;
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, plain_table_options));
|
||||
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("0000000000000bar", "b"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
@@ -350,7 +445,6 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
// Neither key nor value should exist.
|
||||
expect_bloom_not_match = true;
|
||||
ASSERT_EQ("NOT_FOUND", Get("5_not00000000bar"));
|
||||
|
||||
// Key doesn't exist any more but prefix exists.
|
||||
if (total_order) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("1000000000000not"));
|
||||
@@ -359,6 +453,7 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
expect_bloom_not_match = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,13 +475,28 @@ TEST(PlainTableDBTest, Iterator) {
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.prefix_extractor = nullptr;
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 2;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 16, bloom_bits, 0, 2, huge_page_tlb_size,
|
||||
encoding_type));
|
||||
&expect_bloom_not_match, plain_table_options));
|
||||
} else {
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = bloom_bits;
|
||||
plain_table_options.hash_table_ratio = 0.75;
|
||||
plain_table_options.index_sparseness = 16;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
plain_table_options.encoding_type = encoding_type;
|
||||
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 16, bloom_bits, 0.75, 16,
|
||||
huge_page_tlb_size, encoding_type));
|
||||
&expect_bloom_not_match, plain_table_options));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -485,7 +595,13 @@ std::string MakeLongKey(size_t length, char c) {
|
||||
|
||||
TEST(PlainTableDBTest, IteratorLargeKeys) {
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewPlainTableFactory(0, 0, 0));
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 0;
|
||||
plain_table_options.bloom_bits_per_key = 0;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset();
|
||||
DestroyAndReopen(&options);
|
||||
@@ -529,7 +645,16 @@ std::string MakeLongKeyWithPrefix(size_t length, char c) {
|
||||
|
||||
TEST(PlainTableDBTest, IteratorLargeKeysWithPrefix) {
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewPlainTableFactory(16, 0, 0.8, 3, 0, kPrefix));
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = 0;
|
||||
plain_table_options.hash_table_ratio = 0.8;
|
||||
plain_table_options.index_sparseness = 3;
|
||||
plain_table_options.huge_page_tlb_size = 0;
|
||||
plain_table_options.encoding_type = kPrefix;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -665,8 +790,16 @@ TEST(PlainTableDBTest, HashBucketConflict) {
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(16, 0, 0, 2 ^ i, huge_page_tlb_size));
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = 0;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 2 ^ i;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("5000000000000fo0", "v1"));
|
||||
ASSERT_OK(Put("5000000000000fo1", "v2"));
|
||||
@@ -752,8 +885,15 @@ TEST(PlainTableDBTest, HashBucketConflictReverseSuffixComparator) {
|
||||
options.comparator = ∁
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
options.table_factory.reset(
|
||||
NewPlainTableFactory(16, 0, 0, 2 ^ i, huge_page_tlb_size));
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = 0;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 2 ^ i;
|
||||
plain_table_options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("5000000000000fo0", "v1"));
|
||||
ASSERT_OK(Put("5000000000000fo1", "v2"));
|
||||
@@ -833,7 +973,13 @@ TEST(PlainTableDBTest, NonExistingKeyToNonEmptyBucket) {
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
options.table_factory.reset(NewPlainTableFactory(16, 0, 0, 5));
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 16;
|
||||
plain_table_options.bloom_bits_per_key = 0;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
plain_table_options.index_sparseness = 5;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory(plain_table_options));
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("5000000000000fo0", "v1"));
|
||||
ASSERT_OK(Put("5000000000000fo1", "v2"));
|
||||
|
||||
+2
-1
@@ -126,7 +126,8 @@ class Repairer {
|
||||
std::vector<std::string> filenames;
|
||||
bool found_file = false;
|
||||
for (uint32_t path_id = 0; path_id < options_.db_paths.size(); path_id++) {
|
||||
Status status = env_->GetChildren(options_.db_paths[path_id], &filenames);
|
||||
Status status =
|
||||
env_->GetChildren(options_.db_paths[path_id].path, &filenames);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ class TableCache {
|
||||
|
||||
private:
|
||||
Env* const env_;
|
||||
const std::vector<std::string> db_paths_;
|
||||
const std::vector<DbPath> db_paths_;
|
||||
const Options* options_;
|
||||
const EnvOptions& storage_options_;
|
||||
Cache* const cache_;
|
||||
|
||||
@@ -213,7 +213,14 @@ TEST(TablePropertiesTest, CustomizedTablePropertiesCollector) {
|
||||
Options options;
|
||||
options.table_properties_collector_factories.emplace_back(
|
||||
new RegularKeysStartWithAFactory());
|
||||
options.table_factory = std::make_shared<PlainTableFactory>(8, 8, 0);
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 8;
|
||||
plain_table_options.bloom_bits_per_key = 8;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
|
||||
options.table_factory =
|
||||
std::make_shared<PlainTableFactory>(plain_table_options);
|
||||
test::PlainInternalKeyComparator ikc(options.comparator);
|
||||
TestCustomizedTablePropertiesCollector(kPlainTableMagicNumber, true, options,
|
||||
ikc);
|
||||
@@ -299,11 +306,15 @@ TEST(TablePropertiesTest, InternalKeyPropertiesCollector) {
|
||||
true /* not sanitize */,
|
||||
std::make_shared<BlockBasedTableFactory>()
|
||||
);
|
||||
|
||||
PlainTableOptions plain_table_options;
|
||||
plain_table_options.user_key_len = 8;
|
||||
plain_table_options.bloom_bits_per_key = 8;
|
||||
plain_table_options.hash_table_ratio = 0;
|
||||
|
||||
TestInternalKeyPropertiesCollector(
|
||||
kPlainTableMagicNumber,
|
||||
false /* not sanitize */,
|
||||
std::make_shared<PlainTableFactory>(8, 8, 0)
|
||||
);
|
||||
kPlainTableMagicNumber, false /* not sanitize */,
|
||||
std::make_shared<PlainTableFactory>(plain_table_options));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <string>
|
||||
#include "rocksdb/cache.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
+376
-303
@@ -40,6 +40,267 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
// Find File in FileLevel data structure
|
||||
// Within an index range defined by left and right
|
||||
int FindFileInRange(const InternalKeyComparator& icmp,
|
||||
const FileLevel& file_level,
|
||||
const Slice& key,
|
||||
uint32_t left,
|
||||
uint32_t right) {
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
const FdWithKeyRange& f = file_level.files[mid];
|
||||
if (icmp.InternalKeyComparator::Compare(f.largest_key, key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
return a->smallest_seqno > b->smallest_seqno;
|
||||
}
|
||||
if (a->largest_seqno != b->largest_seqno) {
|
||||
return a->largest_seqno > b->largest_seqno;
|
||||
}
|
||||
// Break ties by file number
|
||||
return a->fd.GetNumber() > b->fd.GetNumber();
|
||||
}
|
||||
|
||||
bool BySmallestKey(FileMetaData* a, FileMetaData* b,
|
||||
const InternalKeyComparator* cmp) {
|
||||
int r = cmp->Compare(a->smallest, b->smallest);
|
||||
if (r != 0) {
|
||||
return (r < 0);
|
||||
}
|
||||
// Break ties by file number
|
||||
return (a->fd.GetNumber() < b->fd.GetNumber());
|
||||
}
|
||||
|
||||
// Class to help choose the next file to search for the particular key.
|
||||
// Searches and returns files level by level.
|
||||
// We can search level-by-level since entries never hop across
|
||||
// levels. Therefore we are guaranteed that if we find data
|
||||
// in a smaller level, later levels are irrelevant (unless we
|
||||
// are MergeInProgress).
|
||||
class FilePicker {
|
||||
public:
|
||||
FilePicker(
|
||||
std::vector<FileMetaData*>* files,
|
||||
const Slice& user_key,
|
||||
const Slice& ikey,
|
||||
autovector<FileLevel>* file_levels,
|
||||
unsigned int num_levels,
|
||||
FileIndexer* file_indexer,
|
||||
const Comparator* user_comparator,
|
||||
const InternalKeyComparator* internal_comparator)
|
||||
: num_levels_(num_levels),
|
||||
curr_level_(-1),
|
||||
search_left_bound_(0),
|
||||
search_right_bound_(FileIndexer::kLevelMaxIndex),
|
||||
#ifndef NDEBUG
|
||||
files_(files),
|
||||
#endif
|
||||
file_levels_(file_levels),
|
||||
user_key_(user_key),
|
||||
ikey_(ikey),
|
||||
file_indexer_(file_indexer),
|
||||
user_comparator_(user_comparator),
|
||||
internal_comparator_(internal_comparator) {
|
||||
// Setup member variables to search first level.
|
||||
search_ended_ = !PrepareNextLevel();
|
||||
if (!search_ended_) {
|
||||
// Prefetch Level 0 table data to avoid cache miss if possible.
|
||||
for (unsigned int i = 0; i < (*file_levels_)[0].num_files; ++i) {
|
||||
auto* r = (*file_levels_)[0].files[i].fd.table_reader;
|
||||
if (r) {
|
||||
r->Prepare(ikey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FdWithKeyRange* GetNextFile() {
|
||||
while (!search_ended_) { // Loops over different levels.
|
||||
while (curr_index_in_curr_level_ < curr_file_level_->num_files) {
|
||||
// Loops over all files in current level.
|
||||
FdWithKeyRange* f = &curr_file_level_->files[curr_index_in_curr_level_];
|
||||
int cmp_largest = -1;
|
||||
|
||||
// Do key range filtering of files or/and fractional cascading if:
|
||||
// (1) not all the files are in level 0, or
|
||||
// (2) there are more than 3 Level 0 files
|
||||
// If there are only 3 or less level 0 files in the system, we skip
|
||||
// the key range filtering. In this case, more likely, the system is
|
||||
// highly tuned to minimize number of tables queried by each query,
|
||||
// so it is unlikely that key range filtering is more efficient than
|
||||
// querying the files.
|
||||
if (num_levels_ > 1 || curr_file_level_->num_files > 3) {
|
||||
// Check if key is within a file's range. If search left bound and
|
||||
// right bound point to the same find, we are sure key falls in
|
||||
// range.
|
||||
assert(
|
||||
curr_level_ == 0 ||
|
||||
curr_index_in_curr_level_ == start_index_in_curr_level_ ||
|
||||
user_comparator_->Compare(user_key_,
|
||||
ExtractUserKey(f->smallest_key)) <= 0);
|
||||
|
||||
int cmp_smallest = user_comparator_->Compare(user_key_,
|
||||
ExtractUserKey(f->smallest_key));
|
||||
if (cmp_smallest >= 0) {
|
||||
cmp_largest = user_comparator_->Compare(user_key_,
|
||||
ExtractUserKey(f->largest_key));
|
||||
}
|
||||
|
||||
// Setup file search bound for the next level based on the
|
||||
// comparison results
|
||||
if (curr_level_ > 0) {
|
||||
file_indexer_->GetNextLevelIndex(curr_level_,
|
||||
curr_index_in_curr_level_,
|
||||
cmp_smallest, cmp_largest,
|
||||
&search_left_bound_,
|
||||
&search_right_bound_);
|
||||
}
|
||||
// Key falls out of current file's range
|
||||
if (cmp_smallest < 0 || cmp_largest > 0) {
|
||||
if (curr_level_ == 0) {
|
||||
++curr_index_in_curr_level_;
|
||||
continue;
|
||||
} else {
|
||||
// Search next level.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// Sanity check to make sure that the files are correctly sorted
|
||||
if (prev_file_) {
|
||||
if (curr_level_ != 0) {
|
||||
int comp_sign = internal_comparator_->Compare(
|
||||
prev_file_->largest_key, f->smallest_key);
|
||||
assert(comp_sign < 0);
|
||||
} else {
|
||||
// level == 0, the current file cannot be newer than the previous
|
||||
// one. Use compressed data structure, has no attribute seqNo
|
||||
assert(curr_index_in_curr_level_ > 0);
|
||||
assert(!NewestFirstBySeqNo(files_[0][curr_index_in_curr_level_],
|
||||
files_[0][curr_index_in_curr_level_-1]));
|
||||
}
|
||||
}
|
||||
prev_file_ = f;
|
||||
#endif
|
||||
if (curr_level_ > 0 && cmp_largest < 0) {
|
||||
// No more files to search in this level.
|
||||
search_ended_ = !PrepareNextLevel();
|
||||
} else {
|
||||
++curr_index_in_curr_level_;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
// Start searching next level.
|
||||
search_ended_ = !PrepareNextLevel();
|
||||
}
|
||||
// Search ended.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int num_levels_;
|
||||
unsigned int curr_level_;
|
||||
int search_left_bound_;
|
||||
int search_right_bound_;
|
||||
#ifndef NDEBUG
|
||||
std::vector<FileMetaData*>* files_;
|
||||
#endif
|
||||
autovector<FileLevel>* file_levels_;
|
||||
bool search_ended_;
|
||||
FileLevel* curr_file_level_;
|
||||
unsigned int curr_index_in_curr_level_;
|
||||
unsigned int start_index_in_curr_level_;
|
||||
Slice user_key_;
|
||||
Slice ikey_;
|
||||
FileIndexer* file_indexer_;
|
||||
const Comparator* user_comparator_;
|
||||
const InternalKeyComparator* internal_comparator_;
|
||||
#ifndef NDEBUG
|
||||
FdWithKeyRange* prev_file_;
|
||||
#endif
|
||||
|
||||
// Setup local variables to search next level.
|
||||
// Returns false if there are no more levels to search.
|
||||
bool PrepareNextLevel() {
|
||||
curr_level_++;
|
||||
while (curr_level_ < num_levels_) {
|
||||
curr_file_level_ = &(*file_levels_)[curr_level_];
|
||||
if (curr_file_level_->num_files == 0) {
|
||||
// When current level is empty, the search bound generated from upper
|
||||
// level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is
|
||||
// also empty.
|
||||
assert(search_left_bound_ == 0);
|
||||
assert(search_right_bound_ == -1 ||
|
||||
search_right_bound_ == FileIndexer::kLevelMaxIndex);
|
||||
// Since current level is empty, it will need to search all files in
|
||||
// the next level
|
||||
search_left_bound_ = 0;
|
||||
search_right_bound_ = FileIndexer::kLevelMaxIndex;
|
||||
curr_level_++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some files may overlap each other. We find
|
||||
// all files that overlap user_key and process them in order from
|
||||
// newest to oldest. In the context of merge-operator, this can occur at
|
||||
// any level. Otherwise, it only occurs at Level-0 (since Put/Deletes
|
||||
// are always compacted into a single entry).
|
||||
int32_t start_index;
|
||||
if (curr_level_ == 0) {
|
||||
// On Level-0, we read through all files to check for overlap.
|
||||
start_index = 0;
|
||||
} else {
|
||||
// On Level-n (n>=1), files are sorted. Binary search to find the
|
||||
// earliest file whose largest key >= ikey. Search left bound and
|
||||
// right bound are used to narrow the range.
|
||||
if (search_left_bound_ == search_right_bound_) {
|
||||
start_index = search_left_bound_;
|
||||
} else if (search_left_bound_ < search_right_bound_) {
|
||||
if (search_right_bound_ == FileIndexer::kLevelMaxIndex) {
|
||||
search_right_bound_ = curr_file_level_->num_files - 1;
|
||||
}
|
||||
start_index = FindFileInRange(*internal_comparator_,
|
||||
*curr_file_level_, ikey_,
|
||||
search_left_bound_, search_right_bound_);
|
||||
} else {
|
||||
// search_left_bound > search_right_bound, key does not exist in
|
||||
// this level. Since no comparision is done in this level, it will
|
||||
// need to search all files in the next level.
|
||||
search_left_bound_ = 0;
|
||||
search_right_bound_ = FileIndexer::kLevelMaxIndex;
|
||||
curr_level_++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
start_index_in_curr_level_ = start_index;
|
||||
curr_index_in_curr_level_ = start_index;
|
||||
#ifndef NDEBUG
|
||||
prev_file_ = nullptr;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
// curr_level_ = num_levels_. So, no more levels to search.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
} // 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++) {
|
||||
@@ -82,62 +343,42 @@ Version::~Version() {
|
||||
delete[] files_;
|
||||
}
|
||||
|
||||
int FindFileInRange(const InternalKeyComparator& icmp,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const Slice& key,
|
||||
uint32_t left,
|
||||
uint32_t right) {
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
const FileMetaData* f = files[mid];
|
||||
if (icmp.InternalKeyComparator::Compare(f->largest.Encode(), key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
int FindFile(const InternalKeyComparator& icmp,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const Slice& key) {
|
||||
return FindFileInRange(icmp, files, key, 0, files.size());
|
||||
}
|
||||
|
||||
// Find File in FileLevel data structure
|
||||
// Within an index range defined by left and right
|
||||
int FindFileInRange(const InternalKeyComparator& icmp,
|
||||
const FileLevel& file_level,
|
||||
const Slice& key,
|
||||
uint32_t left,
|
||||
uint32_t right) {
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
const FdWithKeyRange& f = file_level.files[mid];
|
||||
if (icmp.InternalKeyComparator::Compare(f.largest_key, key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
int FindFile(const InternalKeyComparator& icmp,
|
||||
const FileLevel& file_level,
|
||||
const Slice& key) {
|
||||
return FindFileInRange(icmp, file_level, key, 0, file_level.num_files);
|
||||
}
|
||||
|
||||
void DoGenerateFileLevel(FileLevel* file_level,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
Arena* arena) {
|
||||
assert(file_level);
|
||||
assert(files.size() >= 0);
|
||||
assert(arena);
|
||||
|
||||
size_t num = files.size();
|
||||
file_level->num_files = num;
|
||||
char* mem = arena->AllocateAligned(num * sizeof(FdWithKeyRange));
|
||||
file_level->files = new (mem)FdWithKeyRange[num];
|
||||
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
Slice smallest_key = files[i]->smallest.Encode();
|
||||
Slice largest_key = files[i]->largest.Encode();
|
||||
|
||||
// Copy key slice to sequential memory
|
||||
size_t smallest_size = smallest_key.size();
|
||||
size_t largest_size = largest_key.size();
|
||||
mem = arena->AllocateAligned(smallest_size + largest_size);
|
||||
memcpy(mem, smallest_key.data(), smallest_size);
|
||||
memcpy(mem + smallest_size, largest_key.data(), largest_size);
|
||||
|
||||
FdWithKeyRange& f = file_level->files[i];
|
||||
f.fd = files[i]->fd;
|
||||
f.smallest_key = Slice(mem, smallest_size);
|
||||
f.largest_key = Slice(mem + smallest_size, largest_size);
|
||||
}
|
||||
}
|
||||
|
||||
static bool AfterFile(const Comparator* ucmp,
|
||||
const Slice* user_key, const FdWithKeyRange* f) {
|
||||
// nullptr user_key occurs before all keys and is therefore never after *f
|
||||
@@ -152,7 +393,6 @@ static bool BeforeFile(const Comparator* ucmp,
|
||||
ucmp->Compare(*user_key, ExtractUserKey(f->smallest_key)) < 0);
|
||||
}
|
||||
|
||||
|
||||
bool SomeFileOverlapsRange(
|
||||
const InternalKeyComparator& icmp,
|
||||
bool disjoint_sorted_files,
|
||||
@@ -198,21 +438,21 @@ bool SomeFileOverlapsRange(
|
||||
class Version::LevelFileNumIterator : public Iterator {
|
||||
public:
|
||||
LevelFileNumIterator(const InternalKeyComparator& icmp,
|
||||
const std::vector<FileMetaData*>* flist)
|
||||
const FileLevel* flevel)
|
||||
: icmp_(icmp),
|
||||
flist_(flist),
|
||||
index_(flist->size()),
|
||||
flevel_(flevel),
|
||||
index_(flevel->num_files),
|
||||
current_value_(0, 0, 0) { // Marks as invalid
|
||||
}
|
||||
virtual bool Valid() const {
|
||||
return index_ < flist_->size();
|
||||
return index_ < flevel_->num_files;
|
||||
}
|
||||
virtual void Seek(const Slice& target) {
|
||||
index_ = FindFile(icmp_, *flist_, target);
|
||||
index_ = FindFile(icmp_, *flevel_, target);
|
||||
}
|
||||
virtual void SeekToFirst() { index_ = 0; }
|
||||
virtual void SeekToLast() {
|
||||
index_ = flist_->empty() ? 0 : flist_->size() - 1;
|
||||
index_ = (flevel_->num_files == 0) ? 0 : flevel_->num_files - 1;
|
||||
}
|
||||
virtual void Next() {
|
||||
assert(Valid());
|
||||
@@ -221,26 +461,27 @@ class Version::LevelFileNumIterator : public Iterator {
|
||||
virtual void Prev() {
|
||||
assert(Valid());
|
||||
if (index_ == 0) {
|
||||
index_ = flist_->size(); // Marks as invalid
|
||||
index_ = flevel_->num_files; // Marks as invalid
|
||||
} else {
|
||||
index_--;
|
||||
}
|
||||
}
|
||||
Slice key() const {
|
||||
assert(Valid());
|
||||
return (*flist_)[index_]->largest.Encode();
|
||||
return flevel_->files[index_].largest_key;
|
||||
}
|
||||
Slice value() const {
|
||||
assert(Valid());
|
||||
auto* file_meta = (*flist_)[index_];
|
||||
current_value_ = file_meta->fd;
|
||||
|
||||
auto file_meta = flevel_->files[index_];
|
||||
current_value_ = file_meta.fd;
|
||||
return Slice(reinterpret_cast<const char*>(¤t_value_),
|
||||
sizeof(FileDescriptor));
|
||||
}
|
||||
virtual Status status() const { return Status::OK(); }
|
||||
private:
|
||||
const InternalKeyComparator icmp_;
|
||||
const std::vector<FileMetaData*>* const flist_;
|
||||
const FileLevel* flevel_;
|
||||
uint32_t index_;
|
||||
mutable FileDescriptor current_value_;
|
||||
};
|
||||
@@ -325,8 +566,7 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
RecordTick(options->statistics.get(),
|
||||
NUMBER_DIRECT_LOAD_TABLE_PROPERTIES);
|
||||
RecordTick(options->statistics.get(), NUMBER_DIRECT_LOAD_TABLE_PROPERTIES);
|
||||
|
||||
*tp = std::shared_ptr<const TableProperties>(raw_table_properties);
|
||||
return s;
|
||||
@@ -353,25 +593,35 @@ Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t Version::GetEstimatedActiveKeys() {
|
||||
// Estimation will be not accurate when:
|
||||
// (1) there is merge keys
|
||||
// (2) keys are directly overwritten
|
||||
// (3) deletion on non-existing keys
|
||||
return num_non_deletions_ - num_deletions_;
|
||||
}
|
||||
|
||||
void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
std::vector<Iterator*>* iters) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (const FileMetaData* file : files_[0]) {
|
||||
for (size_t i = 0; i < file_levels_[0].num_files; i++) {
|
||||
const auto& file = file_levels_[0].files[i];
|
||||
iters->push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file->fd));
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
for (int level = 1; level < num_levels_; level++) {
|
||||
if (!files_[level].empty()) {
|
||||
if (file_levels_[level].num_files != 0) {
|
||||
iters->push_back(NewTwoLevelIterator(new LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), false /* for_compaction */,
|
||||
cfd_->options()->prefix_extractor != nullptr),
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(), &files_[level])));
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(),
|
||||
&file_levels_[level])));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,9 +630,10 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (const FileMetaData* file : files_[0]) {
|
||||
for (size_t i = 0; i < file_levels_[0].num_files; i++) {
|
||||
const auto& file = file_levels_[0].files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file->fd, nullptr,
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd, nullptr,
|
||||
false, merge_iter_builder->GetArena()));
|
||||
}
|
||||
|
||||
@@ -390,14 +641,14 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
for (int level = 1; level < num_levels_; level++) {
|
||||
if (!files_[level].empty()) {
|
||||
if (file_levels_[level].num_files != 0) {
|
||||
merge_iter_builder->AddIterator(NewTwoLevelIterator(
|
||||
new LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), false /* for_compaction */,
|
||||
cfd_->options()->prefix_extractor != nullptr),
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(), &files_[level]),
|
||||
merge_iter_builder->GetArena()));
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(),
|
||||
&file_levels_[level]), merge_iter_builder->GetArena()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -501,36 +752,13 @@ static bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
return a->smallest_seqno > b->smallest_seqno;
|
||||
}
|
||||
if (a->largest_seqno != b->largest_seqno) {
|
||||
return a->largest_seqno > b->largest_seqno;
|
||||
}
|
||||
// Break ties by file number
|
||||
return a->fd.GetNumber() > b->fd.GetNumber();
|
||||
}
|
||||
bool BySmallestKey(FileMetaData* a, FileMetaData* b,
|
||||
const InternalKeyComparator* cmp) {
|
||||
int r = cmp->Compare(a->smallest, b->smallest);
|
||||
if (r != 0) {
|
||||
return (r < 0);
|
||||
}
|
||||
// Break ties by file number
|
||||
return (a->fd.GetNumber() < b->fd.GetNumber());
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
||||
uint64_t version_number)
|
||||
: cfd_(cfd),
|
||||
internal_comparator_((cfd == nullptr) ? nullptr
|
||||
: &cfd->internal_comparator()),
|
||||
user_comparator_((cfd == nullptr)
|
||||
? nullptr
|
||||
: internal_comparator_->user_comparator()),
|
||||
user_comparator_(
|
||||
(cfd == nullptr) ? nullptr : internal_comparator_->user_comparator()),
|
||||
table_cache_((cfd == nullptr) ? nullptr : cfd->table_cache()),
|
||||
merge_operator_((cfd == nullptr) ? nullptr
|
||||
: cfd->options()->merge_operator.get()),
|
||||
@@ -540,6 +768,9 @@ Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
||||
// cfd is nullptr if Version is dummy
|
||||
num_levels_(cfd == nullptr ? 0 : cfd->NumberLevels()),
|
||||
num_non_empty_levels_(num_levels_),
|
||||
file_indexer_(cfd == nullptr
|
||||
? nullptr
|
||||
: cfd->internal_comparator().user_comparator()),
|
||||
vset_(vset),
|
||||
next_(this),
|
||||
prev_(this),
|
||||
@@ -550,17 +781,17 @@ Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
||||
compaction_score_(num_levels_),
|
||||
compaction_level_(num_levels_),
|
||||
version_number_(version_number),
|
||||
file_indexer_(num_levels_, cfd == nullptr ? nullptr
|
||||
: cfd->internal_comparator().user_comparator()),
|
||||
total_file_size_(0),
|
||||
total_raw_key_size_(0),
|
||||
total_raw_value_size_(0),
|
||||
num_non_deletions_(0) {
|
||||
num_non_deletions_(0),
|
||||
num_deletions_(0) {
|
||||
if (cfd != nullptr && cfd->current() != nullptr) {
|
||||
total_file_size_ = cfd->current()->total_file_size_;
|
||||
total_raw_key_size_ = cfd->current()->total_raw_key_size_;
|
||||
total_raw_value_size_ = cfd->current()->total_raw_value_size_;
|
||||
num_non_deletions_ = cfd->current()->num_non_deletions_;
|
||||
num_deletions_ = cfd->current()->num_deletions_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,168 +816,34 @@ void Version::Get(const ReadOptions& options,
|
||||
saver.logger = info_log_;
|
||||
saver.statistics = db_statistics_;
|
||||
|
||||
// We can search level-by-level since entries never hop across
|
||||
// levels. Therefore we are guaranteed that if we find data
|
||||
// in an smaller level, later levels are irrelevant (unless we
|
||||
// are MergeInProgress).
|
||||
|
||||
int32_t search_left_bound = 0;
|
||||
int32_t search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
for (int level = 0; level < num_non_empty_levels_; ++level) {
|
||||
int num_files = file_levels_[level].num_files;
|
||||
if (num_files == 0) {
|
||||
// When current level is empty, the search bound generated from upper
|
||||
// level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is
|
||||
// also empty.
|
||||
assert(search_left_bound == 0);
|
||||
assert(search_right_bound == -1 ||
|
||||
search_right_bound == FileIndexer::kLevelMaxIndex);
|
||||
// Since current level is empty, it will need to search all files in the
|
||||
// next level
|
||||
search_left_bound = 0;
|
||||
search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
continue;
|
||||
FilePicker fp(files_, user_key, ikey, &file_levels_, num_non_empty_levels_,
|
||||
&file_indexer_, user_comparator_, internal_comparator_);
|
||||
FdWithKeyRange* f = fp.GetNextFile();
|
||||
while (f != nullptr) {
|
||||
*status = table_cache_->Get(options, *internal_comparator_, f->fd, ikey,
|
||||
&saver, SaveValue, MarkKeyMayExist);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefetch table data to avoid cache miss if possible
|
||||
if (level == 0) {
|
||||
for (int i = 0; i < num_files; ++i) {
|
||||
auto* r = file_levels_[0].files[i].fd.table_reader;
|
||||
if (r) {
|
||||
r->Prepare(ikey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list of files to search in this level
|
||||
FdWithKeyRange* files = file_levels_[level].files;
|
||||
|
||||
// Some files may overlap each other. We find
|
||||
// all files that overlap user_key and process them in order from
|
||||
// newest to oldest. In the context of merge-operator,
|
||||
// this can occur at any level. Otherwise, it only occurs
|
||||
// at Level-0 (since Put/Deletes are always compacted into a single entry).
|
||||
int32_t start_index;
|
||||
if (level == 0) {
|
||||
// On Level-0, we read through all files to check for overlap.
|
||||
start_index = 0;
|
||||
} else {
|
||||
// On Level-n (n>=1), files are sorted. Binary search to find the earliest
|
||||
// file whose largest key >= ikey. Search left bound and right bound are
|
||||
// used to narrow the range.
|
||||
if (search_left_bound == search_right_bound) {
|
||||
start_index = search_left_bound;
|
||||
} else if (search_left_bound < search_right_bound) {
|
||||
if (search_right_bound == FileIndexer::kLevelMaxIndex) {
|
||||
search_right_bound = num_files - 1;
|
||||
}
|
||||
start_index = FindFileInRange(cfd_->internal_comparator(),
|
||||
file_levels_[level], ikey,
|
||||
search_left_bound, search_right_bound);
|
||||
} else {
|
||||
// search_left_bound > search_right_bound, key does not exist in this
|
||||
// level. Since no comparision is done in this level, it will need to
|
||||
// search all files in the next level.
|
||||
search_left_bound = 0;
|
||||
search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Traverse each relevant file to find the desired key
|
||||
#ifndef NDEBUG
|
||||
FdWithKeyRange* prev_file = nullptr;
|
||||
#endif
|
||||
|
||||
for (int32_t i = start_index; i < num_files;) {
|
||||
FdWithKeyRange* f = &files[i];
|
||||
int cmp_largest = -1;
|
||||
|
||||
// Do key range filtering of files or/and fractional cascading if:
|
||||
// (1) not all the files are in level 0, or
|
||||
// (2) there are more than 3 Level 0 files
|
||||
// If there are only 3 or less level 0 files in the system, we skip the
|
||||
// key range filtering. In this case, more likely, the system is highly
|
||||
// tuned to minimize number of tables queried by each query, so it is
|
||||
// unlikely that key range filtering is more efficient than querying the
|
||||
// files.
|
||||
if (num_non_empty_levels_ > 1 || num_files > 3) {
|
||||
// Check if key is within a file's range. If search left bound and right
|
||||
// bound point to the same find, we are sure key falls in range.
|
||||
assert(
|
||||
level == 0 || i == start_index || user_comparator_->Compare(
|
||||
user_key, ExtractUserKey(f->smallest_key)) <= 0);
|
||||
|
||||
int cmp_smallest = user_comparator_->Compare(user_key,
|
||||
ExtractUserKey(f->smallest_key));
|
||||
if (cmp_smallest >= 0) {
|
||||
cmp_largest = user_comparator_->Compare(user_key,
|
||||
ExtractUserKey(f->largest_key));
|
||||
}
|
||||
|
||||
// Setup file search bound for the next level based on the comparison
|
||||
// results
|
||||
if (level > 0) {
|
||||
file_indexer_.GetNextLevelIndex(level, i, cmp_smallest, cmp_largest,
|
||||
&search_left_bound,
|
||||
&search_right_bound);
|
||||
}
|
||||
// Key falls out of current file's range
|
||||
if (cmp_smallest < 0 || cmp_largest > 0) {
|
||||
if (level == 0) {
|
||||
++i;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Sanity check to make sure that the files are correctly sorted
|
||||
if (prev_file) {
|
||||
if (level != 0) {
|
||||
int comp_sign = internal_comparator_->Compare(prev_file->largest_key,
|
||||
f->smallest_key);
|
||||
assert(comp_sign < 0);
|
||||
} else {
|
||||
// level == 0, the current file cannot be newer than the previous one.
|
||||
// Use compressed data structure, has no attribute seqNo
|
||||
assert(i > 0);
|
||||
assert(!NewestFirstBySeqNo(files_[0][i], files_[0][i-1]));
|
||||
}
|
||||
}
|
||||
prev_file = f;
|
||||
#endif
|
||||
*status = table_cache_->Get(options, *internal_comparator_, f->fd, ikey,
|
||||
&saver, SaveValue, MarkKeyMayExist);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
switch (saver.state) {
|
||||
case kNotFound:
|
||||
break; // Keep searching in other files
|
||||
case kFound:
|
||||
return;
|
||||
}
|
||||
|
||||
switch (saver.state) {
|
||||
case kNotFound:
|
||||
break; // Keep searching in other files
|
||||
case kFound:
|
||||
return;
|
||||
case kDeleted:
|
||||
*status = Status::NotFound(); // Use empty error message for speed
|
||||
return;
|
||||
case kCorrupt:
|
||||
*status = Status::Corruption("corrupted key for ", user_key);
|
||||
return;
|
||||
case kMerge:
|
||||
break;
|
||||
}
|
||||
if (level > 0 && cmp_largest < 0) {
|
||||
case kDeleted:
|
||||
*status = Status::NotFound(); // Use empty error message for speed
|
||||
return;
|
||||
case kCorrupt:
|
||||
*status = Status::Corruption("corrupted key for ", user_key);
|
||||
return;
|
||||
case kMerge:
|
||||
break;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
f = fp.GetNextFile();
|
||||
}
|
||||
|
||||
|
||||
if (kMerge == saver.state) {
|
||||
// merge_operands are in saver and we hit the beginning of the key history
|
||||
// do a final merge of nullptr and operands;
|
||||
@@ -767,29 +864,7 @@ void Version::Get(const ReadOptions& options,
|
||||
void Version::GenerateFileLevels() {
|
||||
file_levels_.resize(num_non_empty_levels_);
|
||||
for (int level = 0; level < num_non_empty_levels_; level++) {
|
||||
const auto& files = files_[level];
|
||||
auto& file_level = file_levels_[level];
|
||||
|
||||
size_t num = files.size();
|
||||
file_level.num_files = num;
|
||||
char* mem = arena_.AllocateAligned(num * sizeof(FdWithKeyRange));
|
||||
file_level.files = new (mem)FdWithKeyRange[num];
|
||||
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
Slice smallest_key = files[i]->smallest.Encode();
|
||||
Slice largest_key = files[i]->largest.Encode();
|
||||
|
||||
// Copy key slice to sequential memory
|
||||
size_t smallest_size = smallest_key.size();
|
||||
size_t largest_size = largest_key.size();
|
||||
mem = arena_.AllocateAligned(smallest_size + largest_size);
|
||||
memcpy(mem, smallest_key.data(), smallest_size);
|
||||
memcpy(mem + smallest_size, largest_key.data(), largest_size);
|
||||
|
||||
file_level.files[i].fd = files[i]->fd;
|
||||
file_level.files[i].smallest_key = Slice(mem, smallest_size);
|
||||
file_level.files[i].largest_key = Slice(mem+smallest_size, largest_size);
|
||||
}
|
||||
DoGenerateFileLevel(&file_levels_[level], files_[level], &arena_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,6 +873,7 @@ void Version::PrepareApply(std::vector<uint64_t>& size_being_compacted) {
|
||||
ComputeCompactionScore(size_being_compacted);
|
||||
UpdateFilesBySize();
|
||||
UpdateNumNonEmptyLevels();
|
||||
file_indexer_.UpdateIndex(&arena_, num_non_empty_levels_, files_);
|
||||
GenerateFileLevels();
|
||||
}
|
||||
|
||||
@@ -835,6 +911,7 @@ void Version::UpdateTemporaryStats() {
|
||||
total_raw_value_size_ += file_meta->raw_value_size;
|
||||
num_non_deletions_ +=
|
||||
file_meta->num_entries - file_meta->num_deletions;
|
||||
num_deletions_ += file_meta->num_deletions;
|
||||
init_count++;
|
||||
}
|
||||
total_count++;
|
||||
@@ -862,13 +939,10 @@ void Version::ComputeCompactionScore(
|
||||
double max_score = 0;
|
||||
int max_score_level = 0;
|
||||
|
||||
int num_levels_to_check =
|
||||
(cfd_->options()->compaction_style != kCompactionStyleUniversal &&
|
||||
cfd_->options()->compaction_style != kCompactionStyleFIFO)
|
||||
? NumberLevels() - 1
|
||||
: 1;
|
||||
int max_input_level =
|
||||
cfd_->compaction_picker()->MaxInputLevel(NumberLevels());
|
||||
|
||||
for (int level = 0; level < num_levels_to_check; level++) {
|
||||
for (int level = 0; level <= max_input_level; level++) {
|
||||
double score;
|
||||
if (level == 0) {
|
||||
// We treat level-0 specially by bounding the number of files
|
||||
@@ -1017,12 +1091,10 @@ bool Version::NeedsCompaction() const {
|
||||
// ending up with nothing to do. We can improve it later.
|
||||
// TODO(sdong): improve this function to be accurate for universal
|
||||
// compactions.
|
||||
int num_levels_to_check =
|
||||
(cfd_->options()->compaction_style != kCompactionStyleUniversal &&
|
||||
cfd_->options()->compaction_style != kCompactionStyleFIFO)
|
||||
? NumberLevels() - 1
|
||||
: 1;
|
||||
for (int i = 0; i < num_levels_to_check; i++) {
|
||||
int max_input_level =
|
||||
cfd_->compaction_picker()->MaxInputLevel(NumberLevels());
|
||||
|
||||
for (int i = 0; i <= max_input_level; i++) {
|
||||
if (compaction_score_[i] >= 1) {
|
||||
return true;
|
||||
}
|
||||
@@ -1181,7 +1253,7 @@ void Version::GetOverlappingInputsBinarySearch(
|
||||
// The midIndex specifies the index of at least one file that
|
||||
// overlaps the specified range. From that file, iterate backward
|
||||
// and forward to find all overlapping files.
|
||||
// Use compressed file meda data, make search faster
|
||||
// Use FileLevel in searching, make it faster
|
||||
void Version::ExtendOverlappingInputs(
|
||||
int level,
|
||||
const Slice& user_begin,
|
||||
@@ -1615,8 +1687,6 @@ class VersionSet::Builder {
|
||||
}
|
||||
|
||||
CheckConsistency(v);
|
||||
|
||||
v->file_indexer_.UpdateIndex(v->files_);
|
||||
}
|
||||
|
||||
void LoadTableHandlers() {
|
||||
@@ -2764,16 +2834,19 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
// Level-0 files have to be merged together. For other levels,
|
||||
// we will make a concatenating iterator per level.
|
||||
// TODO(opt): use concatenating iterator for level-0 if there is no overlap
|
||||
const int space = (c->level() == 0 ? c->inputs(0)->size() + 1 : 2);
|
||||
const int space = (c->level() == 0 ?
|
||||
c->input_levels(0)->num_files + c->num_input_levels() - 1:
|
||||
c->num_input_levels());
|
||||
Iterator** list = new Iterator*[space];
|
||||
int num = 0;
|
||||
for (int which = 0; which < 2; which++) {
|
||||
if (!c->inputs(which)->empty()) {
|
||||
if (c->level() + which == 0) {
|
||||
for (const auto& file : *c->inputs(which)) {
|
||||
for (int which = 0; which < c->num_input_levels(); which++) {
|
||||
if (c->input_levels(which)->num_files != 0) {
|
||||
if (c->level(which) == 0) {
|
||||
const FileLevel* flevel = c->input_levels(which);
|
||||
for (size_t i = 0; i < flevel->num_files; i++) {
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, storage_options_compactions_,
|
||||
cfd->internal_comparator(), file->fd, nullptr,
|
||||
cfd->internal_comparator(), flevel->files[i].fd, nullptr,
|
||||
true /* for compaction */);
|
||||
}
|
||||
} else {
|
||||
@@ -2783,7 +2856,7 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
cfd->internal_comparator(), true /* for_compaction */,
|
||||
false /* prefix enabled */),
|
||||
new Version::LevelFileNumIterator(cfd->internal_comparator(),
|
||||
c->inputs(which)));
|
||||
c->input_levels(which)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2872,10 +2945,10 @@ void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
filemetadata.column_family_name = cfd->GetName();
|
||||
uint32_t path_id = file->fd.GetPathId();
|
||||
if (path_id < options_->db_paths.size()) {
|
||||
filemetadata.db_path = options_->db_paths[path_id];
|
||||
filemetadata.db_path = options_->db_paths[path_id].path;
|
||||
} else {
|
||||
assert(!options_->db_paths.empty());
|
||||
filemetadata.db_path = options_->db_paths.back();
|
||||
filemetadata.db_path = options_->db_paths.back().path;
|
||||
}
|
||||
filemetadata.name = MakeTableFileName("", file->fd.GetNumber());
|
||||
filemetadata.level = level;
|
||||
|
||||
+13
-11
@@ -53,14 +53,6 @@ class ColumnFamilySet;
|
||||
class TableCache;
|
||||
class MergeIteratorBuilder;
|
||||
|
||||
|
||||
// Return the smallest index i such that files[i]->largest >= key.
|
||||
// Return files.size() if there is no such file.
|
||||
// REQUIRES: "files" contains a sorted list of non-overlapping files.
|
||||
extern int FindFile(const InternalKeyComparator& icmp,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const Slice& key);
|
||||
|
||||
// Return the smallest index i such that file_level.files[i]->largest >= key.
|
||||
// Return file_level.num_files if there is no such file.
|
||||
// REQUIRES: "file_level.files" contains a sorted list of
|
||||
@@ -82,6 +74,13 @@ extern bool SomeFileOverlapsRange(
|
||||
const Slice* smallest_user_key,
|
||||
const Slice* largest_user_key);
|
||||
|
||||
// Generate FileLevel from vector<FdWithKeyRange*>
|
||||
// Would copy smallest_key and largest_key data to sequential memory
|
||||
// arena: Arena used to allocate the memory
|
||||
extern void DoGenerateFileLevel(FileLevel* file_level,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
Arena* arena);
|
||||
|
||||
class Version {
|
||||
public:
|
||||
// Append to *iters a sequence of iterators that will
|
||||
@@ -233,6 +232,8 @@ class Version {
|
||||
// tables' propertis, represented as shared_ptr.
|
||||
Status GetPropertiesOfAllTables(TablePropertiesCollection* props);
|
||||
|
||||
uint64_t GetEstimatedActiveKeys();
|
||||
|
||||
// used to sort files by size
|
||||
struct Fsize {
|
||||
int index;
|
||||
@@ -249,6 +250,7 @@ class Version {
|
||||
friend class UniversalCompactionPicker;
|
||||
friend class FIFOCompactionPicker;
|
||||
friend class ForwardIterator;
|
||||
friend class InternalStats;
|
||||
|
||||
class LevelFileNumIterator;
|
||||
class LevelFileIteratorState;
|
||||
@@ -284,6 +286,7 @@ class Version {
|
||||
int num_levels_; // Number of levels
|
||||
int num_non_empty_levels_; // Number of levels. Any level larger than it
|
||||
// is guaranteed to be empty.
|
||||
FileIndexer file_indexer_;
|
||||
VersionSet* vset_; // VersionSet to which this Version belongs
|
||||
Arena arena_; // Used to allocate space for file_levels_
|
||||
Version* next_; // Next version in linked list
|
||||
@@ -294,7 +297,6 @@ class Version {
|
||||
// in increasing order of keys
|
||||
std::vector<FileMetaData*>* files_;
|
||||
|
||||
|
||||
// A list for the same set of files that are stored in files_,
|
||||
// but files in each level are now sorted based on file
|
||||
// size. The file with the largest size is at the front.
|
||||
@@ -327,7 +329,6 @@ class Version {
|
||||
uint64_t version_number_;
|
||||
|
||||
Version(ColumnFamilyData* cfd, VersionSet* vset, uint64_t version_number = 0);
|
||||
FileIndexer file_indexer_;
|
||||
|
||||
// total file size
|
||||
uint64_t total_file_size_;
|
||||
@@ -337,7 +338,8 @@ class Version {
|
||||
uint64_t total_raw_value_size_;
|
||||
// total number of non-deletion entries
|
||||
uint64_t num_non_deletions_;
|
||||
|
||||
// total number of deletion entries
|
||||
uint64_t num_deletions_;
|
||||
|
||||
~Version();
|
||||
|
||||
|
||||
+34
-44
@@ -14,14 +14,15 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class FindFileTest {
|
||||
class GenerateFileLevelTest {
|
||||
public:
|
||||
std::vector<FileMetaData*> files_;
|
||||
bool disjoint_sorted_files_;
|
||||
FileLevel file_level_;
|
||||
Arena arena_;
|
||||
|
||||
FindFileTest() : disjoint_sorted_files_(true) { }
|
||||
GenerateFileLevelTest() { }
|
||||
|
||||
~FindFileTest() {
|
||||
~GenerateFileLevelTest() {
|
||||
for (unsigned int i = 0; i < files_.size(); i++) {
|
||||
delete files_[i];
|
||||
}
|
||||
@@ -37,55 +38,44 @@ class FindFileTest {
|
||||
files_.push_back(f);
|
||||
}
|
||||
|
||||
int Find(const char* key) {
|
||||
InternalKey target(key, 100, kTypeValue);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
return FindFile(cmp, files_, target.Encode());
|
||||
int Compare() {
|
||||
int diff = 0;
|
||||
for (size_t i = 0; i < files_.size(); i++) {
|
||||
if (file_level_.files[i].fd.GetNumber() != files_[i]->fd.GetNumber()) {
|
||||
diff++;
|
||||
}
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(FindFileTest, Empty) {
|
||||
ASSERT_EQ(0, Find("foo"));
|
||||
TEST(GenerateFileLevelTest, Empty) {
|
||||
DoGenerateFileLevel(&file_level_, files_, &arena_);
|
||||
ASSERT_EQ(0u, file_level_.num_files);
|
||||
ASSERT_EQ(0, Compare());
|
||||
}
|
||||
|
||||
TEST(FindFileTest, Single) {
|
||||
TEST(GenerateFileLevelTest, Single) {
|
||||
Add("p", "q");
|
||||
ASSERT_EQ(0, Find("a"));
|
||||
ASSERT_EQ(0, Find("p"));
|
||||
ASSERT_EQ(0, Find("p1"));
|
||||
ASSERT_EQ(0, Find("q"));
|
||||
ASSERT_EQ(1, Find("q1"));
|
||||
ASSERT_EQ(1, Find("z"));
|
||||
DoGenerateFileLevel(&file_level_, files_, &arena_);
|
||||
ASSERT_EQ(1u, file_level_.num_files);
|
||||
ASSERT_EQ(0, Compare());
|
||||
}
|
||||
|
||||
|
||||
TEST(FindFileTest, Multiple) {
|
||||
TEST(GenerateFileLevelTest, Multiple) {
|
||||
Add("150", "200");
|
||||
Add("200", "250");
|
||||
Add("300", "350");
|
||||
Add("400", "450");
|
||||
ASSERT_EQ(0, Find("100"));
|
||||
ASSERT_EQ(0, Find("150"));
|
||||
ASSERT_EQ(0, Find("151"));
|
||||
ASSERT_EQ(0, Find("199"));
|
||||
ASSERT_EQ(0, Find("200"));
|
||||
ASSERT_EQ(1, Find("201"));
|
||||
ASSERT_EQ(1, Find("249"));
|
||||
ASSERT_EQ(1, Find("250"));
|
||||
ASSERT_EQ(2, Find("251"));
|
||||
ASSERT_EQ(2, Find("299"));
|
||||
ASSERT_EQ(2, Find("300"));
|
||||
ASSERT_EQ(2, Find("349"));
|
||||
ASSERT_EQ(2, Find("350"));
|
||||
ASSERT_EQ(3, Find("351"));
|
||||
ASSERT_EQ(3, Find("400"));
|
||||
ASSERT_EQ(3, Find("450"));
|
||||
ASSERT_EQ(4, Find("451"));
|
||||
DoGenerateFileLevel(&file_level_, files_, &arena_);
|
||||
ASSERT_EQ(4u, file_level_.num_files);
|
||||
ASSERT_EQ(0, Compare());
|
||||
}
|
||||
|
||||
class FindLevelFileTest {
|
||||
public:
|
||||
FileLevel level_files_;
|
||||
FileLevel file_level_;
|
||||
bool disjoint_sorted_files_;
|
||||
Arena arena_;
|
||||
|
||||
@@ -96,8 +86,8 @@ class FindLevelFileTest {
|
||||
|
||||
void LevelFileInit(size_t num = 0) {
|
||||
char* mem = arena_.AllocateAligned(num * sizeof(FdWithKeyRange));
|
||||
level_files_.files = new (mem)FdWithKeyRange[num];
|
||||
level_files_.num_files = 0;
|
||||
file_level_.files = new (mem)FdWithKeyRange[num];
|
||||
file_level_.num_files = 0;
|
||||
}
|
||||
|
||||
void Add(const char* smallest, const char* largest,
|
||||
@@ -115,27 +105,27 @@ class FindLevelFileTest {
|
||||
memcpy(mem + smallest_slice.size(), largest_slice.data(),
|
||||
largest_slice.size());
|
||||
|
||||
// add compressd_level_
|
||||
size_t num = level_files_.num_files;
|
||||
auto& file = level_files_.files[num];
|
||||
// add to file_level_
|
||||
size_t num = file_level_.num_files;
|
||||
auto& file = file_level_.files[num];
|
||||
file.fd = FileDescriptor(num + 1, 0, 0);
|
||||
file.smallest_key = Slice(mem, smallest_slice.size());
|
||||
file.largest_key = Slice(mem + smallest_slice.size(),
|
||||
largest_slice.size());
|
||||
level_files_.num_files++;
|
||||
file_level_.num_files++;
|
||||
}
|
||||
|
||||
int Find(const char* key) {
|
||||
InternalKey target(key, 100, kTypeValue);
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
return FindFile(cmp, level_files_, target.Encode());
|
||||
return FindFile(cmp, file_level_, target.Encode());
|
||||
}
|
||||
|
||||
bool Overlaps(const char* smallest, const char* largest) {
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
Slice s(smallest != nullptr ? smallest : "");
|
||||
Slice l(largest != nullptr ? largest : "");
|
||||
return SomeFileOverlapsRange(cmp, disjoint_sorted_files_, level_files_,
|
||||
return SomeFileOverlapsRange(cmp, disjoint_sorted_files_, file_level_,
|
||||
(smallest != nullptr ? &s : nullptr),
|
||||
(largest != nullptr ? &l : nullptr));
|
||||
}
|
||||
|
||||
+2
-2
@@ -436,8 +436,8 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
// Failed to merge!
|
||||
RecordTick(options->statistics.get(), NUMBER_MERGE_FAILURES);
|
||||
|
||||
// Store the delta in memtable
|
||||
perform_merge = false;
|
||||
// Store the delta in memtable
|
||||
perform_merge = false;
|
||||
} else {
|
||||
// 3) Add value to memtable
|
||||
mem->Add(sequence_, kTypeValue, key, new_value);
|
||||
|
||||
+16
-5
@@ -160,6 +160,7 @@ class DB {
|
||||
virtual Status DropColumnFamily(ColumnFamilyHandle* column_family);
|
||||
|
||||
// Set the database entry for "key" to "value".
|
||||
// If "key" already exists, it will be overwritten.
|
||||
// Returns OK on success, and a non-OK status on error.
|
||||
// Note: consider setting options.sync = true.
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
@@ -306,6 +307,14 @@ class DB {
|
||||
return GetProperty(DefaultColumnFamily(), property, value);
|
||||
}
|
||||
|
||||
// Similar to GetProperty(), but only works for a subset of properties whose
|
||||
// return value is an integer. Return the value by integer.
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) = 0;
|
||||
virtual bool GetIntProperty(const Slice& property, uint64_t* value) {
|
||||
return GetIntProperty(DefaultColumnFamily(), property, value);
|
||||
}
|
||||
|
||||
// For each i in [0,n-1], store in "sizes[i]", the approximate
|
||||
// file system space used by keys in "[range[i].start .. range[i].limit)".
|
||||
//
|
||||
@@ -338,15 +347,17 @@ class DB {
|
||||
// hosting all the files. In this case, client could set reduce_level
|
||||
// to true, to move the files back to the minimum level capable of holding
|
||||
// the data set or a given level (specified by non-negative target_level).
|
||||
// Compaction outputs should be placed in options.db_paths[target_path_id].
|
||||
// Behavior is undefined if target_path_id is out of range.
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false,
|
||||
int target_level = -1) = 0;
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) = 0;
|
||||
virtual Status CompactRange(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) {
|
||||
return CompactRange(DefaultColumnFamily(), begin, end, reduce_level,
|
||||
target_level);
|
||||
target_level, target_path_id);
|
||||
}
|
||||
|
||||
// Number of levels used for this DB.
|
||||
|
||||
+41
-23
@@ -39,7 +39,6 @@ class Slice;
|
||||
class SliceTransform;
|
||||
class Statistics;
|
||||
class InternalKeyComparator;
|
||||
class RateLimiter;
|
||||
|
||||
// DB contents are stored in a set of blocks, each of which holds a
|
||||
// sequence of key,value pairs. Each block may be compressed before
|
||||
@@ -83,6 +82,14 @@ enum UpdateStatus { // Return status For inplace update callback
|
||||
UPDATED = 2, // No inplace update. Merged value set
|
||||
};
|
||||
|
||||
struct DbPath {
|
||||
std::string path;
|
||||
uint64_t target_size; // Target size of total files under the path, in byte.
|
||||
|
||||
DbPath() : target_size(0) {}
|
||||
DbPath(const std::string& p, uint64_t t) : path(p), target_size(t) {}
|
||||
};
|
||||
|
||||
struct Options;
|
||||
|
||||
struct ColumnFamilyOptions {
|
||||
@@ -674,18 +681,34 @@ struct DBOptions {
|
||||
// Default: false
|
||||
bool use_fsync;
|
||||
|
||||
// This number controls how often a new scribe log about
|
||||
// db deploy stats is written out.
|
||||
// -1 indicates no logging at all.
|
||||
// Default value is 1800 (half an hour).
|
||||
// This options is not used!!
|
||||
int db_stats_log_interval;
|
||||
|
||||
// A list paths where SST files can be put into. A compaction style can
|
||||
// determine which of those paths it will put the file to.
|
||||
// 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.
|
||||
//
|
||||
// For example, you have a flash device with 10GB allocated for the DB,
|
||||
// as well as a hard drive of 2TB, you should config it to be:
|
||||
// [{"/flash_path", 10GB}, {"/hard_drive", 2TB}]
|
||||
//
|
||||
// The system will try to guarantee data under each path is close to but
|
||||
// not larger than the target size. But current and future file sizes used
|
||||
// by determining where to place a file are based on best-effort estimation,
|
||||
// which means there is a chance that the actual size under the directory
|
||||
// is slightly more than target size under some workloads. User should give
|
||||
// some buffer room for those cases.
|
||||
//
|
||||
// If none of the paths has sufficient room to place a file, the file will
|
||||
// be placed to the last path anyway, despite to the target size.
|
||||
//
|
||||
// Placing newer data to ealier paths is also best-efforts. User should
|
||||
// expect user files to be placed in higher levels in some extreme cases.
|
||||
//
|
||||
// If left empty, only one path will be used, which is db_name passed when
|
||||
// opening the DB.
|
||||
// Default: empty
|
||||
std::vector<std::string> db_paths;
|
||||
std::vector<DbPath> db_paths;
|
||||
|
||||
// This specifies the info LOG dir.
|
||||
// If it is empty, the log files will be in the same dir as data.
|
||||
@@ -1003,21 +1026,16 @@ struct FlushOptions {
|
||||
FlushOptions() : wait(true) {}
|
||||
};
|
||||
|
||||
|
||||
// Create a RateLimiter object, which can be shared among RocksDB instances to
|
||||
// control write rate of flush and compaction.
|
||||
// @rate_bytes_per_sec: desired total write rate in bytes per second.
|
||||
// @refill_period_us: token refill interval in micro-second.
|
||||
// @fairness: RateLimiter accepts high-pri requests and low-pri requests.
|
||||
// low-pri request is usually blocked in favor of hi-pri request. To prevent
|
||||
// low-pri request from being blocked for too long, it can get processed first
|
||||
// by 1/fairness chance.
|
||||
extern RateLimiter* NewRateLimiter(
|
||||
int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us = 100 * 1000,
|
||||
int32_t fairness = 10);
|
||||
|
||||
|
||||
// Get options based on some guidelines. Now only tune parameter based on
|
||||
// flush/compaction and fill default parameters for other parameters.
|
||||
// total_write_buffer_limit: budget for memory spent for mem tables
|
||||
// read_amplification_threshold: comfortable value of read amplification
|
||||
// write_amplification_threshold: comfortable value of write amplification.
|
||||
// target_db_size: estimated total DB size.
|
||||
extern Options GetOptions(size_t total_write_buffer_limit,
|
||||
int read_amplification_threshold = 8,
|
||||
int write_amplification_threshold = 32,
|
||||
uint64_t target_db_size = 68719476736 /* 64GB */);
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // STORAGE_ROCKSDB_INCLUDE_OPTIONS_H_
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class RateLimiter {
|
||||
public:
|
||||
virtual ~RateLimiter() {}
|
||||
|
||||
// Request for token to write bytes. If this request can not be satisfied,
|
||||
// the call is blocked. Caller is responsible to make sure
|
||||
// bytes < GetSingleBurstBytes()
|
||||
virtual void Request(const int64_t bytes, const Env::IOPriority pri) = 0;
|
||||
|
||||
// Max bytes can be granted in a single burst
|
||||
virtual int64_t GetSingleBurstBytes() const = 0;
|
||||
|
||||
// Total bytes that go though rate limiter
|
||||
virtual int64_t GetTotalBytesThrough(
|
||||
const Env::IOPriority pri = Env::IO_TOTAL) const = 0;
|
||||
|
||||
// Total # of requests that go though rate limiter
|
||||
virtual int64_t GetTotalRequests(
|
||||
const Env::IOPriority pri = Env::IO_TOTAL) const = 0;
|
||||
};
|
||||
|
||||
// Create a RateLimiter object, which can be shared among RocksDB instances to
|
||||
// control write rate of flush and compaction.
|
||||
// @rate_bytes_per_sec: this is the only parameter you want to set most of the
|
||||
// time. It controls the total write rate of compaction and flush in bytes per
|
||||
// second. Currently, RocksDB does not enforce rate limit for anything other
|
||||
// than flush and compaction, e.g. write to WAL.
|
||||
// @refill_period_us: this controls how often tokens are refilled. For example,
|
||||
// when rate_bytes_per_sec is set to 10MB/s and refill_period_us is set to
|
||||
// 100ms, then 1MB is refilled every 100ms internally. Larger value can lead to
|
||||
// burstier writes while smaller value introduces more CPU overhead.
|
||||
// The default should work for most cases.
|
||||
// @fairness: RateLimiter accepts high-pri requests and low-pri requests.
|
||||
// A low-pri request is usually blocked in favor of hi-pri request. Currently,
|
||||
// RocksDB assigns low-pri to request from compaciton and high-pri to request
|
||||
// from flush. Low-pri requests can get blocked if flush requests come in
|
||||
// continuouly. This fairness parameter grants low-pri requests permission by
|
||||
// 1/fairness chance even though high-pri requests exist to avoid starvation.
|
||||
// You should be good by leaving it at default 10.
|
||||
extern RateLimiter* NewGenericRateLimiter(
|
||||
int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us = 100 * 1000,
|
||||
int32_t fairness = 10);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -20,12 +20,12 @@ namespace rocksdb {
|
||||
* 1. Any ticker should be added before TICKER_ENUM_MAX.
|
||||
* 2. Add a readable string in TickersNameMap below for the newly added ticker.
|
||||
*/
|
||||
enum Tickers {
|
||||
enum Tickers : uint32_t {
|
||||
// total block cache misses
|
||||
// REQUIRES: BLOCK_CACHE_MISS == BLOCK_CACHE_INDEX_MISS +
|
||||
// BLOCK_CACHE_FILTER_MISS +
|
||||
// BLOCK_CACHE_DATA_MISS;
|
||||
BLOCK_CACHE_MISS,
|
||||
BLOCK_CACHE_MISS = 0,
|
||||
// total block cache hit
|
||||
// REQUIRES: BLOCK_CACHE_HIT == BLOCK_CACHE_INDEX_HIT +
|
||||
// BLOCK_CACHE_FILTER_HIT +
|
||||
@@ -198,8 +198,8 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
* Add a string representation in HistogramsNameMap below
|
||||
* And increment HISTOGRAM_ENUM_MAX
|
||||
*/
|
||||
enum Histograms {
|
||||
DB_GET,
|
||||
enum Histograms : uint32_t {
|
||||
DB_GET = 0,
|
||||
DB_WRITE,
|
||||
COMPACTION_TIME,
|
||||
TABLE_SYNC_MICROS,
|
||||
@@ -256,14 +256,21 @@ class Statistics {
|
||||
public:
|
||||
virtual ~Statistics() {}
|
||||
|
||||
virtual long getTickerCount(Tickers tickerType) = 0;
|
||||
virtual void recordTick(Tickers tickerType, uint64_t count = 0) = 0;
|
||||
virtual void setTickerCount(Tickers tickerType, uint64_t count) = 0;
|
||||
virtual void measureTime(Histograms histogramType, uint64_t time) = 0;
|
||||
virtual uint64_t getTickerCount(uint32_t tickerType) const = 0;
|
||||
virtual void histogramData(uint32_t type,
|
||||
HistogramData* const data) const = 0;
|
||||
|
||||
virtual void recordTick(uint32_t tickerType, uint64_t count = 0) = 0;
|
||||
virtual void setTickerCount(uint32_t tickerType, uint64_t count) = 0;
|
||||
virtual void measureTime(uint32_t histogramType, uint64_t time) = 0;
|
||||
|
||||
virtual void histogramData(Histograms type, HistogramData* const data) = 0;
|
||||
// String representation of the statistic object.
|
||||
std::string ToString();
|
||||
virtual std::string ToString() const = 0;
|
||||
|
||||
// Override this function to disable particular histogram collection
|
||||
virtual bool HistEnabledForType(uint32_t type) const {
|
||||
return type < HISTOGRAM_ENUM_MAX;
|
||||
}
|
||||
};
|
||||
|
||||
// Create a concrete DBStatistics object
|
||||
|
||||
+42
-11
@@ -119,33 +119,43 @@ enum EncodingType : char {
|
||||
struct PlainTablePropertyNames {
|
||||
static const std::string kPrefixExtractorName;
|
||||
static const std::string kEncodingType;
|
||||
static const std::string kBloomVersion;
|
||||
static const std::string kNumBloomBlocks;
|
||||
};
|
||||
|
||||
// -- Plain Table with prefix-only seek
|
||||
// For this factory, you need to set Options.prefix_extrator properly to make it
|
||||
// work. Look-up will starts with prefix hash lookup for key prefix. Inside the
|
||||
// hash bucket found, a binary search is executed for hash conflicts. Finally,
|
||||
// a linear search is used.
|
||||
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;
|
||||
|
||||
// @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;
|
||||
|
||||
// @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;
|
||||
|
||||
// @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
|
||||
@@ -153,13 +163,34 @@ struct PlainTablePropertyNames {
|
||||
// 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;
|
||||
|
||||
const uint32_t kPlainTableVariableLength = 0;
|
||||
extern TableFactory* NewPlainTableFactory(
|
||||
uint32_t user_key_len = kPlainTableVariableLength,
|
||||
int bloom_bits_per_prefix = 10, double hash_table_ratio = 0.75,
|
||||
size_t index_sparseness = 16, size_t huge_page_tlb_size = 0,
|
||||
EncodingType encoding_type = kPlain, bool full_scan_mode = false);
|
||||
// @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
|
||||
// file building and store it in file. When reading
|
||||
// file, index will be mmaped instead of recomputation.
|
||||
bool store_index_in_file = false;
|
||||
};
|
||||
|
||||
// -- Plain Table with prefix-only seek
|
||||
// For this factory, you need to set Options.prefix_extrator properly to make it
|
||||
// work. Look-up will starts with prefix hash lookup for key prefix. Inside the
|
||||
// hash bucket found, a binary search is executed for hash conflicts. Finally,
|
||||
// a linear search is used.
|
||||
|
||||
extern TableFactory* NewPlainTableFactory(const PlainTableOptions& options =
|
||||
PlainTableOptions());
|
||||
|
||||
struct CuckooTablePropertyNames {
|
||||
static const std::string kEmptyKey;
|
||||
static const std::string kValueLength;
|
||||
static const std::string kNumHashTable;
|
||||
static const std::string kMaxNumBuckets;
|
||||
static const std::string kIsLastLevel;
|
||||
};
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct BackupableDBOptions {
|
||||
// Where to keep the backup files. Has to be different than dbname_
|
||||
// Best to set this to dbname_ + "/backups"
|
||||
// Required
|
||||
std::string backup_dir;
|
||||
|
||||
// Backup Env object. It will be used for backup file I/O. If it's
|
||||
// nullptr, backups will be written out using DBs Env. If it's
|
||||
// non-nullptr, backup's I/O will be performed using this object.
|
||||
// If you want to have backups on HDFS, use HDFS Env here!
|
||||
// Default: nullptr
|
||||
Env* backup_env;
|
||||
|
||||
// If share_table_files == true, backup will assume that table files with
|
||||
// same name have the same contents. This enables incremental backups and
|
||||
// avoids unnecessary data copies.
|
||||
// If share_table_files == false, each backup will be on its own and will
|
||||
// not share any data with other backups.
|
||||
// default: true
|
||||
bool share_table_files;
|
||||
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log;
|
||||
|
||||
// If sync == true, we can guarantee you'll get consistent backup even
|
||||
// on a machine crash/reboot. Backup process is slower with sync enabled.
|
||||
// If sync == false, we don't guarantee anything on machine reboot. However,
|
||||
// chances are some of the backups are consistent.
|
||||
// Default: true
|
||||
bool sync;
|
||||
|
||||
// If true, it will delete whatever backups there are already
|
||||
// Default: false
|
||||
bool destroy_old_data;
|
||||
|
||||
// If false, we won't backup log files. This option can be useful for backing
|
||||
// up in-memory databases where log file are persisted, but table files are in
|
||||
// memory.
|
||||
// Default: true
|
||||
bool backup_log_files;
|
||||
|
||||
// Max bytes that can be transferred in a second during backup.
|
||||
// If 0, go as fast as you can
|
||||
// Default: 0
|
||||
uint64_t backup_rate_limit;
|
||||
|
||||
// Max bytes that can be transferred in a second during restore.
|
||||
// If 0, go as fast as you can
|
||||
// Default: 0
|
||||
uint64_t restore_rate_limit;
|
||||
|
||||
// Only used if share_table_files is set to true. If true, will consider that
|
||||
// backups can come from different databases, hence a sst is not uniquely
|
||||
// identifed by its name, but by the triple (file name, crc32, file length)
|
||||
// Default: false
|
||||
// Note: this is an experimental option, and you'll need to set it manually
|
||||
// *turn it on only if you know what you're doing*
|
||||
bool share_files_with_checksum;
|
||||
|
||||
void Dump(Logger* logger) const;
|
||||
|
||||
explicit BackupableDBOptions(const std::string& _backup_dir,
|
||||
Env* _backup_env = nullptr,
|
||||
bool _share_table_files = true,
|
||||
Logger* _info_log = nullptr, bool _sync = true,
|
||||
bool _destroy_old_data = false,
|
||||
bool _backup_log_files = true,
|
||||
uint64_t _backup_rate_limit = 0,
|
||||
uint64_t _restore_rate_limit = 0)
|
||||
: backup_dir(_backup_dir),
|
||||
backup_env(_backup_env),
|
||||
share_table_files(_share_table_files),
|
||||
info_log(_info_log),
|
||||
sync(_sync),
|
||||
destroy_old_data(_destroy_old_data),
|
||||
backup_log_files(_backup_log_files),
|
||||
backup_rate_limit(_backup_rate_limit),
|
||||
restore_rate_limit(_restore_rate_limit),
|
||||
share_files_with_checksum(false) {
|
||||
assert(share_table_files || !share_files_with_checksum);
|
||||
}
|
||||
};
|
||||
|
||||
struct RestoreOptions {
|
||||
// If true, restore won't overwrite the existing log files in wal_dir. It will
|
||||
// also move all log files from archive directory to wal_dir. Use this option
|
||||
// in combination with BackupableDBOptions::backup_log_files = false for
|
||||
// persisting in-memory databases.
|
||||
// Default: false
|
||||
bool keep_log_files;
|
||||
|
||||
explicit RestoreOptions(bool _keep_log_files = false)
|
||||
: keep_log_files(_keep_log_files) {}
|
||||
};
|
||||
|
||||
typedef uint32_t BackupID;
|
||||
|
||||
struct BackupInfo {
|
||||
BackupID backup_id;
|
||||
int64_t timestamp;
|
||||
uint64_t size;
|
||||
|
||||
BackupInfo() {}
|
||||
BackupInfo(BackupID _backup_id, int64_t _timestamp, uint64_t _size)
|
||||
: backup_id(_backup_id), timestamp(_timestamp), size(_size) {}
|
||||
};
|
||||
|
||||
class BackupEngineReadOnly {
|
||||
public:
|
||||
virtual ~BackupEngineReadOnly() {}
|
||||
|
||||
static BackupEngineReadOnly* NewReadOnlyBackupEngine(
|
||||
Env* db_env, const BackupableDBOptions& options);
|
||||
|
||||
// You can GetBackupInfo safely, even with other BackupEngine performing
|
||||
// backups on the same directory
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
|
||||
// Restoring DB from backup is NOT safe when there is another BackupEngine
|
||||
// running that might call DeleteBackup() or PurgeOldBackups(). It is caller's
|
||||
// responsibility to synchronize the operation, i.e. don't delete the backup
|
||||
// when you're restoring from it
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
};
|
||||
|
||||
// Please see the documentation in BackupableDB and RestoreBackupableDB
|
||||
class BackupEngine {
|
||||
public:
|
||||
virtual ~BackupEngine() {}
|
||||
|
||||
static BackupEngine* NewBackupEngine(Env* db_env,
|
||||
const BackupableDBOptions& options);
|
||||
|
||||
virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false) = 0;
|
||||
virtual Status PurgeOldBackups(uint32_t num_backups_to_keep) = 0;
|
||||
virtual Status DeleteBackup(BackupID backup_id) = 0;
|
||||
virtual void StopBackup() = 0;
|
||||
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
};
|
||||
|
||||
// Stack your DB with BackupableDB to be able to backup the DB
|
||||
class BackupableDB : public StackableDB {
|
||||
public:
|
||||
// BackupableDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// BackupableDB ownes the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of BackupableDB
|
||||
BackupableDB(DB* db, const BackupableDBOptions& options);
|
||||
virtual ~BackupableDB();
|
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
Status CreateNewBackup(bool flush_before_backup = false);
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
// Call this from another thread if you want to stop the backup
|
||||
// that is currently happening. It will return immediatelly, will
|
||||
// not wait for the backup to stop.
|
||||
// The backup will stop ASAP and the call to CreateNewBackup will
|
||||
// return Status::Incomplete(). It will not clean up after itself, but
|
||||
// the state will remain consistent. The state will be cleaned up
|
||||
// next time you create BackupableDB or RestoreBackupableDB.
|
||||
void StopBackup();
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
};
|
||||
|
||||
// Use this class to access information about backups and restore from them
|
||||
class RestoreBackupableDB {
|
||||
public:
|
||||
RestoreBackupableDB(Env* db_env, const BackupableDBOptions& options);
|
||||
~RestoreBackupableDB();
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
|
||||
// restore from backup with backup_id
|
||||
// IMPORTANT -- if options_.share_table_files == true and you restore DB
|
||||
// from some backup that is not the latest, and you start creating new
|
||||
// backups from the new DB, they will probably fail
|
||||
//
|
||||
// Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
|
||||
// If you add new data to the DB and try creating a new backup now, the
|
||||
// database will diverge from backups 4 and 5 and the new backup will fail.
|
||||
// If you want to create new backup, you will first have to delete backups 4
|
||||
// and 5.
|
||||
Status RestoreDBFromBackup(BackupID backup_id, const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
|
||||
// restore from the latest backup
|
||||
Status RestoreDBFromLatestBackup(const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Database with TTL support.
|
||||
//
|
||||
// USE-CASES:
|
||||
// This API should be used to open the db when key-values inserted are
|
||||
// meant to be removed from the db in a non-strict 'ttl' amount of time
|
||||
// Therefore, this guarantees that key-values inserted will remain in the
|
||||
// db for >= ttl amount of time and the db will make efforts to remove the
|
||||
// key-values as soon as possible after ttl seconds of their insertion.
|
||||
//
|
||||
// BEHAVIOUR:
|
||||
// TTL is accepted in seconds
|
||||
// (int32_t)Timestamp(creation) is suffixed to values in Put internally
|
||||
// Expired TTL values deleted in compaction only:(Timestamp+ttl<time_now)
|
||||
// Get/Iterator may return expired entries(compaction not run on them yet)
|
||||
// Different TTL may be used during different Opens
|
||||
// Example: Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2
|
||||
// Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t>=5
|
||||
// read_only=true opens in the usual read-only mode. Compactions will not be
|
||||
// triggered(neither manual nor automatic), so no expired entries removed
|
||||
//
|
||||
// CONSTRAINTS:
|
||||
// Not specifying/passing or non-positive TTL behaves like TTL = infinity
|
||||
//
|
||||
// !!!WARNING!!!:
|
||||
// Calling DB::Open directly to re-open a db created by this API will get
|
||||
// corrupt values(timestamp suffixed) and no ttl effect will be there
|
||||
// during the second Open, so use this API consistently to open the db
|
||||
// Be careful when passing ttl with a small positive value because the
|
||||
// whole database may be deleted in a small amount of time
|
||||
|
||||
class DBWithTTL : public StackableDB {
|
||||
public:
|
||||
virtual Status CreateColumnFamilyWithTtl(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle, int ttl) = 0;
|
||||
|
||||
static Status Open(const Options& options, const std::string& dbname,
|
||||
DBWithTTL** dbptr, int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
|
||||
static Status Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles,
|
||||
DBWithTTL** dbptr, std::vector<int32_t> ttls,
|
||||
bool read_only = false);
|
||||
|
||||
protected:
|
||||
explicit DBWithTTL(DB* db) : StackableDB(db) {}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
#include "rocksdb/utilities/json_document.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// IMPORTANT: DocumentDB is a work in progress. It is unstable and we might
|
||||
// change the API without warning. Talk to RocksDB team before using this in
|
||||
// production ;)
|
||||
|
||||
// DocumentDB is a layer on top of RocksDB that provides a very simple JSON API.
|
||||
// When creating a DB, you specify a list of indexes you want to keep on your
|
||||
// data. You can insert a JSON document to the DB, which is automatically
|
||||
// indexed. Every document added to the DB needs to have "_id" field which is
|
||||
// automatically indexed and is an unique primary key. All other indexes are
|
||||
// non-unique.
|
||||
|
||||
// NOTE: field names in the JSON are NOT allowed to start with '$' or
|
||||
// contain '.'. We don't currently enforce that rule, but will start behaving
|
||||
// badly.
|
||||
|
||||
// Cursor is what you get as a result of executing query. To get all
|
||||
// results from a query, call Next() on a Cursor while Valid() returns true
|
||||
class Cursor {
|
||||
public:
|
||||
Cursor() = default;
|
||||
virtual ~Cursor() {}
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
virtual void Next() = 0;
|
||||
// Lifecycle of the returned JSONDocument is until the next Next() call
|
||||
virtual const JSONDocument& document() const = 0;
|
||||
virtual Status status() const = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Cursor(const Cursor&);
|
||||
void operator=(const Cursor&);
|
||||
};
|
||||
|
||||
struct DocumentDBOptions {
|
||||
int background_threads = 4;
|
||||
uint64_t memtable_size = 128 * 1024 * 1024; // 128 MB
|
||||
uint64_t cache_size = 1 * 1024 * 1024 * 1024; // 1 GB
|
||||
};
|
||||
|
||||
// TODO(icanadi) Add `JSONDocument* info` parameter to all calls that can be
|
||||
// used by the caller to get more information about the call execution (number
|
||||
// of dropped records, number of updated records, etc.)
|
||||
class DocumentDB : public StackableDB {
|
||||
public:
|
||||
struct IndexDescriptor {
|
||||
// Currently, you can only define an index on a single field. To specify an
|
||||
// index on a field X, set index description to JSON "{X: 1}"
|
||||
// Currently the value needs to be 1, which means ascending.
|
||||
// In the future, we plan to also support indexes on multiple keys, where
|
||||
// you could mix ascending sorting (1) with descending sorting indexes (-1)
|
||||
JSONDocument* description;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// Open DocumentDB with specified indexes. The list of indexes has to be
|
||||
// complete, i.e. include all indexes present in the DB, except the primary
|
||||
// key index.
|
||||
// Otherwise, Open() will return an error
|
||||
static Status Open(const DocumentDBOptions& options, const std::string& name,
|
||||
const std::vector<IndexDescriptor>& indexes,
|
||||
DocumentDB** db, bool read_only = false);
|
||||
|
||||
explicit DocumentDB(DB* db) : StackableDB(db) {}
|
||||
|
||||
// Create a new index. It will stop all writes for the duration of the call.
|
||||
// All current documents in the DB are scanned and corresponding index entries
|
||||
// are created
|
||||
virtual Status CreateIndex(const WriteOptions& write_options,
|
||||
const IndexDescriptor& index) = 0;
|
||||
|
||||
// Drop an index. Client is responsible to make sure that index is not being
|
||||
// used by currently executing queries
|
||||
virtual Status DropIndex(const std::string& name) = 0;
|
||||
|
||||
// Insert a document to the DB. The document needs to have a primary key "_id"
|
||||
// which can either be a string or an integer. Otherwise the write will fail
|
||||
// with InvalidArgument.
|
||||
virtual Status Insert(const WriteOptions& options,
|
||||
const JSONDocument& document) = 0;
|
||||
|
||||
// Deletes all documents matching a filter atomically
|
||||
virtual Status Remove(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
const JSONDocument& query) = 0;
|
||||
|
||||
// Does this sequence of operations:
|
||||
// 1. Find all documents matching a filter
|
||||
// 2. For all documents, atomically:
|
||||
// 2.1. apply the update operators
|
||||
// 2.2. update the secondary indexes
|
||||
//
|
||||
// Currently only $set update operator is supported.
|
||||
// Syntax is: {$set: {key1: value1, key2: value2, etc...}}
|
||||
// This operator will change a document's key1 field to value1, key2 to
|
||||
// value2, etc. New values will be set even if a document didn't have an entry
|
||||
// for the specified key.
|
||||
//
|
||||
// You can not change a primary key of a document.
|
||||
//
|
||||
// Update example: Update({id: {$gt: 5}, $index: id}, {$set: {enabled: true}})
|
||||
virtual Status Update(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
const JSONDocument& filter,
|
||||
const JSONDocument& updates) = 0;
|
||||
|
||||
// query has to be an array in which every element is an operator. Currently
|
||||
// only $filter operator is supported. Syntax of $filter operator is:
|
||||
// {$filter: {key1: condition1, key2: condition2, etc.}} where conditions can
|
||||
// be either:
|
||||
// 1) a single value in which case the condition is equality condition, or
|
||||
// 2) a defined operators, like {$gt: 4}, which will match all documents that
|
||||
// have key greater than 4.
|
||||
//
|
||||
// Supported operators are:
|
||||
// 1) $gt -- greater than
|
||||
// 2) $gte -- greater than or equal
|
||||
// 3) $lt -- less than
|
||||
// 4) $lte -- less than or equal
|
||||
// If you want the filter to use an index, you need to specify it like this:
|
||||
// {$filter: {...(conditions)..., $index: index_name}}
|
||||
//
|
||||
// Example query:
|
||||
// * [{$filter: {name: John, age: {$gte: 18}, $index: age}}]
|
||||
// will return all Johns whose age is greater or equal to 18 and it will use
|
||||
// index "age" to satisfy the query.
|
||||
virtual Cursor* Query(const ReadOptions& read_options,
|
||||
const JSONDocument& query) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,105 @@
|
||||
// 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
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
//
|
||||
// Configurable options needed for setting up a Geo database
|
||||
//
|
||||
struct GeoDBOptions {
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log;
|
||||
|
||||
explicit GeoDBOptions(Logger* _info_log = nullptr):info_log(_info_log) { }
|
||||
};
|
||||
|
||||
//
|
||||
// A position in the earth's geoid
|
||||
//
|
||||
class GeoPosition {
|
||||
public:
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
explicit GeoPosition(double la = 0, double lo = 0) :
|
||||
latitude(la), longitude(lo) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Description of an object on the Geoid. It is located by a GPS location,
|
||||
// and is identified by the id. The value associated with this object is
|
||||
// an opaque string 'value'. Different objects identified by unique id's
|
||||
// can have the same gps-location associated with them.
|
||||
//
|
||||
class GeoObject {
|
||||
public:
|
||||
GeoPosition position;
|
||||
std::string id;
|
||||
std::string value;
|
||||
|
||||
GeoObject() {}
|
||||
|
||||
GeoObject(const GeoPosition& pos, const std::string& i,
|
||||
const std::string& val) :
|
||||
position(pos), id(i), value(val) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Stack your DB with GeoDB to be able to get geo-spatial support
|
||||
//
|
||||
class GeoDB : public StackableDB {
|
||||
public:
|
||||
// GeoDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// GeoDB owns the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of GeoDB
|
||||
// GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
virtual ~GeoDB() {}
|
||||
|
||||
// Insert a new object into the location database. The object is
|
||||
// uniquely identified by the id. If an object with the same id already
|
||||
// exists in the db, then the old one is overwritten by the new
|
||||
// object being inserted here.
|
||||
virtual Status Insert(const GeoObject& object) = 0;
|
||||
|
||||
// Retrieve the value of the object located at the specified GPS
|
||||
// location and is identified by the 'id'.
|
||||
virtual Status GetByPosition(const GeoPosition& pos,
|
||||
const Slice& id, std::string* value) = 0;
|
||||
|
||||
// Retrieve the value of the object identified by the 'id'. This method
|
||||
// could be potentially slower than GetByPosition
|
||||
virtual Status GetById(const Slice& id, GeoObject* object) = 0;
|
||||
|
||||
// Delete the specified object
|
||||
virtual Status Remove(const Slice& id) = 0;
|
||||
|
||||
// Returns a list of all items within a circular radius from the
|
||||
// specified gps location. If 'number_of_values' is specified,
|
||||
// then this call returns at most that many number of objects.
|
||||
// The radius is specified in 'meters'.
|
||||
virtual Status SearchRadial(const GeoPosition& pos,
|
||||
double radius,
|
||||
std::vector<GeoObject>* values,
|
||||
int number_of_values = INT_MAX) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,174 @@
|
||||
// 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 <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
// We use JSONDocument for DocumentDB API
|
||||
// Implementation inspired by folly::dynamic and rapidjson
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// NOTE: none of this is thread-safe
|
||||
class JSONDocument {
|
||||
public:
|
||||
// return nullptr on parse failure
|
||||
static JSONDocument* ParseJSON(const char* json);
|
||||
|
||||
enum Type {
|
||||
kNull,
|
||||
kArray,
|
||||
kBool,
|
||||
kDouble,
|
||||
kInt64,
|
||||
kObject,
|
||||
kString,
|
||||
};
|
||||
|
||||
JSONDocument(); // null
|
||||
/* implicit */ JSONDocument(bool b);
|
||||
/* implicit */ JSONDocument(double d);
|
||||
/* implicit */ JSONDocument(int64_t i);
|
||||
/* implicit */ JSONDocument(const std::string& s);
|
||||
/* implicit */ JSONDocument(const char* s);
|
||||
// constructs JSONDocument of specific type with default value
|
||||
explicit JSONDocument(Type type);
|
||||
|
||||
// copy constructor
|
||||
JSONDocument(const JSONDocument& json_document);
|
||||
|
||||
~JSONDocument();
|
||||
|
||||
Type type() const;
|
||||
|
||||
// REQUIRES: IsObject()
|
||||
bool Contains(const std::string& key) const;
|
||||
// Returns nullptr if !Contains()
|
||||
// don't delete the returned pointer
|
||||
// REQUIRES: IsObject()
|
||||
const JSONDocument* Get(const std::string& key) const;
|
||||
// REQUIRES: IsObject()
|
||||
JSONDocument& operator[](const std::string& key);
|
||||
// REQUIRES: IsObject()
|
||||
const JSONDocument& operator[](const std::string& key) const;
|
||||
// returns `this`, so you can chain operations.
|
||||
// Copies value
|
||||
// REQUIRES: IsObject()
|
||||
JSONDocument* Set(const std::string& key, const JSONDocument& value);
|
||||
|
||||
// REQUIRES: IsArray() == true || IsObject() == true
|
||||
size_t Count() const;
|
||||
|
||||
// REQUIRES: IsArray()
|
||||
const JSONDocument* GetFromArray(size_t i) const;
|
||||
// REQUIRES: IsArray()
|
||||
JSONDocument& operator[](size_t i);
|
||||
// REQUIRES: IsArray()
|
||||
const JSONDocument& operator[](size_t i) const;
|
||||
// returns `this`, so you can chain operations.
|
||||
// Copies the value
|
||||
// REQUIRES: IsArray() && i < Count()
|
||||
JSONDocument* SetInArray(size_t i, const JSONDocument& value);
|
||||
// REQUIRES: IsArray()
|
||||
JSONDocument* PushBack(const JSONDocument& value);
|
||||
|
||||
bool IsNull() const;
|
||||
bool IsArray() const;
|
||||
bool IsBool() const;
|
||||
bool IsDouble() const;
|
||||
bool IsInt64() const;
|
||||
bool IsObject() const;
|
||||
bool IsString() const;
|
||||
|
||||
// REQUIRES: IsBool() == true
|
||||
bool GetBool() const;
|
||||
// REQUIRES: IsDouble() == true
|
||||
double GetDouble() const;
|
||||
// REQUIRES: IsInt64() == true
|
||||
int64_t GetInt64() const;
|
||||
// REQUIRES: IsString() == true
|
||||
const std::string& GetString() const;
|
||||
|
||||
bool operator==(const JSONDocument& rhs) const;
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
class ItemsIteratorGenerator;
|
||||
|
||||
public:
|
||||
// REQUIRES: IsObject()
|
||||
ItemsIteratorGenerator Items() const;
|
||||
|
||||
// appends serialized object to dst
|
||||
void Serialize(std::string* dst) const;
|
||||
// returns nullptr if Slice doesn't represent valid serialized JSONDocument
|
||||
static JSONDocument* Deserialize(const Slice& src);
|
||||
|
||||
private:
|
||||
void SerializeInternal(std::string* dst, bool type_prefix) const;
|
||||
// returns false if Slice doesn't represent valid serialized JSONDocument.
|
||||
// Otherwise, true
|
||||
bool DeserializeInternal(Slice* input);
|
||||
|
||||
typedef std::vector<JSONDocument*> Array;
|
||||
typedef std::unordered_map<std::string, JSONDocument*> Object;
|
||||
|
||||
// iteration on objects
|
||||
class const_item_iterator {
|
||||
public:
|
||||
typedef Object::const_iterator It;
|
||||
typedef Object::value_type value_type;
|
||||
/* implicit */ const_item_iterator(It it) : it_(it) {}
|
||||
It& operator++() { return ++it_; }
|
||||
bool operator!=(const const_item_iterator& other) {
|
||||
return it_ != other.it_;
|
||||
}
|
||||
value_type operator*() { return *it_; }
|
||||
|
||||
private:
|
||||
It it_;
|
||||
};
|
||||
class ItemsIteratorGenerator {
|
||||
public:
|
||||
/* implicit */ ItemsIteratorGenerator(const Object& object)
|
||||
: object_(object) {}
|
||||
const_item_iterator begin() { return object_.begin(); }
|
||||
const_item_iterator end() { return object_.end(); }
|
||||
|
||||
private:
|
||||
const Object& object_;
|
||||
};
|
||||
|
||||
union Data {
|
||||
Data() : n(nullptr) {}
|
||||
~Data() {}
|
||||
|
||||
void* n;
|
||||
Array a;
|
||||
bool b;
|
||||
double d;
|
||||
int64_t i;
|
||||
std::string s;
|
||||
Object o;
|
||||
} data_;
|
||||
const Type type_;
|
||||
|
||||
// Our serialization format's first byte specifies the encoding version. That
|
||||
// way, we can easily change our format while providing backwards
|
||||
// compatibility. This constant specifies the current version of the
|
||||
// serialization format
|
||||
static const char kSerializationFormatVersion;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,233 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace spatial {
|
||||
|
||||
// NOTE: SpatialDB is experimental and we might change its API without warning.
|
||||
// Please talk to us before developing against SpatialDB API.
|
||||
//
|
||||
// SpatialDB is a support for spatial indexes built on top of RocksDB.
|
||||
// When creating a new SpatialDB, clients specifies a list of spatial indexes to
|
||||
// build on their data. Each spatial index is defined by the area and
|
||||
// granularity. If you're storing map data, different spatial index
|
||||
// granularities can be used for different zoom levels.
|
||||
//
|
||||
// Each element inserted into SpatialDB has:
|
||||
// * a bounding box, which determines how will the element be indexed
|
||||
// * string blob, which will usually be WKB representation of the polygon
|
||||
// (http://en.wikipedia.org/wiki/Well-known_text)
|
||||
// * feature set, which is a map of key-value pairs, where value can be null,
|
||||
// int, double, bool, string
|
||||
// * a list of indexes to insert the element in
|
||||
//
|
||||
// Each query is executed on a single spatial index. Query guarantees that it
|
||||
// will return all elements intersecting the specified bounding box, but it
|
||||
// might also return some extra non-intersecting elements.
|
||||
|
||||
// Variant is a class that can be many things: null, bool, int, double or string
|
||||
// It is used to store different value types in FeatureSet (see below)
|
||||
struct Variant {
|
||||
// Don't change the values here, they are persisted on disk
|
||||
enum Type {
|
||||
kNull = 0x0,
|
||||
kBool = 0x1,
|
||||
kInt = 0x2,
|
||||
kDouble = 0x3,
|
||||
kString = 0x4,
|
||||
};
|
||||
|
||||
Variant() : type_(kNull) {}
|
||||
/* implicit */ Variant(bool b) : type_(kBool) { data_.b = b; }
|
||||
/* implicit */ Variant(uint64_t i) : type_(kInt) { data_.i = i; }
|
||||
/* implicit */ Variant(double d) : type_(kDouble) { data_.d = d; }
|
||||
/* implicit */ Variant(const std::string& s) : type_(kString) {
|
||||
new (&data_.s) std::string(s);
|
||||
}
|
||||
|
||||
Variant(const Variant& v);
|
||||
|
||||
~Variant() {
|
||||
if (type_ == kString) {
|
||||
using std::string;
|
||||
(&data_.s)->~string();
|
||||
}
|
||||
}
|
||||
|
||||
Type type() const { return type_; }
|
||||
bool get_bool() const { return data_.b; }
|
||||
uint64_t get_int() const { return data_.i; }
|
||||
double get_double() const { return data_.d; }
|
||||
const std::string& get_string() const { return data_.s; }
|
||||
|
||||
bool operator==(const Variant& other);
|
||||
bool operator!=(const Variant& other);
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
union Data {
|
||||
Data() {}
|
||||
~Data() {}
|
||||
bool b;
|
||||
uint64_t i;
|
||||
double d;
|
||||
std::string s;
|
||||
} data_;
|
||||
};
|
||||
|
||||
// FeatureSet is a map of key-value pairs. One feature set is associated with
|
||||
// each element in SpatialDB. It can be used to add rich data about the element.
|
||||
class FeatureSet {
|
||||
private:
|
||||
typedef std::unordered_map<std::string, Variant> map;
|
||||
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
/* implicit */ iterator(const map::const_iterator itr) : itr_(itr) {}
|
||||
iterator& operator++() {
|
||||
++itr_;
|
||||
return *this;
|
||||
}
|
||||
bool operator!=(const iterator& other) { return itr_ != other.itr_; }
|
||||
bool operator==(const iterator& other) { return itr_ == other.itr_; }
|
||||
map::value_type operator*() { return *itr_; }
|
||||
|
||||
private:
|
||||
map::const_iterator itr_;
|
||||
};
|
||||
FeatureSet() = default;
|
||||
|
||||
FeatureSet* Set(const std::string& key, const Variant& value);
|
||||
bool Contains(const std::string& key) const;
|
||||
// REQUIRES: Contains(key)
|
||||
const Variant& Get(const std::string& key) const;
|
||||
iterator Find(const std::string& key) const;
|
||||
|
||||
iterator begin() const { return map_.begin(); }
|
||||
iterator end() const { return map_.end(); }
|
||||
|
||||
void Clear();
|
||||
|
||||
void Serialize(std::string* output) const;
|
||||
// REQUIRED: empty FeatureSet
|
||||
bool Deserialize(const Slice& input);
|
||||
|
||||
private:
|
||||
map map_;
|
||||
};
|
||||
|
||||
// BoundingBox is a helper structure for defining rectangles representing
|
||||
// bounding boxes of spatial elements.
|
||||
template <typename T>
|
||||
struct BoundingBox {
|
||||
T min_x, min_y, max_x, max_y;
|
||||
BoundingBox() = default;
|
||||
BoundingBox(T _min_x, T _min_y, T _max_x, T _max_y)
|
||||
: min_x(_min_x), min_y(_min_y), max_x(_max_x), max_y(_max_y) {}
|
||||
|
||||
bool Intersects(const BoundingBox<T>& a) const {
|
||||
return !(min_x > a.max_x || min_y > a.max_y || a.min_x > max_x ||
|
||||
a.min_y > max_y);
|
||||
}
|
||||
};
|
||||
|
||||
struct SpatialDBOptions {
|
||||
uint64_t cache_size = 1 * 1024 * 1024 * 1024LL; // 1GB
|
||||
int num_threads = 16;
|
||||
bool bulk_load = true;
|
||||
};
|
||||
|
||||
// Cursor is used to return data from the query to the client. To get all the
|
||||
// data from the query, just call Next() while Valid() is true
|
||||
class Cursor {
|
||||
public:
|
||||
Cursor() = default;
|
||||
virtual ~Cursor() {}
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() = 0;
|
||||
|
||||
// Lifetime of the underlying storage until the next call to Next()
|
||||
// REQUIRES: Valid()
|
||||
virtual const Slice blob() = 0;
|
||||
// Lifetime of the underlying storage until the next call to Next()
|
||||
// REQUIRES: Valid()
|
||||
virtual const FeatureSet& feature_set() = 0;
|
||||
|
||||
virtual Status status() const = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Cursor(const Cursor&);
|
||||
void operator=(const Cursor&);
|
||||
};
|
||||
|
||||
// SpatialIndexOptions defines a spatial index that will be built on the data
|
||||
struct SpatialIndexOptions {
|
||||
// Spatial indexes are referenced by names
|
||||
std::string name;
|
||||
// An area that is indexed. If the element is not intersecting with spatial
|
||||
// index's bbox, it will not be inserted into the index
|
||||
BoundingBox<double> bbox;
|
||||
// tile_bits control the granularity of the spatial index. Each dimension of
|
||||
// the bbox will be split into (1 << tile_bits) tiles, so there will be a
|
||||
// total of (1 << tile_bits)^2 tiles. It is recommended to configure a size of
|
||||
// each tile to be approximately the size of the query on that spatial index
|
||||
uint32_t tile_bits;
|
||||
SpatialIndexOptions() {}
|
||||
SpatialIndexOptions(const std::string& _name,
|
||||
const BoundingBox<double>& _bbox, uint32_t _tile_bits)
|
||||
: name(_name), bbox(_bbox), tile_bits(_tile_bits) {}
|
||||
};
|
||||
|
||||
class SpatialDB : public StackableDB {
|
||||
public:
|
||||
// Creates the SpatialDB with specified list of indexes.
|
||||
// REQUIRED: db doesn't exist
|
||||
static Status Create(const SpatialDBOptions& options, const std::string& name,
|
||||
const std::vector<SpatialIndexOptions>& spatial_indexes);
|
||||
|
||||
// Open the existing SpatialDB. The resulting db object will be returned
|
||||
// through db parameter.
|
||||
// REQUIRED: db was created using SpatialDB::Create
|
||||
static Status Open(const SpatialDBOptions& options, const std::string& name,
|
||||
SpatialDB** db, bool read_only = false);
|
||||
|
||||
explicit SpatialDB(DB* db) : StackableDB(db) {}
|
||||
|
||||
// Insert the element into the DB. Element will be inserted into specified
|
||||
// spatial_indexes, based on specified bbox.
|
||||
// REQUIRES: spatial_indexes.size() > 0
|
||||
virtual Status Insert(const WriteOptions& write_options,
|
||||
const BoundingBox<double>& bbox, const Slice& blob,
|
||||
const FeatureSet& feature_set,
|
||||
const std::vector<std::string>& spatial_indexes) = 0;
|
||||
|
||||
// Calling Compact() after inserting a bunch of elements should speed up
|
||||
// reading. This is especially useful if you use SpatialDBOptions::bulk_load
|
||||
virtual Status Compact() = 0;
|
||||
|
||||
// Query the specified spatial_index. Query will return all elements that
|
||||
// intersect bbox, but it may also return some extra elements.
|
||||
virtual Cursor* Query(const ReadOptions& read_options,
|
||||
const BoundingBox<double>& bbox,
|
||||
const std::string& spatial_index) = 0;
|
||||
};
|
||||
|
||||
} // namespace spatial
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// This class contains APIs to stack rocksdb wrappers.Eg. Stack TTL over base d
|
||||
class StackableDB : public DB {
|
||||
public:
|
||||
// StackableDB is the owner of db now!
|
||||
explicit StackableDB(DB* db) : db_(db) {}
|
||||
|
||||
~StackableDB() {
|
||||
delete db_;
|
||||
}
|
||||
|
||||
virtual DB* GetBaseDB() {
|
||||
return db_;
|
||||
}
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
return db_->CreateColumnFamily(options, column_family_name, handle);
|
||||
}
|
||||
|
||||
virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
return db_->DropColumnFamily(column_family);
|
||||
}
|
||||
|
||||
using DB::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& val) override {
|
||||
return db_->Put(options, column_family, key, val);
|
||||
}
|
||||
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value) override {
|
||||
return db_->Get(options, column_family, key, value);
|
||||
}
|
||||
|
||||
using DB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override {
|
||||
return db_->MultiGet(options, column_family, keys, values);
|
||||
}
|
||||
|
||||
using DB::KeyMayExist;
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) override {
|
||||
return db_->KeyMayExist(options, column_family, key, value, value_found);
|
||||
}
|
||||
|
||||
using DB::Delete;
|
||||
virtual Status Delete(const WriteOptions& wopts,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
return db_->Delete(wopts, column_family, key);
|
||||
}
|
||||
|
||||
using DB::Merge;
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
return db_->Merge(options, column_family, key, value);
|
||||
}
|
||||
|
||||
|
||||
virtual Status Write(const WriteOptions& opts, WriteBatch* updates)
|
||||
override {
|
||||
return db_->Write(opts, updates);
|
||||
}
|
||||
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& opts,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return db_->NewIterator(opts, column_family);
|
||||
}
|
||||
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
return db_->NewIterators(options, column_families, iterators);
|
||||
}
|
||||
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override {
|
||||
return db_->GetSnapshot();
|
||||
}
|
||||
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
|
||||
return db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override {
|
||||
return db_->GetProperty(column_family, property, value);
|
||||
}
|
||||
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override {
|
||||
return db_->GetIntProperty(column_family, property, value);
|
||||
}
|
||||
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* r, int n,
|
||||
uint64_t* sizes) override {
|
||||
return db_->GetApproximateSizes(column_family, r, n, sizes);
|
||||
}
|
||||
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false, int target_level = -1,
|
||||
uint32_t target_path_id = 0) override {
|
||||
return db_->CompactRange(column_family, begin, end, reduce_level,
|
||||
target_level, target_path_id);
|
||||
}
|
||||
|
||||
using DB::NumberLevels;
|
||||
virtual int NumberLevels(ColumnFamilyHandle* column_family) override {
|
||||
return db_->NumberLevels(column_family);
|
||||
}
|
||||
|
||||
using DB::MaxMemCompactionLevel;
|
||||
virtual int MaxMemCompactionLevel(ColumnFamilyHandle* column_family)
|
||||
override {
|
||||
return db_->MaxMemCompactionLevel(column_family);
|
||||
}
|
||||
|
||||
using DB::Level0StopWriteTrigger;
|
||||
virtual int Level0StopWriteTrigger(ColumnFamilyHandle* column_family)
|
||||
override {
|
||||
return db_->Level0StopWriteTrigger(column_family);
|
||||
}
|
||||
|
||||
virtual const std::string& GetName() const override {
|
||||
return db_->GetName();
|
||||
}
|
||||
|
||||
virtual Env* GetEnv() const override {
|
||||
return db_->GetEnv();
|
||||
}
|
||||
|
||||
using DB::GetOptions;
|
||||
virtual const Options& GetOptions(ColumnFamilyHandle* column_family) const
|
||||
override {
|
||||
return db_->GetOptions(column_family);
|
||||
}
|
||||
|
||||
using DB::Flush;
|
||||
virtual Status Flush(const FlushOptions& fopts,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return db_->Flush(fopts, column_family);
|
||||
}
|
||||
|
||||
virtual Status DisableFileDeletions() override {
|
||||
return db_->DisableFileDeletions();
|
||||
}
|
||||
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
return db_->EnableFileDeletions(force);
|
||||
}
|
||||
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* metadata) override {
|
||||
db_->GetLiveFilesMetaData(metadata);
|
||||
}
|
||||
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,
|
||||
bool flush_memtable = true) override {
|
||||
return db_->GetLiveFiles(vec, mfs, flush_memtable);
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override {
|
||||
return db_->GetLatestSequenceNumber();
|
||||
}
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files) override {
|
||||
return db_->GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(std::string name) override {
|
||||
return db_->DeleteFile(name);
|
||||
}
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) {
|
||||
return db_->GetDbIdentity(identity);
|
||||
}
|
||||
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props) {
|
||||
return db_->GetPropertiesOfAllTables(column_family, props);
|
||||
}
|
||||
|
||||
virtual Status GetUpdatesSince(
|
||||
SequenceNumber seq_number, unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) override {
|
||||
return db_->GetUpdatesSince(seq_number, iter, read_options);
|
||||
}
|
||||
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
|
||||
return db_->DefaultColumnFamily();
|
||||
}
|
||||
|
||||
protected:
|
||||
DB* db_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Please don't use this class. It's deprecated
|
||||
class UtilityDB {
|
||||
public:
|
||||
// This function is here only for backwards compatibility. Please use the
|
||||
// functions defined in DBWithTTl (rocksdb/utilities/db_ttl.h)
|
||||
// (deprecated)
|
||||
__attribute__((deprecated)) static Status OpenTtlDB(const Options& options,
|
||||
const std::string& name,
|
||||
StackableDB** dbptr,
|
||||
int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Also update Makefile if you change these
|
||||
#define ROCKSDB_MAJOR 3
|
||||
#define ROCKSDB_MINOR 3
|
||||
#define ROCKSDB_MINOR 4
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -8,244 +8,5 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct BackupableDBOptions {
|
||||
// Where to keep the backup files. Has to be different than dbname_
|
||||
// Best to set this to dbname_ + "/backups"
|
||||
// Required
|
||||
std::string backup_dir;
|
||||
|
||||
// Backup Env object. It will be used for backup file I/O. If it's
|
||||
// nullptr, backups will be written out using DBs Env. If it's
|
||||
// non-nullptr, backup's I/O will be performed using this object.
|
||||
// If you want to have backups on HDFS, use HDFS Env here!
|
||||
// Default: nullptr
|
||||
Env* backup_env;
|
||||
|
||||
// If share_table_files == true, backup will assume that table files with
|
||||
// same name have the same contents. This enables incremental backups and
|
||||
// avoids unnecessary data copies.
|
||||
// If share_table_files == false, each backup will be on its own and will
|
||||
// not share any data with other backups.
|
||||
// default: true
|
||||
bool share_table_files;
|
||||
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log;
|
||||
|
||||
// If sync == true, we can guarantee you'll get consistent backup even
|
||||
// on a machine crash/reboot. Backup process is slower with sync enabled.
|
||||
// If sync == false, we don't guarantee anything on machine reboot. However,
|
||||
// chances are some of the backups are consistent.
|
||||
// Default: true
|
||||
bool sync;
|
||||
|
||||
// If true, it will delete whatever backups there are already
|
||||
// Default: false
|
||||
bool destroy_old_data;
|
||||
|
||||
// If false, we won't backup log files. This option can be useful for backing
|
||||
// up in-memory databases where log file are persisted, but table files are in
|
||||
// memory.
|
||||
// Default: true
|
||||
bool backup_log_files;
|
||||
|
||||
// Max bytes that can be transferred in a second during backup.
|
||||
// If 0, go as fast as you can
|
||||
// Default: 0
|
||||
uint64_t backup_rate_limit;
|
||||
|
||||
// Max bytes that can be transferred in a second during restore.
|
||||
// If 0, go as fast as you can
|
||||
// Default: 0
|
||||
uint64_t restore_rate_limit;
|
||||
|
||||
// Only used if share_table_files is set to true. If true, will consider that
|
||||
// backups can come from different databases, hence a sst is not uniquely
|
||||
// identifed by its name, but by the triple (file name, crc32, file length)
|
||||
// Default: false
|
||||
// Note: this is an experimental option, and you'll need to set it manually
|
||||
// *turn it on only if you know what you're doing*
|
||||
bool share_files_with_checksum;
|
||||
|
||||
void Dump(Logger* logger) const;
|
||||
|
||||
explicit BackupableDBOptions(const std::string& _backup_dir,
|
||||
Env* _backup_env = nullptr,
|
||||
bool _share_table_files = true,
|
||||
Logger* _info_log = nullptr, bool _sync = true,
|
||||
bool _destroy_old_data = false,
|
||||
bool _backup_log_files = true,
|
||||
uint64_t _backup_rate_limit = 0,
|
||||
uint64_t _restore_rate_limit = 0)
|
||||
: backup_dir(_backup_dir),
|
||||
backup_env(_backup_env),
|
||||
share_table_files(_share_table_files),
|
||||
info_log(_info_log),
|
||||
sync(_sync),
|
||||
destroy_old_data(_destroy_old_data),
|
||||
backup_log_files(_backup_log_files),
|
||||
backup_rate_limit(_backup_rate_limit),
|
||||
restore_rate_limit(_restore_rate_limit),
|
||||
share_files_with_checksum(false) {
|
||||
assert(share_table_files || !share_files_with_checksum);
|
||||
}
|
||||
};
|
||||
|
||||
struct RestoreOptions {
|
||||
// If true, restore won't overwrite the existing log files in wal_dir. It will
|
||||
// also move all log files from archive directory to wal_dir. Use this option
|
||||
// in combination with BackupableDBOptions::backup_log_files = false for
|
||||
// persisting in-memory databases.
|
||||
// Default: false
|
||||
bool keep_log_files;
|
||||
|
||||
explicit RestoreOptions(bool _keep_log_files = false)
|
||||
: keep_log_files(_keep_log_files) {}
|
||||
};
|
||||
|
||||
typedef uint32_t BackupID;
|
||||
|
||||
struct BackupInfo {
|
||||
BackupID backup_id;
|
||||
int64_t timestamp;
|
||||
uint64_t size;
|
||||
|
||||
BackupInfo() {}
|
||||
BackupInfo(BackupID _backup_id, int64_t _timestamp, uint64_t _size)
|
||||
: backup_id(_backup_id), timestamp(_timestamp), size(_size) {}
|
||||
};
|
||||
|
||||
class BackupEngineReadOnly {
|
||||
public:
|
||||
virtual ~BackupEngineReadOnly() {}
|
||||
|
||||
static BackupEngineReadOnly* NewReadOnlyBackupEngine(
|
||||
Env* db_env, const BackupableDBOptions& options);
|
||||
|
||||
// You can GetBackupInfo safely, even with other BackupEngine performing
|
||||
// backups on the same directory
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
|
||||
// Restoring DB from backup is NOT safe when there is another BackupEngine
|
||||
// running that might call DeleteBackup() or PurgeOldBackups(). It is caller's
|
||||
// responsibility to synchronize the operation, i.e. don't delete the backup
|
||||
// when you're restoring from it
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
};
|
||||
|
||||
// Please see the documentation in BackupableDB and RestoreBackupableDB
|
||||
class BackupEngine {
|
||||
public:
|
||||
virtual ~BackupEngine() {}
|
||||
|
||||
static BackupEngine* NewBackupEngine(Env* db_env,
|
||||
const BackupableDBOptions& options);
|
||||
|
||||
virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false) = 0;
|
||||
virtual Status PurgeOldBackups(uint32_t num_backups_to_keep) = 0;
|
||||
virtual Status DeleteBackup(BackupID backup_id) = 0;
|
||||
virtual void StopBackup() = 0;
|
||||
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
};
|
||||
|
||||
// Stack your DB with BackupableDB to be able to backup the DB
|
||||
class BackupableDB : public StackableDB {
|
||||
public:
|
||||
// BackupableDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// BackupableDB ownes the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of BackupableDB
|
||||
BackupableDB(DB* db, const BackupableDBOptions& options);
|
||||
virtual ~BackupableDB();
|
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
Status CreateNewBackup(bool flush_before_backup = false);
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
// Call this from another thread if you want to stop the backup
|
||||
// that is currently happening. It will return immediatelly, will
|
||||
// not wait for the backup to stop.
|
||||
// The backup will stop ASAP and the call to CreateNewBackup will
|
||||
// return Status::Incomplete(). It will not clean up after itself, but
|
||||
// the state will remain consistent. The state will be cleaned up
|
||||
// next time you create BackupableDB or RestoreBackupableDB.
|
||||
void StopBackup();
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
};
|
||||
|
||||
// Use this class to access information about backups and restore from them
|
||||
class RestoreBackupableDB {
|
||||
public:
|
||||
RestoreBackupableDB(Env* db_env, const BackupableDBOptions& options);
|
||||
~RestoreBackupableDB();
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
|
||||
// restore from backup with backup_id
|
||||
// IMPORTANT -- if options_.share_table_files == true and you restore DB
|
||||
// from some backup that is not the latest, and you start creating new
|
||||
// backups from the new DB, they will probably fail
|
||||
//
|
||||
// Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
|
||||
// If you add new data to the DB and try creating a new backup now, the
|
||||
// database will diverge from backups 4 and 5 and the new backup will fail.
|
||||
// If you want to create new backup, you will first have to delete backups 4
|
||||
// and 5.
|
||||
Status RestoreDBFromBackup(BackupID backup_id, const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
|
||||
// restore from the latest backup
|
||||
Status RestoreDBFromLatestBackup(const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/backupable_db.h
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
@@ -4,65 +4,5 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Database with TTL support.
|
||||
//
|
||||
// USE-CASES:
|
||||
// This API should be used to open the db when key-values inserted are
|
||||
// meant to be removed from the db in a non-strict 'ttl' amount of time
|
||||
// Therefore, this guarantees that key-values inserted will remain in the
|
||||
// db for >= ttl amount of time and the db will make efforts to remove the
|
||||
// key-values as soon as possible after ttl seconds of their insertion.
|
||||
//
|
||||
// BEHAVIOUR:
|
||||
// TTL is accepted in seconds
|
||||
// (int32_t)Timestamp(creation) is suffixed to values in Put internally
|
||||
// Expired TTL values deleted in compaction only:(Timestamp+ttl<time_now)
|
||||
// Get/Iterator may return expired entries(compaction not run on them yet)
|
||||
// Different TTL may be used during different Opens
|
||||
// Example: Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2
|
||||
// Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t>=5
|
||||
// read_only=true opens in the usual read-only mode. Compactions will not be
|
||||
// triggered(neither manual nor automatic), so no expired entries removed
|
||||
//
|
||||
// CONSTRAINTS:
|
||||
// Not specifying/passing or non-positive TTL behaves like TTL = infinity
|
||||
//
|
||||
// !!!WARNING!!!:
|
||||
// Calling DB::Open directly to re-open a db created by this API will get
|
||||
// corrupt values(timestamp suffixed) and no ttl effect will be there
|
||||
// during the second Open, so use this API consistently to open the db
|
||||
// Be careful when passing ttl with a small positive value because the
|
||||
// whole database may be deleted in a small amount of time
|
||||
|
||||
class DBWithTTL : public StackableDB {
|
||||
public:
|
||||
virtual Status CreateColumnFamilyWithTtl(
|
||||
const ColumnFamilyOptions& options, const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle, int ttl) = 0;
|
||||
|
||||
static Status Open(const Options& options, const std::string& dbname,
|
||||
DBWithTTL** dbptr, int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
|
||||
static Status Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles,
|
||||
DBWithTTL** dbptr, std::vector<int32_t> ttls,
|
||||
bool read_only = false);
|
||||
|
||||
protected:
|
||||
explicit DBWithTTL(DB* db) : StackableDB(db) {}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/db_ttl.h
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
@@ -4,146 +4,5 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "utilities/json_document.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// IMPORTANT: DocumentDB is a work in progress. It is unstable and we might
|
||||
// change the API without warning. Talk to RocksDB team before using this in
|
||||
// production ;)
|
||||
|
||||
// DocumentDB is a layer on top of RocksDB that provides a very simple JSON API.
|
||||
// When creating a DB, you specify a list of indexes you want to keep on your
|
||||
// data. You can insert a JSON document to the DB, which is automatically
|
||||
// indexed. Every document added to the DB needs to have "_id" field which is
|
||||
// automatically indexed and is an unique primary key. All other indexes are
|
||||
// non-unique.
|
||||
|
||||
// NOTE: field names in the JSON are NOT allowed to start with '$' or
|
||||
// contain '.'. We don't currently enforce that rule, but will start behaving
|
||||
// badly.
|
||||
|
||||
// Cursor is what you get as a result of executing query. To get all
|
||||
// results from a query, call Next() on a Cursor while Valid() returns true
|
||||
class Cursor {
|
||||
public:
|
||||
Cursor() = default;
|
||||
virtual ~Cursor() {}
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
virtual void Next() = 0;
|
||||
// Lifecycle of the returned JSONDocument is until the next Next() call
|
||||
virtual const JSONDocument& document() const = 0;
|
||||
virtual Status status() const = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Cursor(const Cursor&);
|
||||
void operator=(const Cursor&);
|
||||
};
|
||||
|
||||
struct DocumentDBOptions {
|
||||
int background_threads = 4;
|
||||
uint64_t memtable_size = 128 * 1024 * 1024; // 128 MB
|
||||
uint64_t cache_size = 1 * 1024 * 1024 * 1024; // 1 GB
|
||||
};
|
||||
|
||||
// TODO(icanadi) Add `JSONDocument* info` parameter to all calls that can be
|
||||
// used by the caller to get more information about the call execution (number
|
||||
// of dropped records, number of updated records, etc.)
|
||||
class DocumentDB : public StackableDB {
|
||||
public:
|
||||
struct IndexDescriptor {
|
||||
// Currently, you can only define an index on a single field. To specify an
|
||||
// index on a field X, set index description to JSON "{X: 1}"
|
||||
// Currently the value needs to be 1, which means ascending.
|
||||
// In the future, we plan to also support indexes on multiple keys, where
|
||||
// you could mix ascending sorting (1) with descending sorting indexes (-1)
|
||||
JSONDocument* description;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// Open DocumentDB with specified indexes. The list of indexes has to be
|
||||
// complete, i.e. include all indexes present in the DB, except the primary
|
||||
// key index.
|
||||
// Otherwise, Open() will return an error
|
||||
static Status Open(const DocumentDBOptions& options, const std::string& name,
|
||||
const std::vector<IndexDescriptor>& indexes,
|
||||
DocumentDB** db, bool read_only = false);
|
||||
|
||||
explicit DocumentDB(DB* db) : StackableDB(db) {}
|
||||
|
||||
// Create a new index. It will stop all writes for the duration of the call.
|
||||
// All current documents in the DB are scanned and corresponding index entries
|
||||
// are created
|
||||
virtual Status CreateIndex(const WriteOptions& write_options,
|
||||
const IndexDescriptor& index) = 0;
|
||||
|
||||
// Drop an index. Client is responsible to make sure that index is not being
|
||||
// used by currently executing queries
|
||||
virtual Status DropIndex(const std::string& name) = 0;
|
||||
|
||||
// Insert a document to the DB. The document needs to have a primary key "_id"
|
||||
// which can either be a string or an integer. Otherwise the write will fail
|
||||
// with InvalidArgument.
|
||||
virtual Status Insert(const WriteOptions& options,
|
||||
const JSONDocument& document) = 0;
|
||||
|
||||
// Deletes all documents matching a filter atomically
|
||||
virtual Status Remove(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
const JSONDocument& query) = 0;
|
||||
|
||||
// Does this sequence of operations:
|
||||
// 1. Find all documents matching a filter
|
||||
// 2. For all documents, atomically:
|
||||
// 2.1. apply the update operators
|
||||
// 2.2. update the secondary indexes
|
||||
//
|
||||
// Currently only $set update operator is supported.
|
||||
// Syntax is: {$set: {key1: value1, key2: value2, etc...}}
|
||||
// This operator will change a document's key1 field to value1, key2 to
|
||||
// value2, etc. New values will be set even if a document didn't have an entry
|
||||
// for the specified key.
|
||||
//
|
||||
// You can not change a primary key of a document.
|
||||
//
|
||||
// Update example: Update({id: {$gt: 5}, $index: id}, {$set: {enabled: true}})
|
||||
virtual Status Update(const ReadOptions& read_options,
|
||||
const WriteOptions& write_options,
|
||||
const JSONDocument& filter,
|
||||
const JSONDocument& updates) = 0;
|
||||
|
||||
// query has to be an array in which every element is an operator. Currently
|
||||
// only $filter operator is supported. Syntax of $filter operator is:
|
||||
// {$filter: {key1: condition1, key2: condition2, etc.}} where conditions can
|
||||
// be either:
|
||||
// 1) a single value in which case the condition is equality condition, or
|
||||
// 2) a defined operators, like {$gt: 4}, which will match all documents that
|
||||
// have key greater than 4.
|
||||
//
|
||||
// Supported operators are:
|
||||
// 1) $gt -- greater than
|
||||
// 2) $gte -- greater than or equal
|
||||
// 3) $lt -- less than
|
||||
// 4) $lte -- less than or equal
|
||||
// If you want the filter to use an index, you need to specify it like this:
|
||||
// {$filter: {...(conditions)..., $index: index_name}}
|
||||
//
|
||||
// Example query:
|
||||
// * [{$filter: {name: John, age: {$gte: 18}, $index: age}}]
|
||||
// will return all Johns whose age is greater or equal to 18 and it will use
|
||||
// index "age" to satisfy the query.
|
||||
virtual Cursor* Query(const ReadOptions& read_options,
|
||||
const JSONDocument& query) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/document_db.h
|
||||
#include "rocksdb/utilities/document_db.h"
|
||||
@@ -2,104 +2,7 @@
|
||||
// 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
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
//
|
||||
// Configurable options needed for setting up a Geo database
|
||||
//
|
||||
struct GeoDBOptions {
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log;
|
||||
|
||||
explicit GeoDBOptions(Logger* _info_log = nullptr):info_log(_info_log) { }
|
||||
};
|
||||
|
||||
//
|
||||
// A position in the earth's geoid
|
||||
//
|
||||
class GeoPosition {
|
||||
public:
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
explicit GeoPosition(double la = 0, double lo = 0) :
|
||||
latitude(la), longitude(lo) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Description of an object on the Geoid. It is located by a GPS location,
|
||||
// and is identified by the id. The value associated with this object is
|
||||
// an opaque string 'value'. Different objects identified by unique id's
|
||||
// can have the same gps-location associated with them.
|
||||
//
|
||||
class GeoObject {
|
||||
public:
|
||||
GeoPosition position;
|
||||
std::string id;
|
||||
std::string value;
|
||||
|
||||
GeoObject() {}
|
||||
|
||||
GeoObject(const GeoPosition& pos, const std::string& i,
|
||||
const std::string& val) :
|
||||
position(pos), id(i), value(val) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Stack your DB with GeoDB to be able to get geo-spatial support
|
||||
//
|
||||
class GeoDB : public StackableDB {
|
||||
public:
|
||||
// GeoDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// GeoDB owns the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of GeoDB
|
||||
// GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
virtual ~GeoDB() {}
|
||||
|
||||
// Insert a new object into the location database. The object is
|
||||
// uniquely identified by the id. If an object with the same id already
|
||||
// exists in the db, then the old one is overwritten by the new
|
||||
// object being inserted here.
|
||||
virtual Status Insert(const GeoObject& object) = 0;
|
||||
|
||||
// Retrieve the value of the object located at the specified GPS
|
||||
// location and is identified by the 'id'.
|
||||
virtual Status GetByPosition(const GeoPosition& pos,
|
||||
const Slice& id, std::string* value) = 0;
|
||||
|
||||
// Retrieve the value of the object identified by the 'id'. This method
|
||||
// could be potentially slower than GetByPosition
|
||||
virtual Status GetById(const Slice& id, GeoObject* object) = 0;
|
||||
|
||||
// Delete the specified object
|
||||
virtual Status Remove(const Slice& id) = 0;
|
||||
|
||||
// Returns a list of all items within a circular radius from the
|
||||
// specified gps location. If 'number_of_values' is specified,
|
||||
// then this call returns at most that many number of objects.
|
||||
// The radius is specified in 'meters'.
|
||||
virtual Status SearchRadial(const GeoPosition& pos,
|
||||
double radius,
|
||||
std::vector<GeoObject>* values,
|
||||
int number_of_values = INT_MAX) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/geo_db.h
|
||||
#include "rocksdb/utilities/geo_db.h"
|
||||
@@ -3,172 +3,5 @@
|
||||
// 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 <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
// We use JSONDocument for DocumentDB API
|
||||
// Implementation inspired by folly::dynamic and rapidjson
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// NOTE: none of this is thread-safe
|
||||
class JSONDocument {
|
||||
public:
|
||||
// return nullptr on parse failure
|
||||
static JSONDocument* ParseJSON(const char* json);
|
||||
|
||||
enum Type {
|
||||
kNull,
|
||||
kArray,
|
||||
kBool,
|
||||
kDouble,
|
||||
kInt64,
|
||||
kObject,
|
||||
kString,
|
||||
};
|
||||
|
||||
JSONDocument(); // null
|
||||
/* implicit */ JSONDocument(bool b);
|
||||
/* implicit */ JSONDocument(double d);
|
||||
/* implicit */ JSONDocument(int64_t i);
|
||||
/* implicit */ JSONDocument(const std::string& s);
|
||||
/* implicit */ JSONDocument(const char* s);
|
||||
// constructs JSONDocument of specific type with default value
|
||||
explicit JSONDocument(Type type);
|
||||
|
||||
// copy constructor
|
||||
JSONDocument(const JSONDocument& json_document);
|
||||
|
||||
~JSONDocument();
|
||||
|
||||
Type type() const;
|
||||
|
||||
// REQUIRES: IsObject()
|
||||
bool Contains(const std::string& key) const;
|
||||
// Returns nullptr if !Contains()
|
||||
// don't delete the returned pointer
|
||||
// REQUIRES: IsObject()
|
||||
const JSONDocument* Get(const std::string& key) const;
|
||||
// REQUIRES: IsObject()
|
||||
JSONDocument& operator[](const std::string& key);
|
||||
// REQUIRES: IsObject()
|
||||
const JSONDocument& operator[](const std::string& key) const;
|
||||
// returns `this`, so you can chain operations.
|
||||
// Copies value
|
||||
// REQUIRES: IsObject()
|
||||
JSONDocument* Set(const std::string& key, const JSONDocument& value);
|
||||
|
||||
// REQUIRES: IsArray() == true || IsObject() == true
|
||||
size_t Count() const;
|
||||
|
||||
// REQUIRES: IsArray()
|
||||
const JSONDocument* GetFromArray(size_t i) const;
|
||||
// REQUIRES: IsArray()
|
||||
JSONDocument& operator[](size_t i);
|
||||
// REQUIRES: IsArray()
|
||||
const JSONDocument& operator[](size_t i) const;
|
||||
// returns `this`, so you can chain operations.
|
||||
// Copies the value
|
||||
// REQUIRES: IsArray() && i < Count()
|
||||
JSONDocument* SetInArray(size_t i, const JSONDocument& value);
|
||||
// REQUIRES: IsArray()
|
||||
JSONDocument* PushBack(const JSONDocument& value);
|
||||
|
||||
bool IsNull() const;
|
||||
bool IsArray() const;
|
||||
bool IsBool() const;
|
||||
bool IsDouble() const;
|
||||
bool IsInt64() const;
|
||||
bool IsObject() const;
|
||||
bool IsString() const;
|
||||
|
||||
// REQUIRES: IsBool() == true
|
||||
bool GetBool() const;
|
||||
// REQUIRES: IsDouble() == true
|
||||
double GetDouble() const;
|
||||
// REQUIRES: IsInt64() == true
|
||||
int64_t GetInt64() const;
|
||||
// REQUIRES: IsString() == true
|
||||
const std::string& GetString() const;
|
||||
|
||||
bool operator==(const JSONDocument& rhs) const;
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
class ItemsIteratorGenerator;
|
||||
|
||||
public:
|
||||
// REQUIRES: IsObject()
|
||||
ItemsIteratorGenerator Items() const;
|
||||
|
||||
// appends serialized object to dst
|
||||
void Serialize(std::string* dst) const;
|
||||
// returns nullptr if Slice doesn't represent valid serialized JSONDocument
|
||||
static JSONDocument* Deserialize(const Slice& src);
|
||||
|
||||
private:
|
||||
void SerializeInternal(std::string* dst, bool type_prefix) const;
|
||||
// returns false if Slice doesn't represent valid serialized JSONDocument.
|
||||
// Otherwise, true
|
||||
bool DeserializeInternal(Slice* input);
|
||||
|
||||
typedef std::vector<JSONDocument*> Array;
|
||||
typedef std::unordered_map<std::string, JSONDocument*> Object;
|
||||
|
||||
// iteration on objects
|
||||
class const_item_iterator {
|
||||
public:
|
||||
typedef Object::const_iterator It;
|
||||
typedef Object::value_type value_type;
|
||||
/* implicit */ const_item_iterator(It it) : it_(it) {}
|
||||
It& operator++() { return ++it_; }
|
||||
bool operator!=(const const_item_iterator& other) {
|
||||
return it_ != other.it_;
|
||||
}
|
||||
value_type operator*() { return *it_; }
|
||||
|
||||
private:
|
||||
It it_;
|
||||
};
|
||||
class ItemsIteratorGenerator {
|
||||
public:
|
||||
/* implicit */ ItemsIteratorGenerator(const Object& object)
|
||||
: object_(object) {}
|
||||
const_item_iterator begin() { return object_.begin(); }
|
||||
const_item_iterator end() { return object_.end(); }
|
||||
|
||||
private:
|
||||
const Object& object_;
|
||||
};
|
||||
|
||||
union Data {
|
||||
Data() : n(nullptr) {}
|
||||
~Data() {}
|
||||
|
||||
void* n;
|
||||
Array a;
|
||||
bool b;
|
||||
double d;
|
||||
int64_t i;
|
||||
std::string s;
|
||||
Object o;
|
||||
} data_;
|
||||
const Type type_;
|
||||
|
||||
// Our serialization format's first byte specifies the encoding version. That
|
||||
// way, we can easily change our format while providing backwards
|
||||
// compatibility. This constant specifies the current version of the
|
||||
// serialization format
|
||||
static const char kSerializationFormatVersion;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/json_document.h
|
||||
#include "rocksdb/utilities/json_document.h"
|
||||
@@ -3,218 +3,5 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// This class contains APIs to stack rocksdb wrappers.Eg. Stack TTL over base d
|
||||
class StackableDB : public DB {
|
||||
public:
|
||||
// StackableDB is the owner of db now!
|
||||
explicit StackableDB(DB* db) : db_(db) {}
|
||||
|
||||
~StackableDB() {
|
||||
delete db_;
|
||||
}
|
||||
|
||||
virtual DB* GetBaseDB() {
|
||||
return db_;
|
||||
}
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& options,
|
||||
const std::string& column_family_name,
|
||||
ColumnFamilyHandle** handle) {
|
||||
return db_->CreateColumnFamily(options, column_family_name, handle);
|
||||
}
|
||||
|
||||
virtual Status DropColumnFamily(ColumnFamilyHandle* column_family) {
|
||||
return db_->DropColumnFamily(column_family);
|
||||
}
|
||||
|
||||
using DB::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& val) override {
|
||||
return db_->Put(options, column_family, key, val);
|
||||
}
|
||||
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value) override {
|
||||
return db_->Get(options, column_family, key, value);
|
||||
}
|
||||
|
||||
using DB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override {
|
||||
return db_->MultiGet(options, column_family, keys, values);
|
||||
}
|
||||
|
||||
using DB::KeyMayExist;
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) override {
|
||||
return db_->KeyMayExist(options, column_family, key, value, value_found);
|
||||
}
|
||||
|
||||
using DB::Delete;
|
||||
virtual Status Delete(const WriteOptions& wopts,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
return db_->Delete(wopts, column_family, key);
|
||||
}
|
||||
|
||||
using DB::Merge;
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
return db_->Merge(options, column_family, key, value);
|
||||
}
|
||||
|
||||
|
||||
virtual Status Write(const WriteOptions& opts, WriteBatch* updates)
|
||||
override {
|
||||
return db_->Write(opts, updates);
|
||||
}
|
||||
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& opts,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return db_->NewIterator(opts, column_family);
|
||||
}
|
||||
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
return db_->NewIterators(options, column_families, iterators);
|
||||
}
|
||||
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override {
|
||||
return db_->GetSnapshot();
|
||||
}
|
||||
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override {
|
||||
return db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override {
|
||||
return db_->GetProperty(column_family, property, value);
|
||||
}
|
||||
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* r, int n,
|
||||
uint64_t* sizes) override {
|
||||
return db_->GetApproximateSizes(column_family, r, n, sizes);
|
||||
}
|
||||
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool reduce_level = false,
|
||||
int target_level = -1) override {
|
||||
return db_->CompactRange(column_family, begin, end, reduce_level,
|
||||
target_level);
|
||||
}
|
||||
|
||||
using DB::NumberLevels;
|
||||
virtual int NumberLevels(ColumnFamilyHandle* column_family) override {
|
||||
return db_->NumberLevels(column_family);
|
||||
}
|
||||
|
||||
using DB::MaxMemCompactionLevel;
|
||||
virtual int MaxMemCompactionLevel(ColumnFamilyHandle* column_family)
|
||||
override {
|
||||
return db_->MaxMemCompactionLevel(column_family);
|
||||
}
|
||||
|
||||
using DB::Level0StopWriteTrigger;
|
||||
virtual int Level0StopWriteTrigger(ColumnFamilyHandle* column_family)
|
||||
override {
|
||||
return db_->Level0StopWriteTrigger(column_family);
|
||||
}
|
||||
|
||||
virtual const std::string& GetName() const override {
|
||||
return db_->GetName();
|
||||
}
|
||||
|
||||
virtual Env* GetEnv() const override {
|
||||
return db_->GetEnv();
|
||||
}
|
||||
|
||||
using DB::GetOptions;
|
||||
virtual const Options& GetOptions(ColumnFamilyHandle* column_family) const
|
||||
override {
|
||||
return db_->GetOptions(column_family);
|
||||
}
|
||||
|
||||
using DB::Flush;
|
||||
virtual Status Flush(const FlushOptions& fopts,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
return db_->Flush(fopts, column_family);
|
||||
}
|
||||
|
||||
virtual Status DisableFileDeletions() override {
|
||||
return db_->DisableFileDeletions();
|
||||
}
|
||||
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
return db_->EnableFileDeletions(force);
|
||||
}
|
||||
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* metadata) override {
|
||||
db_->GetLiveFilesMetaData(metadata);
|
||||
}
|
||||
|
||||
virtual Status GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,
|
||||
bool flush_memtable = true) override {
|
||||
return db_->GetLiveFiles(vec, mfs, flush_memtable);
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override {
|
||||
return db_->GetLatestSequenceNumber();
|
||||
}
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files) override {
|
||||
return db_->GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(std::string name) override {
|
||||
return db_->DeleteFile(name);
|
||||
}
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) {
|
||||
return db_->GetDbIdentity(identity);
|
||||
}
|
||||
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props) {
|
||||
return db_->GetPropertiesOfAllTables(column_family, props);
|
||||
}
|
||||
|
||||
virtual Status GetUpdatesSince(
|
||||
SequenceNumber seq_number, unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) override {
|
||||
return db_->GetUpdatesSince(seq_number, iter, read_options);
|
||||
}
|
||||
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
|
||||
return db_->DefaultColumnFamily();
|
||||
}
|
||||
|
||||
protected:
|
||||
DB* db_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#warning This file was moved to rocksdb/utilities/stackable_db.h
|
||||
#include "rocksdb/utilities/stackable_db.h"
|
||||
@@ -3,28 +3,5 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "utilities/db_ttl.h"
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Please don't use this class. It's deprecated
|
||||
class UtilityDB {
|
||||
public:
|
||||
// This function is here only for backwards compatibility. Please use the
|
||||
// functions defined in DBWithTTl (utilities/db_ttl.h)
|
||||
// (deprecated)
|
||||
__attribute__((deprecated)) static Status OpenTtlDB(const Options& options,
|
||||
const std::string& name,
|
||||
StackableDB** dbptr,
|
||||
int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
#warning This file was moved to rocksdb/utilities/utility_db.h
|
||||
#include "rocksdb/utilities/utility_db.h"
|
||||
@@ -3,11 +3,6 @@ NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB org.rocksdb.Options org.rocksdb.WriteB
|
||||
NATIVE_INCLUDE = ./include
|
||||
ROCKSDB_JAR = rocksdbjni.jar
|
||||
|
||||
ROCKSDBJNILIB = ./librocksdbjni.so
|
||||
ifeq ($(PLATFORM), OS_MACOSX)
|
||||
ROCKSDBJNILIB = ./librocksdbjni.jnilib
|
||||
endif
|
||||
|
||||
clean:
|
||||
-find . -name "*.class" -exec rm {} \;
|
||||
-find . -name "hs*.log" -exec rm {} \;
|
||||
@@ -16,7 +11,6 @@ clean:
|
||||
java:
|
||||
javac org/rocksdb/util/*.java org/rocksdb/*.java
|
||||
@cp ../HISTORY.md ./HISTORY-CPP.md
|
||||
jar -cf $(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class HISTORY*.md $(ROCKSDBJNILIB)
|
||||
@rm -f ./HISTORY-CPP.md
|
||||
javah -d $(NATIVE_INCLUDE) -jni $(NATIVE_JAVA_CLASSES)
|
||||
|
||||
|
||||
@@ -44,7 +44,9 @@ public class RocksDBSample {
|
||||
.setBlockSize(64 * SizeUnit.KB)
|
||||
.setMaxBackgroundCompactions(10)
|
||||
.setFilter(filter)
|
||||
.setCompressionType(CompressionType.SNAPPY_COMPRESSION);
|
||||
.setCacheNumShardBits(6)
|
||||
.setCompressionType(CompressionType.SNAPPY_COMPRESSION)
|
||||
.setCompactionStyle(CompactionStyle.UNIVERSAL);
|
||||
Statistics stats = options.statisticsPtr();
|
||||
|
||||
assert(options.createIfMissing() == true);
|
||||
@@ -53,7 +55,9 @@ public class RocksDBSample {
|
||||
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);
|
||||
|
||||
assert(options.memTableFactoryName().equals("SkipListFactory"));
|
||||
options.setMemTableConfig(
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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;
|
||||
|
||||
public enum CompactionStyle {
|
||||
LEVEL((byte) 0),
|
||||
UNIVERSAL((byte) 1),
|
||||
FIFO((byte) 2);
|
||||
|
||||
private final byte value_;
|
||||
|
||||
private CompactionStyle(byte value) {
|
||||
value_ = value;
|
||||
}
|
||||
|
||||
public byte getValue() {
|
||||
return value_;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,12 @@ package org.rocksdb;
|
||||
* Options to control the behavior of a database. It will be used
|
||||
* during the creation of a RocksDB (i.e., RocksDB.open()).
|
||||
*
|
||||
* Note that dispose() must be called before an Options instance
|
||||
* become out-of-scope to release the allocated memory in c++.
|
||||
* If dispose() function is not called, then it will be GC'd automatically and
|
||||
* native resources will be released as part of the process.
|
||||
*/
|
||||
public class Options extends RocksObject {
|
||||
static final long DEFAULT_CACHE_SIZE = 8 << 20;
|
||||
static final int DEFAULT_NUM_SHARD_BITS = -1;
|
||||
/**
|
||||
* Construct options for opening a RocksDB.
|
||||
*
|
||||
@@ -23,6 +24,7 @@ public class Options extends RocksObject {
|
||||
public Options() {
|
||||
super();
|
||||
cacheSize_ = DEFAULT_CACHE_SIZE;
|
||||
numShardBits_ = DEFAULT_NUM_SHARD_BITS;
|
||||
newOptions();
|
||||
env_ = RocksEnv.getDefault();
|
||||
}
|
||||
@@ -166,8 +168,11 @@ public class Options extends RocksObject {
|
||||
/**
|
||||
* Use the specified filter policy to reduce disk reads.
|
||||
*
|
||||
* Note that the caller should not dispose the input filter as
|
||||
* Options.dispose() will dispose this filter.
|
||||
* 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.
|
||||
@@ -215,6 +220,7 @@ public class Options extends RocksObject {
|
||||
* If cacheSize is non-positive, then cache will not be used.
|
||||
*
|
||||
* DEFAULT: 8M
|
||||
* @see setCacheNumShardBits()
|
||||
*/
|
||||
public Options setCacheSize(long cacheSize) {
|
||||
cacheSize_ = cacheSize;
|
||||
@@ -223,11 +229,42 @@ public class Options extends RocksObject {
|
||||
|
||||
/**
|
||||
* @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.
|
||||
@@ -1335,6 +1372,30 @@ 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.
|
||||
*/
|
||||
public Options setCompactionStyle(CompactionStyle compactionStyle) {
|
||||
setCompactionStyle(nativeHandle_, compactionStyle.getValue());
|
||||
return this;
|
||||
}
|
||||
private native void setCompactionStyle(long handle, byte compactionStyle);
|
||||
|
||||
/**
|
||||
* If true, place whole keys in the filter (not just prefixes).
|
||||
@@ -2397,6 +2458,7 @@ public class Options extends RocksObject {
|
||||
long handle, int prefixLength);
|
||||
|
||||
long cacheSize_;
|
||||
int numShardBits_;
|
||||
Filter filter_;
|
||||
RocksEnv env_;
|
||||
}
|
||||
@@ -99,6 +99,15 @@ 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
|
||||
* use any locks to prevent concurrent updates.
|
||||
*/
|
||||
public static RocksDB open(Options options, String path)
|
||||
throws RocksDBException {
|
||||
@@ -106,10 +115,16 @@ 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_, path);
|
||||
db.transferCppRawPointersOwnershipFrom(options);
|
||||
db.open(options.nativeHandle_, options.cacheSize_,
|
||||
options.numShardBits_, path);
|
||||
|
||||
db.storeOptionsInstance(options);
|
||||
return db;
|
||||
}
|
||||
|
||||
private void storeOptionsInstance(Options options) {
|
||||
options_ = options;
|
||||
}
|
||||
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
@@ -317,20 +332,10 @@ public class RocksDB extends RocksObject {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer the ownership of all c++ raw-pointers from Options
|
||||
* to RocksDB to ensure the life-time of those raw-pointers
|
||||
* will be at least as long as the life-time of any RocksDB
|
||||
* that uses these raw-pointers.
|
||||
*/
|
||||
protected void transferCppRawPointersOwnershipFrom(Options opt) {
|
||||
filter_ = opt.filter_;
|
||||
opt.filter_ = null;
|
||||
}
|
||||
|
||||
// native methods
|
||||
protected native void open(
|
||||
long optionsHandle, long cacheSize, String path) throws RocksDBException;
|
||||
long optionsHandle, long cacheSize, int numShardBits,
|
||||
String path) throws RocksDBException;
|
||||
protected native void put(
|
||||
long handle, byte[] key, int keyLen,
|
||||
byte[] value, int valueLen) throws RocksDBException;
|
||||
@@ -363,5 +368,5 @@ public class RocksDB extends RocksObject {
|
||||
protected native long iterator0(long optHandle);
|
||||
private native void disposeInternal(long handle);
|
||||
|
||||
protected Filter filter_;
|
||||
protected Options options_;
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -20,41 +22,43 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* provided in constructor) reference has been disposed.
|
||||
*/
|
||||
public class StatisticsCollector {
|
||||
private final Statistics _statistics;
|
||||
private final ThreadPoolExecutor _threadPoolExecutor;
|
||||
private final List<StatsCollectorInput> _statsCollectorInputList;
|
||||
private final ExecutorService _executorService;
|
||||
private final int _statsCollectionInterval;
|
||||
private final StatisticsCollectorCallback _statsCallback;
|
||||
private volatile boolean _isRunning = true;
|
||||
|
||||
/**
|
||||
* Constructor for statistics collector.
|
||||
* @param statistics Reference of DB statistics.
|
||||
*
|
||||
* @param statsCollectorInputList List of statistics collector input.
|
||||
* @param statsCollectionIntervalInMilliSeconds Statistics collection time
|
||||
* period (specified in milliseconds)
|
||||
* @param statsCallback Reference of statistics callback interface.
|
||||
* period (specified in milliseconds).
|
||||
*/
|
||||
public StatisticsCollector(Statistics statistics,
|
||||
int statsCollectionIntervalInMilliSeconds,
|
||||
StatisticsCollectorCallback statsCallback) {
|
||||
_statistics = statistics;
|
||||
public StatisticsCollector(List<StatsCollectorInput> statsCollectorInputList,
|
||||
int statsCollectionIntervalInMilliSeconds) {
|
||||
_statsCollectorInputList = statsCollectorInputList;
|
||||
_statsCollectionInterval = statsCollectionIntervalInMilliSeconds;
|
||||
_statsCallback = statsCallback;
|
||||
|
||||
_threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(1));
|
||||
_executorService = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
_threadPoolExecutor.submit(collectStatistics());
|
||||
_executorService.submit(collectStatistics());
|
||||
}
|
||||
|
||||
public void shutDown() throws InterruptedException {
|
||||
/**
|
||||
* Shuts down statistics collector.
|
||||
*
|
||||
* @param shutdownTimeout Time in milli-seconds to wait for shutdown before
|
||||
* killing the collection process.
|
||||
*/
|
||||
public void shutDown(int shutdownTimeout) throws InterruptedException {
|
||||
_isRunning = false;
|
||||
|
||||
_threadPoolExecutor.shutdown();
|
||||
_executorService.shutdownNow();
|
||||
// Wait for collectStatistics runnable to finish so that disposal of
|
||||
// statistics does not cause any exceptions to be thrown.
|
||||
_threadPoolExecutor.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
|
||||
_executorService.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private Runnable collectStatistics() {
|
||||
@@ -64,24 +68,34 @@ public class StatisticsCollector {
|
||||
public void run() {
|
||||
while (_isRunning) {
|
||||
try {
|
||||
// Collect ticker data
|
||||
for(TickerType ticker : TickerType.values()) {
|
||||
long tickerValue = _statistics.getTickerCount(ticker);
|
||||
_statsCallback.tickerCallback(ticker, tickerValue);
|
||||
}
|
||||
if(Thread.currentThread().isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
for(StatsCollectorInput statsCollectorInput :
|
||||
_statsCollectorInputList) {
|
||||
Statistics statistics = statsCollectorInput.getStatistics();
|
||||
StatisticsCollectorCallback statsCallback =
|
||||
statsCollectorInput.getCallback();
|
||||
|
||||
// Collect ticker data
|
||||
for(TickerType ticker : TickerType.values()) {
|
||||
long tickerValue = statistics.getTickerCount(ticker);
|
||||
statsCallback.tickerCallback(ticker, tickerValue);
|
||||
}
|
||||
|
||||
// Collect histogram data
|
||||
for(HistogramType histogramType : HistogramType.values()) {
|
||||
HistogramData histogramData =
|
||||
_statistics.geHistogramData(histogramType);
|
||||
_statsCallback.histogramCallback(histogramType, histogramData);
|
||||
}
|
||||
// Collect histogram data
|
||||
for(HistogramType histogramType : HistogramType.values()) {
|
||||
HistogramData histogramData =
|
||||
statistics.geHistogramData(histogramType);
|
||||
statsCallback.histogramCallback(histogramType, histogramData);
|
||||
}
|
||||
|
||||
Thread.sleep(_statsCollectionInterval);
|
||||
Thread.sleep(_statsCollectionInterval);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Thread got interrupted!", e);
|
||||
break;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Error while calculating statistics", e);
|
||||
|
||||
@@ -12,7 +12,7 @@ package org.rocksdb;
|
||||
* StatisticsCollector doesn't make any guarantees about thread safety.
|
||||
* If the same reference of StatisticsCollectorCallback is passed to multiple
|
||||
* StatisticsCollector references, then its the responsibility of the
|
||||
* user to make StatisticsCollectorCallback' implementation thread-safe.
|
||||
* user to make StatisticsCollectorCallback's implementation thread-safe.
|
||||
*
|
||||
* @param tickerType
|
||||
* @param tickerCount
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* Contains all information necessary to collect statistics from one instance
|
||||
* of DB statistics.
|
||||
*/
|
||||
public class StatsCollectorInput {
|
||||
private final Statistics _statistics;
|
||||
private final StatisticsCollectorCallback _statsCallback;
|
||||
|
||||
/**
|
||||
* Constructor for StatsCollectorInput.
|
||||
*
|
||||
* @param statistics Reference of DB statistics.
|
||||
* @param statsCallback Reference of statistics callback interface.
|
||||
*/
|
||||
public StatsCollectorInput(Statistics statistics,
|
||||
StatisticsCollectorCallback statsCallback) {
|
||||
_statistics = statistics;
|
||||
_statsCallback = statsCallback;
|
||||
}
|
||||
|
||||
public Statistics getStatistics() {
|
||||
return _statistics;
|
||||
}
|
||||
|
||||
public StatisticsCollectorCallback getCallback() {
|
||||
return _statsCallback;
|
||||
}
|
||||
}
|
||||
@@ -534,6 +534,8 @@ public class DbBenchmark {
|
||||
(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(
|
||||
@@ -616,7 +618,6 @@ public class DbBenchmark {
|
||||
options.setCompressionLevel((Integer)flags_.get(Flag.compression_level));
|
||||
options.setMinLevelToCompress((Integer)flags_.get(Flag.min_level_to_compress));
|
||||
options.setHdfs((String)flags_.get(Flag.hdfs)); // env
|
||||
options.setCacheNumshardbits((Integer)flags_.get(Flag.cache_numshardbits));
|
||||
options.setStatistics((Boolean)flags_.get(Flag.statistics));
|
||||
options.setUniversalSizeRatio(
|
||||
(Integer)flags_.get(Flag.universal_size_ratio));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.rocksdb.test;
|
||||
|
||||
import java.util.Collections;
|
||||
import org.rocksdb.*;
|
||||
|
||||
public class StatisticsCollectorTest {
|
||||
@@ -21,8 +22,10 @@ public class StatisticsCollectorTest {
|
||||
RocksDB db = RocksDB.open(db_path);
|
||||
|
||||
StatsCallbackMock callback = new StatsCallbackMock();
|
||||
StatisticsCollector statsCollector = new StatisticsCollector(stats, 100,
|
||||
callback);
|
||||
StatsCollectorInput statsInput = new StatsCollectorInput(stats, callback);
|
||||
|
||||
StatisticsCollector statsCollector = new StatisticsCollector(
|
||||
Collections.singletonList(statsInput), 100);
|
||||
statsCollector.start();
|
||||
|
||||
Thread.sleep(1000);
|
||||
@@ -30,7 +33,7 @@ public class StatisticsCollectorTest {
|
||||
assert(callback.tickerCallbackCount > 0);
|
||||
assert(callback.histCallbackCount > 0);
|
||||
|
||||
statsCollector.shutDown();
|
||||
statsCollector.shutDown(1000);
|
||||
|
||||
db.close();
|
||||
opt.dispose();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "include/org_rocksdb_BackupableDB.h"
|
||||
#include "include/org_rocksdb_BackupableDBOptions.h"
|
||||
#include "rocksjni/portal.h"
|
||||
#include "utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
|
||||
@@ -956,6 +956,27 @@ jbyte Java_org_rocksdb_Options_compressionType(
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->compression;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: setCompactionStyle
|
||||
* Signature: (JB)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_setCompactionStyle(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jbyte compaction_style) {
|
||||
reinterpret_cast<rocksdb::Options*>(jhandle)->compaction_style =
|
||||
static_cast<rocksdb::CompactionStyle>(compaction_style);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: compactionStyle
|
||||
* Signature: (J)B
|
||||
*/
|
||||
jbyte Java_org_rocksdb_Options_compactionStyle(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
return reinterpret_cast<rocksdb::Options*>(jhandle)->compaction_style;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: wholeKeyFiltering
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <jni.h>
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "include/org_rocksdb_RestoreOptions.h"
|
||||
#include "include/org_rocksdb_RestoreBackupableDB.h"
|
||||
#include "rocksjni/portal.h"
|
||||
#include "utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreOptions
|
||||
* Method: newRestoreOptions
|
||||
|
||||
@@ -27,11 +27,15 @@
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_open(
|
||||
JNIEnv* env, jobject jdb, jlong jopt_handle,
|
||||
jlong jcache_size, jstring jdb_path) {
|
||||
jlong jcache_size, jint jnum_shardbits, jstring jdb_path) {
|
||||
auto opt = reinterpret_cast<rocksdb::Options*>(jopt_handle);
|
||||
if (jcache_size > 0) {
|
||||
opt->no_block_cache = false;
|
||||
opt->block_cache = rocksdb::NewLRUCache(jcache_size);
|
||||
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;
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
jlong Java_org_rocksdb_PlainTableConfig_newTableFactoryHandle(
|
||||
JNIEnv* env, jobject jobj, jint jkey_size, jint jbloom_bits_per_key,
|
||||
jdouble jhash_table_ratio, jint jindex_sparseness) {
|
||||
return reinterpret_cast<jlong>(rocksdb::NewPlainTableFactory(
|
||||
static_cast<uint32_t>(jkey_size),
|
||||
static_cast<int>(jbloom_bits_per_key),
|
||||
static_cast<double>(jhash_table_ratio),
|
||||
static_cast<size_t>(jindex_sparseness)));
|
||||
rocksdb::PlainTableOptions options = rocksdb::PlainTableOptions();
|
||||
options.user_key_len = jkey_size;
|
||||
options.bloom_bits_per_key = jbloom_bits_per_key;
|
||||
options.hash_table_ratio = jhash_table_ratio;
|
||||
options.index_sparseness = jindex_sparseness;
|
||||
return reinterpret_cast<jlong>(rocksdb::NewPlainTableFactory(options));
|
||||
}
|
||||
+40
-17
@@ -22,6 +22,7 @@
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -94,7 +95,7 @@ class Block::Iter : public Iterator {
|
||||
// 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
|
||||
std::string key_;
|
||||
IterKey key_;
|
||||
Slice value_;
|
||||
Status status_;
|
||||
BlockHashIndex* hash_index_;
|
||||
@@ -115,7 +116,7 @@ class Block::Iter : public Iterator {
|
||||
}
|
||||
|
||||
void SeekToRestartPoint(uint32_t index) {
|
||||
key_.clear();
|
||||
key_.Clear();
|
||||
restart_index_ = index;
|
||||
// current_ will be fixed by ParseNextKey();
|
||||
|
||||
@@ -143,7 +144,7 @@ class Block::Iter : public Iterator {
|
||||
virtual Status status() const { return status_; }
|
||||
virtual Slice key() const {
|
||||
assert(Valid());
|
||||
return key_;
|
||||
return key_.GetKey();
|
||||
}
|
||||
virtual Slice value() const {
|
||||
assert(Valid());
|
||||
@@ -193,7 +194,7 @@ class Block::Iter : public Iterator {
|
||||
// Linear search (within restart block) for first key >= target
|
||||
|
||||
while (true) {
|
||||
if (!ParseNextKey() || Compare(key_, target) >= 0) {
|
||||
if (!ParseNextKey() || Compare(key_.GetKey(), target) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -215,7 +216,7 @@ class Block::Iter : public Iterator {
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
status_ = Status::Corruption("bad entry in block");
|
||||
key_.clear();
|
||||
key_.Clear();
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
@@ -233,12 +234,11 @@ class Block::Iter : public Iterator {
|
||||
// Decode next entry
|
||||
uint32_t shared, non_shared, value_length;
|
||||
p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);
|
||||
if (p == nullptr || key_.size() < shared) {
|
||||
if (p == nullptr || key_.Size() < shared) {
|
||||
CorruptionError();
|
||||
return false;
|
||||
} else {
|
||||
key_.resize(shared);
|
||||
key_.append(p, non_shared);
|
||||
key_.TrimAppend(shared, p, non_shared);
|
||||
value_ = Slice(p + non_shared, value_length);
|
||||
while (restart_index_ + 1 < num_restarts_ &&
|
||||
GetRestartPoint(restart_index_ + 1) < current_) {
|
||||
@@ -284,26 +284,36 @@ class Block::Iter : public Iterator {
|
||||
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);
|
||||
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 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;
|
||||
uint32_t region_offset = GetRestartPoint(block_ids[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();
|
||||
|
||||
int cmp = CompareBlockKey(block_ids[mid], target);
|
||||
if (!status_.ok()) {
|
||||
return false;
|
||||
}
|
||||
Slice mid_key(key_ptr, non_shared);
|
||||
int cmp = Compare(mid_key, target);
|
||||
if (cmp < 0) {
|
||||
// Key at "target" is larger than "mid". Therefore all
|
||||
// blocks before or at "mid" are uninteresting.
|
||||
@@ -318,6 +328,19 @@ class Block::Iter : public Iterator {
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
|
||||
@@ -612,8 +612,8 @@ static void DeleteCachedBlock(const Slice& key, void* value) {
|
||||
// Make a copy of the block contents and insert into compressed block cache
|
||||
//
|
||||
Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents,
|
||||
const CompressionType type,
|
||||
const BlockHandle* handle) {
|
||||
const CompressionType type,
|
||||
const BlockHandle* handle) {
|
||||
Rep* r = rep_;
|
||||
Cache* block_cache_compressed = r->options.block_cache_compressed.get();
|
||||
|
||||
@@ -622,8 +622,9 @@ Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents,
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
size_t size = block_contents.size();
|
||||
|
||||
char* ubuf = new char[size]; // make a new copy
|
||||
char* ubuf = new char[size + 1]; // make a new copy
|
||||
memcpy(ubuf, block_contents.data(), size);
|
||||
ubuf[size] = type;
|
||||
|
||||
BlockContents results;
|
||||
Slice sl(ubuf, size);
|
||||
|
||||
@@ -870,10 +870,9 @@ Iterator* BlockBasedTable::NewDataBlockIterator(Rep* rep,
|
||||
statistics, ro, &block);
|
||||
|
||||
if (block.value == nullptr && !no_io && ro.fill_cache) {
|
||||
Histograms histogram = READ_BLOCK_GET_MICROS;
|
||||
Block* raw_block = nullptr;
|
||||
{
|
||||
StopWatch sw(rep->options.env, statistics, histogram);
|
||||
StopWatch sw(rep->options.env, statistics, READ_BLOCK_GET_MICROS);
|
||||
s = ReadBlockFromFile(rep->file.get(), rep->footer, ro, handle,
|
||||
&raw_block, rep->options.env,
|
||||
block_cache_compressed == nullptr);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 "table/bloom_block.h"
|
||||
|
||||
#include <string>
|
||||
#include "rocksdb/slice.h"
|
||||
#include "util/dynamic_bloom.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
void BloomBlockBuilder::AddKeysHashes(const std::vector<uint32_t> keys_hashes) {
|
||||
for (auto hash : keys_hashes) {
|
||||
bloom_.AddHash(hash);
|
||||
}
|
||||
}
|
||||
|
||||
Slice BloomBlockBuilder::Finish() { return bloom_.GetRawData(); }
|
||||
|
||||
const std::string BloomBlockBuilder::kBloomBlock = "kBloomBlock";
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "util/dynamic_bloom.h"
|
||||
|
||||
namespace rocksdb {
|
||||
class Logger;
|
||||
|
||||
class BloomBlockBuilder {
|
||||
public:
|
||||
static const std::string kBloomBlock;
|
||||
|
||||
explicit BloomBlockBuilder(uint32_t num_probes = 6)
|
||||
: bloom_(num_probes, nullptr) {}
|
||||
|
||||
void SetTotalBits(Arena* arena, uint32_t total_bits, uint32_t locality,
|
||||
size_t huge_page_tlb_size, Logger* logger) {
|
||||
bloom_.SetTotalBits(arena, total_bits, locality, huge_page_tlb_size,
|
||||
logger);
|
||||
}
|
||||
|
||||
uint32_t GetNumBlocks() const { return bloom_.GetNumBlocks(); }
|
||||
|
||||
void AddKeysHashes(const std::vector<uint32_t> keys_hashes);
|
||||
|
||||
Slice Finish();
|
||||
|
||||
private:
|
||||
DynamicBloom bloom_;
|
||||
};
|
||||
|
||||
}; // namespace rocksdb
|
||||
@@ -0,0 +1,354 @@
|
||||
// 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_builder.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/format.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace rocksdb {
|
||||
const std::string CuckooTablePropertyNames::kEmptyKey =
|
||||
"rocksdb.cuckoo.bucket.empty.key";
|
||||
const std::string CuckooTablePropertyNames::kNumHashTable =
|
||||
"rocksdb.cuckoo.hash.num";
|
||||
const std::string CuckooTablePropertyNames::kMaxNumBuckets =
|
||||
"rocksdb.cuckoo.bucket.maxnum";
|
||||
const std::string CuckooTablePropertyNames::kValueLength =
|
||||
"rocksdb.cuckoo.value.length";
|
||||
const std::string CuckooTablePropertyNames::kIsLastLevel =
|
||||
"rocksdb.cuckoo.file.islastlevel";
|
||||
|
||||
// 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),
|
||||
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_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) {
|
||||
properties_.num_entries = 0;
|
||||
// Data is in a huge block.
|
||||
properties_.num_data_blocks = 1;
|
||||
properties_.index_size = 0;
|
||||
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.");
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (is_last_level_file_) {
|
||||
buckets_[bucket_id].key.assign(user_key.data(), user_key.size());
|
||||
} else {
|
||||
buckets_[bucket_id].key.assign(key.data(), key.size());
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status CuckooTableBuilder::status() const { return status_; }
|
||||
|
||||
Status CuckooTableBuilder::Finish() {
|
||||
assert(!closed_);
|
||||
closed_ = true;
|
||||
|
||||
if (unused_user_key_.empty()) {
|
||||
if (prev_key_.empty()) {
|
||||
return Status::Corruption("Unable to find unused key");
|
||||
}
|
||||
// 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;
|
||||
while (curr_pos >= 0) {
|
||||
++new_user_key[curr_pos];
|
||||
if (new_user_key > prev_key_) {
|
||||
unused_user_key_ = new_user_key;
|
||||
break;
|
||||
}
|
||||
--curr_pos;
|
||||
}
|
||||
if (curr_pos < 0) {
|
||||
return Status::Corruption("Unable to find unused key");
|
||||
}
|
||||
}
|
||||
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_.fixed_key_len = unused_bucket.size();
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kValueLength].assign(
|
||||
reinterpret_cast<const char*>(&value_length_), sizeof(value_length_));
|
||||
|
||||
unused_bucket.resize(bucket_size_, 'a');
|
||||
// Write the table.
|
||||
uint32_t num_added = 0;
|
||||
for (auto& bucket : buckets_) {
|
||||
Status s;
|
||||
if (bucket.is_empty) {
|
||||
s = file_->Append(Slice(unused_bucket));
|
||||
} else {
|
||||
++num_added;
|
||||
s = file_->Append(Slice(bucket.key));
|
||||
if (s.ok()) {
|
||||
s = file_->Append(Slice(bucket.value));
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
assert(num_added == NumEntries());
|
||||
|
||||
uint64_t offset = buckets_.size() * bucket_size_;
|
||||
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_));
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kMaxNumBuckets].assign(
|
||||
reinterpret_cast<const char*>(&max_num_buckets_),
|
||||
sizeof(max_num_buckets_));
|
||||
properties_.user_collected_properties[
|
||||
CuckooTablePropertyNames::kIsLastLevel].assign(
|
||||
reinterpret_cast<const char*>(&is_last_level_file_),
|
||||
sizeof(is_last_level_file_));
|
||||
|
||||
// Write meta blocks.
|
||||
MetaIndexBuilder meta_index_builder;
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
|
||||
property_block_builder.AddTableProperty(properties_);
|
||||
property_block_builder.Add(properties_.user_collected_properties);
|
||||
Slice property_block = property_block_builder.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);
|
||||
offset += property_block.size();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
meta_index_builder.Add(kPropertiesBlock, property_block_handle);
|
||||
Slice meta_index_block = meta_index_builder.Finish();
|
||||
|
||||
BlockHandle meta_index_block_handle;
|
||||
meta_index_block_handle.set_offset(offset);
|
||||
meta_index_block_handle.set_size(meta_index_block.size());
|
||||
s = file_->Append(meta_index_block);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Footer footer(kCuckooTableMagicNumber);
|
||||
footer.set_metaindex_handle(meta_index_block_handle);
|
||||
footer.set_index_handle(BlockHandle::NullBlockHandle());
|
||||
std::string footer_encoding;
|
||||
footer.EncodeTo(&footer_encoding);
|
||||
s = file_->Append(footer_encoding);
|
||||
return s;
|
||||
}
|
||||
|
||||
void CuckooTableBuilder::Abandon() {
|
||||
assert(!closed_);
|
||||
closed_ = true;
|
||||
}
|
||||
|
||||
uint64_t CuckooTableBuilder::NumEntries() const {
|
||||
return properties_.num_entries;
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
}
|
||||
|
||||
bool CuckooTableBuilder::MakeSpaceForKey(const Slice& key,
|
||||
uint64_t *bucket_id, autovector<uint64_t> hash_vals) {
|
||||
struct CuckooNode {
|
||||
uint64_t bucket_id;
|
||||
uint32_t depth;
|
||||
uint32_t parent_pos;
|
||||
CuckooNode(uint64_t bucket_id, uint32_t depth, int parent_pos)
|
||||
: bucket_id(bucket_id), depth(depth), parent_pos(parent_pos) {}
|
||||
};
|
||||
// This is BFS search tree that is stored simply as a vector.
|
||||
// Each node stores the index of parent node in the vector.
|
||||
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.
|
||||
// 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) {
|
||||
uint64_t bucket_id = hash_vals[hash_cnt];
|
||||
buckets_[bucket_id].make_space_for_key_call_id =
|
||||
make_space_for_key_call_id_;
|
||||
tree.push_back(CuckooNode(bucket_id, 0, 0));
|
||||
}
|
||||
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_) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
++curr_pos;
|
||||
}
|
||||
|
||||
if (null_found) {
|
||||
uint32_t bucket_to_replace_pos = tree.size()-1;
|
||||
while (bucket_to_replace_pos >= 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
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));
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~CuckooTableBuilder();
|
||||
|
||||
// Add key,value to the table being constructed.
|
||||
// REQUIRES: key is after any previously added key according to comparator.
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
void Add(const Slice& key, const Slice& value) override;
|
||||
|
||||
// Return non-ok iff some error has been detected.
|
||||
Status status() const override;
|
||||
|
||||
// Finish building the table. Stops using the file passed to the
|
||||
// constructor after this function returns.
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
Status Finish() override;
|
||||
|
||||
// Indicate that the contents of this builder should be abandoned. Stops
|
||||
// using the file passed to the constructor after this function returns.
|
||||
// If the caller is not going to call Finish(), it must call Abandon()
|
||||
// before destroying this builder.
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
void Abandon() override;
|
||||
|
||||
// Number of calls to Add() so far.
|
||||
uint64_t NumEntries() const override;
|
||||
|
||||
// Size of the file generated so far. If invoked after a successful
|
||||
// Finish() call, returns the size of the final generated file.
|
||||
uint64_t FileSize() const override;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
bool MakeSpaceForKey(const Slice& key, uint64_t* bucket_id,
|
||||
autovector<uint64_t> hash_vals);
|
||||
|
||||
uint32_t num_hash_table_;
|
||||
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 uint32_t max_search_depth_;
|
||||
const bool is_last_level_file_;
|
||||
Status status_;
|
||||
std::vector<CuckooBucket> buckets_;
|
||||
TableProperties properties_;
|
||||
uint64_t make_space_for_key_call_id_;
|
||||
uint64_t (*GetSliceHash)(const Slice& s, uint32_t index,
|
||||
uint64_t max_num_buckets);
|
||||
std::string unused_user_key_ = "";
|
||||
std::string prev_key_;
|
||||
|
||||
bool closed_ = false; // Either Finish() or Abandon() has been called.
|
||||
|
||||
// No copying allowed
|
||||
CuckooTableBuilder(const CuckooTableBuilder&) = delete;
|
||||
void operator=(const CuckooTableBuilder&) = delete;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,491 @@
|
||||
// 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 <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#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];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CuckooBuilderTest {
|
||||
public:
|
||||
CuckooBuilderTest() {
|
||||
env_ = Env::Default();
|
||||
}
|
||||
|
||||
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) {
|
||||
// Read file
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &read_file, env_options_));
|
||||
uint64_t read_file_size;
|
||||
ASSERT_OK(env_->GetFileSize(fname, &read_file_size));
|
||||
|
||||
// Assert Table Properties.
|
||||
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);
|
||||
|
||||
// Check unused bucket.
|
||||
std::string unused_key = props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kEmptyKey];
|
||||
ASSERT_EQ(expected_unused_bucket.substr(0, key_length), 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 =
|
||||
*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 =
|
||||
*reinterpret_cast<const uint32_t*>(props->user_collected_properties[
|
||||
CuckooTablePropertyNames::kNumHashTable].data());
|
||||
ASSERT_EQ(expected_num_hash_fun, num_hash_fun_found);
|
||||
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);
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
const 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;
|
||||
};
|
||||
|
||||
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');
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/BasicTest_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());
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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";
|
||||
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);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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";
|
||||
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);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, 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);
|
||||
}
|
||||
ikey_length = keys[0].size();
|
||||
value_length = values[0].size();
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWithTooManyCollisions_writable";
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
// 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;
|
||||
}
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/WithCollisionPath_writable_file";
|
||||
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]);
|
||||
}
|
||||
ASSERT_OK(cuckoo_builder.Finish());
|
||||
writable_file->Close();
|
||||
CheckFileContents(expected_file_data, expected_unused_bucket,
|
||||
expected_max_buckets, expected_num_hash_fun, false);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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";
|
||||
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);
|
||||
}
|
||||
cuckoo_builder.Add(Slice(keys.back()), Slice(values.back()));
|
||||
ASSERT_TRUE(cuckoo_builder.status().IsCorruption());
|
||||
cuckoo_builder.Abandon();
|
||||
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);
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
fname = test::TmpDir() + "/FailWhenTabelIsFull_writable";
|
||||
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();
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); }
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#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;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
|
||||
#include <string>
|
||||
#include "table/meta_blocks.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
CuckooTableReader::CuckooTableReader(
|
||||
const Options& options,
|
||||
std::unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size,
|
||||
uint64_t (*GetSliceHashPtr)(const Slice&, uint32_t, uint64_t))
|
||||
: file_(std::move(file)),
|
||||
file_size_(file_size),
|
||||
GetSliceHash(GetSliceHashPtr) {
|
||||
if (!options.allow_mmap_reads) {
|
||||
status_ = Status::InvalidArgument("File is not mmaped");
|
||||
}
|
||||
TableProperties* props = nullptr;
|
||||
status_ = ReadTableProperties(file_.get(), file_size, kCuckooTableMagicNumber,
|
||||
options.env, options.info_log.get(), &props);
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
table_props_.reset(props);
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto hash_funs = user_props.find(CuckooTablePropertyNames::kNumHashTable);
|
||||
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());
|
||||
auto unused_key = user_props.find(CuckooTablePropertyNames::kEmptyKey);
|
||||
if (unused_key == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Empty bucket value not found");
|
||||
return;
|
||||
}
|
||||
unused_key_ = unused_key->second;
|
||||
|
||||
key_length_ = props->fixed_key_len;
|
||||
auto value_length = user_props.find(CuckooTablePropertyNames::kValueLength);
|
||||
if (value_length == user_props.end()) {
|
||||
status_ = Status::InvalidArgument("Value length not found");
|
||||
return;
|
||||
}
|
||||
value_length_ = *reinterpret_cast<const uint32_t*>(
|
||||
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");
|
||||
return;
|
||||
}
|
||||
num_buckets_ = *reinterpret_cast<const uint64_t*>(num_buckets->second.data());
|
||||
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());
|
||||
status_ = file_->Read(0, file_size, &file_data_, nullptr);
|
||||
}
|
||||
|
||||
Status CuckooTableReader::Get(
|
||||
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)) {
|
||||
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_;
|
||||
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();
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Iterator* CuckooTableReader::NewIterator(const ReadOptions&, Arena* arena) {
|
||||
// TODO(rbs): Implement this as this will be used in compaction.
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
#endif
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/table_reader.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class TableReader;
|
||||
|
||||
class CuckooTableReader: public TableReader {
|
||||
public:
|
||||
CuckooTableReader(
|
||||
const Options& options,
|
||||
std::unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size,
|
||||
uint64_t (*GetSliceHash)(const Slice&, uint32_t, uint64_t));
|
||||
~CuckooTableReader() {}
|
||||
|
||||
std::shared_ptr<const TableProperties> GetTableProperties() const {
|
||||
return table_props_;
|
||||
}
|
||||
|
||||
Status status() const { return status_; }
|
||||
|
||||
Status Get(
|
||||
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);
|
||||
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr);
|
||||
|
||||
// Following methods are not implemented for Cuckoo Table Reader
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) { return 0; }
|
||||
void SetupForCompaction() {}
|
||||
void Prepare(const Slice& target) {}
|
||||
// End of methods not implemented.
|
||||
|
||||
private:
|
||||
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_;
|
||||
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,
|
||||
uint64_t max_num_buckets);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,347 @@
|
||||
// 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 GFLAGS
|
||||
#include <cstdio>
|
||||
int main() {
|
||||
fprintf(stderr, "Please install gflags to run this test\n");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/cuckoo_table_builder.h"
|
||||
#include "table/cuckoo_table_reader.h"
|
||||
#include "table/cuckoo_table_factory.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
using GFLAGS::ParseCommandLineFlags;
|
||||
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.");
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
extern const uint64_t kCuckooTableMagicNumber;
|
||||
|
||||
namespace {
|
||||
const uint32_t kNumHashFunc = 10;
|
||||
// Methods, variables related to Hash functions.
|
||||
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];
|
||||
}
|
||||
|
||||
// Methods, variables for checking key and values read.
|
||||
struct ValuesToAssert {
|
||||
ValuesToAssert(const std::string& key, const Slice& value)
|
||||
: expected_user_key(key),
|
||||
expected_value(value),
|
||||
call_count(0) {}
|
||||
std::string expected_user_key;
|
||||
Slice expected_value;
|
||||
int call_count;
|
||||
};
|
||||
|
||||
bool AssertValues(void* assert_obj,
|
||||
const ParsedInternalKey& k, const Slice& v) {
|
||||
ValuesToAssert *ptr = reinterpret_cast<ValuesToAssert*>(assert_obj);
|
||||
ASSERT_EQ(ptr->expected_value.ToString(), v.ToString());
|
||||
ASSERT_EQ(ptr->expected_user_key, k.user_key.ToString());
|
||||
++ptr->call_count;
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CuckooReaderTest {
|
||||
public:
|
||||
CuckooReaderTest() {
|
||||
options.allow_mmap_reads = true;
|
||||
env = options.env;
|
||||
env_options = EnvOptions(options);
|
||||
}
|
||||
|
||||
void SetUp(int num_items) {
|
||||
this->num_items = num_items;
|
||||
hash_map.clear();
|
||||
keys.clear();
|
||||
keys.resize(num_items);
|
||||
user_keys.clear();
|
||||
user_keys.resize(num_items);
|
||||
values.clear();
|
||||
values.resize(num_items);
|
||||
}
|
||||
|
||||
void CreateCuckooFile(bool is_last_level) {
|
||||
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);
|
||||
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_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;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
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(
|
||||
ReadOptions(), Slice(keys[i]), &v, AssertValues, nullptr));
|
||||
ASSERT_EQ(1, v.call_count);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> user_keys;
|
||||
std::vector<std::string> values;
|
||||
uint32_t num_items;
|
||||
std::string fname;
|
||||
uint64_t file_size;
|
||||
Options options;
|
||||
Env* env;
|
||||
EnvOptions env_options;
|
||||
};
|
||||
|
||||
TEST(CuckooReaderTest, WhenKeyExists) {
|
||||
SetUp(10);
|
||||
fname = test::TmpDir() + "/CuckooReader_WhenKeyExists";
|
||||
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], i * kNumHashFunc, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFile(false);
|
||||
CheckReader();
|
||||
// Last level file.
|
||||
CreateCuckooFile(true);
|
||||
CheckReader();
|
||||
// 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();
|
||||
// Last level file.
|
||||
CreateCuckooFile(true);
|
||||
CheckReader();
|
||||
}
|
||||
|
||||
TEST(CuckooReaderTest, WhenKeyNotFound) {
|
||||
// Add keys with colliding hash values.
|
||||
SetUp(kNumHashFunc / 2);
|
||||
fname = test::TmpDir() + "/CuckooReader_WhenKeyNotFound";
|
||||
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, kNumHashFunc);
|
||||
}
|
||||
CreateCuckooFile(false);
|
||||
CheckReader();
|
||||
unique_ptr<RandomAccessFile> read_file;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
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_key;
|
||||
AddHashLookups(not_found_user_key, 0, kNumHashFunc);
|
||||
ParsedInternalKey ikey(not_found_user_key, 1000, kTypeValue);
|
||||
AppendInternalKey(¬_found_key, ikey);
|
||||
ValuesToAssert v("", "");
|
||||
ASSERT_OK(reader.Get(
|
||||
ReadOptions(), Slice(not_found_key), &v, AssertValues, nullptr));
|
||||
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;
|
||||
AddHashLookups(not_found_user_key2, kNumHashFunc, kNumHashFunc);
|
||||
ParsedInternalKey ikey2(not_found_user_key2, 1000, kTypeValue);
|
||||
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:";
|
||||
// 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);
|
||||
ASSERT_OK(reader.Get(
|
||||
ReadOptions(), Slice(unused_key), &v, AssertValues, nullptr));
|
||||
ASSERT_EQ(0, v.call_count);
|
||||
ASSERT_OK(reader.status());
|
||||
}
|
||||
|
||||
// Performance tests
|
||||
namespace {
|
||||
bool DoNothing(void* arg, const ParsedInternalKey& k, const Slice& v) {
|
||||
// Deliberately empty.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckValue(void* cnt_ptr, const ParsedInternalKey& k, const Slice& v) {
|
||||
++*reinterpret_cast<int*>(cnt_ptr);
|
||||
std::string expected_value;
|
||||
AppendInternalKey(&expected_value, k);
|
||||
ASSERT_EQ(0, v.compare(Slice(&expected_value[0], v.size())));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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";
|
||||
|
||||
uint64_t predicted_file_size =
|
||||
num * (key_length + value_length) / hash_ratio + 1024;
|
||||
|
||||
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);
|
||||
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));
|
||||
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;
|
||||
ASSERT_OK(env->NewRandomAccessFile(fname, &read_file, env_options));
|
||||
|
||||
CuckooTableReader reader(
|
||||
options,
|
||||
std::move(read_file),
|
||||
file_size,
|
||||
GetSliceMurmurHash);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
fprintf(stderr, "Time taken per op is %.3fus\n",
|
||||
(env->NowMicros() - time_now)*1.0/num_reads);
|
||||
}
|
||||
} // namespace.
|
||||
|
||||
TEST(CuckooReaderTest, Performance) {
|
||||
// In all these tests, num_reads = 10*num_items.
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
rocksdb::test::RunAllTests();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // GFLAGS.
|
||||
@@ -273,4 +273,72 @@ Status FindMetaBlock(Iterator* meta_index_iter,
|
||||
}
|
||||
}
|
||||
|
||||
Status FindMetaBlock(RandomAccessFile* file, uint64_t file_size,
|
||||
uint64_t table_magic_number, Env* env,
|
||||
const std::string& meta_block_name,
|
||||
BlockHandle* block_handle) {
|
||||
Footer footer(table_magic_number);
|
||||
auto s = ReadFooterFromFile(file, file_size, &footer);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
auto metaindex_handle = footer.metaindex_handle();
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, footer, read_options, metaindex_handle,
|
||||
&metaindex_contents, env, false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
Block metaindex_block(metaindex_contents);
|
||||
|
||||
std::unique_ptr<Iterator> meta_iter;
|
||||
meta_iter.reset(metaindex_block.NewIterator(BytewiseComparator()));
|
||||
|
||||
return FindMetaBlock(meta_iter.get(), meta_block_name, block_handle);
|
||||
}
|
||||
|
||||
Status ReadMetaBlock(RandomAccessFile* file, uint64_t file_size,
|
||||
uint64_t table_magic_number, Env* env,
|
||||
const std::string& meta_block_name,
|
||||
BlockContents* contents) {
|
||||
Footer footer(table_magic_number);
|
||||
auto s = ReadFooterFromFile(file, file_size, &footer);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Reading metaindex block
|
||||
auto metaindex_handle = footer.metaindex_handle();
|
||||
BlockContents metaindex_contents;
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
s = ReadBlockContents(file, footer, read_options, metaindex_handle,
|
||||
&metaindex_contents, env, false);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Finding metablock
|
||||
Block metaindex_block(metaindex_contents);
|
||||
|
||||
std::unique_ptr<Iterator> meta_iter;
|
||||
meta_iter.reset(metaindex_block.NewIterator(BytewiseComparator()));
|
||||
|
||||
BlockHandle block_handle;
|
||||
s = FindMetaBlock(meta_iter.get(), meta_block_name, &block_handle);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Reading metablock
|
||||
s = ReadBlockContents(file, footer, read_options, block_handle, contents, env,
|
||||
false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/format.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -128,4 +129,18 @@ Status FindMetaBlock(Iterator* meta_index_iter,
|
||||
const std::string& meta_block_name,
|
||||
BlockHandle* block_handle);
|
||||
|
||||
// Find the meta block
|
||||
Status FindMetaBlock(RandomAccessFile* file, uint64_t file_size,
|
||||
uint64_t table_magic_number, Env* env,
|
||||
const std::string& meta_block_name,
|
||||
BlockHandle* block_handle);
|
||||
|
||||
// Read the specified meta block with name meta_block_name
|
||||
// from `file` and initialize `contents` with contents of this block.
|
||||
// Return Status::OK in case of success.
|
||||
Status ReadMetaBlock(RandomAccessFile* file, uint64_t file_size,
|
||||
uint64_t table_magic_number, Env* env,
|
||||
const std::string& meta_block_name,
|
||||
BlockContents* contents);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/plain_table_builder.h"
|
||||
|
||||
#include <string>
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
|
||||
@@ -17,6 +18,8 @@
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/bloom_block.h"
|
||||
#include "table/plain_table_index.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/meta_blocks.h"
|
||||
@@ -54,20 +57,36 @@ Status WriteBlock(
|
||||
extern const uint64_t kPlainTableMagicNumber = 0x8242229663bf9564ull;
|
||||
extern const uint64_t kLegacyPlainTableMagicNumber = 0x4f3418eb7a8f13b8ull;
|
||||
|
||||
PlainTableBuilder::PlainTableBuilder(const Options& options, WritableFile* file,
|
||||
uint32_t user_key_len,
|
||||
EncodingType encoding_type,
|
||||
size_t index_sparseness)
|
||||
PlainTableBuilder::PlainTableBuilder(
|
||||
const Options& options, WritableFile* file, uint32_t user_key_len,
|
||||
EncodingType encoding_type, size_t index_sparseness,
|
||||
uint32_t bloom_bits_per_key, uint32_t num_probes, size_t huge_page_tlb_size,
|
||||
double hash_table_ratio, bool store_index_in_file)
|
||||
: options_(options),
|
||||
bloom_block_(num_probes),
|
||||
file_(file),
|
||||
bloom_bits_per_key_(bloom_bits_per_key),
|
||||
huge_page_tlb_size_(huge_page_tlb_size),
|
||||
encoder_(encoding_type, user_key_len, options.prefix_extractor.get(),
|
||||
index_sparseness) {
|
||||
index_sparseness),
|
||||
store_index_in_file_(store_index_in_file),
|
||||
prefix_extractor_(options.prefix_extractor.get()) {
|
||||
// Build index block and save it in the file if hash_table_ratio > 0
|
||||
if (store_index_in_file_) {
|
||||
assert(hash_table_ratio > 0 || IsTotalOrderMode());
|
||||
index_builder_.reset(
|
||||
new PlainTableIndexBuilder(&arena_, options, index_sparseness,
|
||||
hash_table_ratio, huge_page_tlb_size_));
|
||||
assert(bloom_bits_per_key_ > 0);
|
||||
properties_.user_collected_properties
|
||||
[PlainTablePropertyNames::kBloomVersion] = "1"; // For future use
|
||||
}
|
||||
|
||||
properties_.fixed_key_len = user_key_len;
|
||||
|
||||
// for plain table, we put all the data in a big chuck.
|
||||
properties_.num_data_blocks = 1;
|
||||
// emphasize that currently plain table doesn't have persistent index or
|
||||
// filter block.
|
||||
// Fill it later if store_index_in_file_ == true
|
||||
properties_.index_size = 0;
|
||||
properties_.filter_size = 0;
|
||||
// To support roll-back to previous version, now still use version 0 for
|
||||
@@ -100,9 +119,28 @@ void PlainTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
char meta_bytes_buf[6];
|
||||
size_t meta_bytes_buf_size = 0;
|
||||
|
||||
ParsedInternalKey internal_key;
|
||||
ParseInternalKey(key, &internal_key);
|
||||
|
||||
// Store key hash
|
||||
if (store_index_in_file_) {
|
||||
if (options_.prefix_extractor.get() == nullptr) {
|
||||
keys_or_prefixes_hashes_.push_back(GetSliceHash(internal_key.user_key));
|
||||
} else {
|
||||
Slice prefix =
|
||||
options_.prefix_extractor->Transform(internal_key.user_key);
|
||||
keys_or_prefixes_hashes_.push_back(GetSliceHash(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
// Write value
|
||||
auto prev_offset = offset_;
|
||||
// Write out the key
|
||||
encoder_.AppendKey(key, file_, &offset_, meta_bytes_buf,
|
||||
&meta_bytes_buf_size);
|
||||
if (SaveIndexInFile()) {
|
||||
index_builder_->AddKeyPrefix(GetPrefix(internal_key), prev_offset);
|
||||
}
|
||||
|
||||
// Write value length
|
||||
int value_size = value.size();
|
||||
@@ -133,12 +171,51 @@ Status PlainTableBuilder::Finish() {
|
||||
|
||||
properties_.data_size = offset_;
|
||||
|
||||
// Write the following blocks
|
||||
// 1. [meta block: properties]
|
||||
// 2. [metaindex block]
|
||||
// 3. [footer]
|
||||
// Write the following blocks
|
||||
// 1. [meta block: bloom] - optional
|
||||
// 2. [meta block: index] - optional
|
||||
// 3. [meta block: properties]
|
||||
// 4. [metaindex block]
|
||||
// 5. [footer]
|
||||
|
||||
MetaIndexBuilder meta_index_builer;
|
||||
|
||||
if (store_index_in_file_ && (properties_.num_entries > 0)) {
|
||||
bloom_block_.SetTotalBits(
|
||||
&arena_, properties_.num_entries * bloom_bits_per_key_,
|
||||
options_.bloom_locality, huge_page_tlb_size_, options_.info_log.get());
|
||||
|
||||
PutVarint32(&properties_.user_collected_properties
|
||||
[PlainTablePropertyNames::kNumBloomBlocks],
|
||||
bloom_block_.GetNumBlocks());
|
||||
|
||||
bloom_block_.AddKeysHashes(keys_or_prefixes_hashes_);
|
||||
BlockHandle bloom_block_handle;
|
||||
auto finish_result = bloom_block_.Finish();
|
||||
|
||||
properties_.filter_size = finish_result.size();
|
||||
auto s = WriteBlock(finish_result, file_, &offset_, &bloom_block_handle);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
BlockHandle index_block_handle;
|
||||
finish_result = index_builder_->Finish();
|
||||
|
||||
properties_.index_size = finish_result.size();
|
||||
s = WriteBlock(finish_result, file_, &offset_, &index_block_handle);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
meta_index_builer.Add(BloomBlockBuilder::kBloomBlock, bloom_block_handle);
|
||||
meta_index_builer.Add(PlainTableIndexBuilder::kPlainTableIndexBlock,
|
||||
index_block_handle);
|
||||
}
|
||||
|
||||
// Calculate bloom block size and index block size
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
// -- Add basic properties
|
||||
property_block_builder.AddTableProperty(properties_);
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "table/plain_table_key_coding.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/bloom_block.h"
|
||||
#include "table/plain_table_index.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -30,7 +32,10 @@ class PlainTableBuilder: public TableBuilder {
|
||||
// that the caller does not know which level the output file will reside.
|
||||
PlainTableBuilder(const Options& options, WritableFile* file,
|
||||
uint32_t user_key_size, EncodingType encoding_type,
|
||||
size_t index_sparseness);
|
||||
size_t index_sparseness, uint32_t bloom_bits_per_key,
|
||||
uint32_t num_probes = 6, size_t huge_page_tlb_size = 0,
|
||||
double hash_table_ratio = 0,
|
||||
bool store_index_in_file = false);
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~PlainTableBuilder();
|
||||
@@ -62,18 +67,59 @@ class PlainTableBuilder: public TableBuilder {
|
||||
// Finish() call, returns the size of the final generated file.
|
||||
uint64_t FileSize() const override;
|
||||
|
||||
bool SaveIndexInFile() const { return store_index_in_file_; }
|
||||
|
||||
private:
|
||||
Arena arena_;
|
||||
Options options_;
|
||||
std::vector<std::unique_ptr<TablePropertiesCollector>>
|
||||
table_properties_collectors_;
|
||||
|
||||
BloomBlockBuilder bloom_block_;
|
||||
std::unique_ptr<PlainTableIndexBuilder> index_builder_;
|
||||
|
||||
WritableFile* file_;
|
||||
uint64_t offset_ = 0;
|
||||
uint32_t bloom_bits_per_key_;
|
||||
uint32_t huge_page_tlb_size_;
|
||||
Status status_;
|
||||
TableProperties properties_;
|
||||
PlainTableKeyEncoder encoder_;
|
||||
|
||||
bool store_index_in_file_;
|
||||
|
||||
std::vector<uint32_t> keys_or_prefixes_hashes_;
|
||||
bool closed_ = false; // Either Finish() or Abandon() has been called.
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
|
||||
Slice GetPrefix(const Slice& target) const {
|
||||
assert(target.size() >= 8); // target is internal key
|
||||
return GetPrefixFromUserKey(GetUserKey(target));
|
||||
}
|
||||
|
||||
Slice GetPrefix(const ParsedInternalKey& target) const {
|
||||
return GetPrefixFromUserKey(target.user_key);
|
||||
}
|
||||
|
||||
Slice GetUserKey(const Slice& key) const {
|
||||
return Slice(key.data(), key.size() - 8);
|
||||
}
|
||||
|
||||
Slice GetPrefixFromUserKey(const Slice& user_key) const {
|
||||
if (!IsTotalOrderMode()) {
|
||||
return prefix_extractor_->Transform(user_key);
|
||||
} else {
|
||||
// Use empty slice as prefix if prefix_extractor is not set.
|
||||
// In that case,
|
||||
// it falls back to pure binary search and
|
||||
// total iterator seek is supported.
|
||||
return Slice();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTotalOrderMode() const { return (prefix_extractor_ == nullptr); }
|
||||
|
||||
// No copying allowed
|
||||
PlainTableBuilder(const PlainTableBuilder&) = delete;
|
||||
void operator=(const PlainTableBuilder&) = delete;
|
||||
|
||||
@@ -30,16 +30,13 @@ TableBuilder* PlainTableFactory::NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const {
|
||||
return new PlainTableBuilder(options, file, user_key_len_, encoding_type_,
|
||||
index_sparseness_);
|
||||
index_sparseness_, bloom_bits_per_key_, 6,
|
||||
huge_page_tlb_size_, hash_table_ratio_,
|
||||
store_index_in_file_);
|
||||
}
|
||||
|
||||
extern TableFactory* NewPlainTableFactory(
|
||||
uint32_t user_key_len, int bloom_bits_per_key, double hash_table_ratio,
|
||||
size_t index_sparseness, size_t huge_page_tlb_size,
|
||||
EncodingType encoding_type, bool full_scan_mode) {
|
||||
return new PlainTableFactory(
|
||||
user_key_len, bloom_bits_per_key, hash_table_ratio, index_sparseness,
|
||||
huge_page_tlb_size, encoding_type, full_scan_mode);
|
||||
extern TableFactory* NewPlainTableFactory(const PlainTableOptions& options) {
|
||||
return new PlainTableFactory(options);
|
||||
}
|
||||
|
||||
const std::string PlainTablePropertyNames::kPrefixExtractorName =
|
||||
@@ -48,5 +45,11 @@ const std::string PlainTablePropertyNames::kPrefixExtractorName =
|
||||
const std::string PlainTablePropertyNames::kEncodingType =
|
||||
"rocksdb.plain.table.encoding.type";
|
||||
|
||||
const std::string PlainTablePropertyNames::kBloomVersion =
|
||||
"rocksdb.plain.table.bloom.version";
|
||||
|
||||
const std::string PlainTablePropertyNames::kNumBloomBlocks =
|
||||
"rocksdb.plain.table.bloom.numblocks";
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
+11
-14
@@ -143,20 +143,16 @@ class PlainTableFactory : public TableFactory {
|
||||
// huge_page_tlb_size determines whether to allocate hash indexes from huge
|
||||
// page TLB and the page size if allocating from there. See comments of
|
||||
// Arena::AllocateAligned() for details.
|
||||
explicit PlainTableFactory(uint32_t user_key_len = kPlainTableVariableLength,
|
||||
int bloom_bits_per_key = 0,
|
||||
double hash_table_ratio = 0.75,
|
||||
size_t index_sparseness = 16,
|
||||
size_t huge_page_tlb_size = 0,
|
||||
EncodingType encoding_type = kPlain,
|
||||
bool full_scan_mode = false)
|
||||
: user_key_len_(user_key_len),
|
||||
bloom_bits_per_key_(bloom_bits_per_key),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
index_sparseness_(index_sparseness),
|
||||
huge_page_tlb_size_(huge_page_tlb_size),
|
||||
encoding_type_(encoding_type),
|
||||
full_scan_mode_(full_scan_mode) {}
|
||||
explicit PlainTableFactory(const PlainTableOptions& options =
|
||||
PlainTableOptions())
|
||||
: user_key_len_(options.user_key_len),
|
||||
bloom_bits_per_key_(options.bloom_bits_per_key),
|
||||
hash_table_ratio_(options.hash_table_ratio),
|
||||
index_sparseness_(options.index_sparseness),
|
||||
huge_page_tlb_size_(options.huge_page_tlb_size),
|
||||
encoding_type_(options.encoding_type),
|
||||
full_scan_mode_(options.full_scan_mode),
|
||||
store_index_in_file_(options.store_index_in_file) {}
|
||||
const char* Name() const override { return "PlainTable"; }
|
||||
Status NewTableReader(const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
@@ -178,6 +174,7 @@ class PlainTableFactory : public TableFactory {
|
||||
size_t huge_page_tlb_size_;
|
||||
EncodingType encoding_type_;
|
||||
bool full_scan_mode_;
|
||||
bool store_index_in_file_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// 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 "table/plain_table_index.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
inline uint32_t GetBucketIdFromHash(uint32_t hash, uint32_t num_buckets) {
|
||||
assert(num_buckets > 0);
|
||||
return hash % num_buckets;
|
||||
}
|
||||
}
|
||||
|
||||
Status PlainTableIndex::InitFromRawData(Slice data) {
|
||||
if (!GetVarint32(&data, &index_size_)) {
|
||||
return Status::Corruption("Couldn't read the index size!");
|
||||
}
|
||||
assert(index_size_ > 0);
|
||||
if (!GetVarint32(&data, &num_prefixes_)) {
|
||||
return Status::Corruption("Couldn't read the index size!");
|
||||
}
|
||||
sub_index_size_ = data.size() - index_size_ * kOffsetLen;
|
||||
|
||||
char* index_data_begin = const_cast<char*>(data.data());
|
||||
index_ = reinterpret_cast<uint32_t*>(index_data_begin);
|
||||
sub_index_ = reinterpret_cast<char*>(index_ + index_size_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
PlainTableIndex::IndexSearchResult PlainTableIndex::GetOffset(
|
||||
uint32_t prefix_hash, uint32_t* bucket_value) const {
|
||||
int bucket = GetBucketIdFromHash(prefix_hash, index_size_);
|
||||
*bucket_value = index_[bucket];
|
||||
if ((*bucket_value & kSubIndexMask) == kSubIndexMask) {
|
||||
*bucket_value ^= kSubIndexMask;
|
||||
return kSubindex;
|
||||
}
|
||||
if (*bucket_value >= kMaxFileSize) {
|
||||
return kNoPrefixForBucket;
|
||||
} else {
|
||||
// point directly to the file
|
||||
return kDirectToFile;
|
||||
}
|
||||
}
|
||||
|
||||
void PlainTableIndexBuilder::IndexRecordList::AddRecord(murmur_t hash,
|
||||
uint32_t offset) {
|
||||
if (num_records_in_current_group_ == kNumRecordsPerGroup) {
|
||||
current_group_ = AllocateNewGroup();
|
||||
num_records_in_current_group_ = 0;
|
||||
}
|
||||
auto& new_record = current_group_[num_records_in_current_group_++];
|
||||
new_record.hash = hash;
|
||||
new_record.offset = offset;
|
||||
new_record.next = nullptr;
|
||||
}
|
||||
|
||||
void PlainTableIndexBuilder::AddKeyPrefix(Slice key_prefix_slice,
|
||||
uint64_t key_offset) {
|
||||
if (is_first_record_ || prev_key_prefix_ != key_prefix_slice.ToString()) {
|
||||
++num_prefixes_;
|
||||
if (!is_first_record_) {
|
||||
keys_per_prefix_hist_.Add(num_keys_per_prefix_);
|
||||
}
|
||||
num_keys_per_prefix_ = 0;
|
||||
prev_key_prefix_ = key_prefix_slice.ToString();
|
||||
prev_key_prefix_hash_ = GetSliceHash(key_prefix_slice);
|
||||
due_index_ = true;
|
||||
}
|
||||
|
||||
if (due_index_) {
|
||||
// Add an index key for every kIndexIntervalForSamePrefixKeys keys
|
||||
record_list_.AddRecord(prev_key_prefix_hash_, key_offset);
|
||||
due_index_ = false;
|
||||
}
|
||||
|
||||
num_keys_per_prefix_++;
|
||||
if (index_sparseness_ == 0 || num_keys_per_prefix_ % index_sparseness_ == 0) {
|
||||
due_index_ = true;
|
||||
}
|
||||
is_first_record_ = false;
|
||||
}
|
||||
|
||||
Slice PlainTableIndexBuilder::Finish() {
|
||||
AllocateIndex();
|
||||
std::vector<IndexRecord*> hash_to_offsets(index_size_, nullptr);
|
||||
std::vector<uint32_t> entries_per_bucket(index_size_, 0);
|
||||
BucketizeIndexes(&hash_to_offsets, &entries_per_bucket);
|
||||
|
||||
keys_per_prefix_hist_.Add(num_keys_per_prefix_);
|
||||
Log(options_.info_log, "Number of Keys per prefix Histogram: %s",
|
||||
keys_per_prefix_hist_.ToString().c_str());
|
||||
|
||||
// From the temp data structure, populate indexes.
|
||||
return FillIndexes(hash_to_offsets, entries_per_bucket);
|
||||
}
|
||||
|
||||
void PlainTableIndexBuilder::AllocateIndex() {
|
||||
if (prefix_extractor_ == nullptr || hash_table_ratio_ <= 0) {
|
||||
// Fall back to pure binary search if the user fails to specify a prefix
|
||||
// extractor.
|
||||
index_size_ = 1;
|
||||
} else {
|
||||
double hash_table_size_multipier = 1.0 / hash_table_ratio_;
|
||||
index_size_ = num_prefixes_ * hash_table_size_multipier + 1;
|
||||
assert(index_size_ > 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PlainTableIndexBuilder::BucketizeIndexes(
|
||||
std::vector<IndexRecord*>* hash_to_offsets,
|
||||
std::vector<uint32_t>* entries_per_bucket) {
|
||||
bool first = true;
|
||||
uint32_t prev_hash = 0;
|
||||
size_t num_records = record_list_.GetNumRecords();
|
||||
for (size_t i = 0; i < num_records; i++) {
|
||||
IndexRecord* index_record = record_list_.At(i);
|
||||
uint32_t cur_hash = index_record->hash;
|
||||
if (first || prev_hash != cur_hash) {
|
||||
prev_hash = cur_hash;
|
||||
first = false;
|
||||
}
|
||||
uint32_t bucket = GetBucketIdFromHash(cur_hash, index_size_);
|
||||
IndexRecord* prev_bucket_head = (*hash_to_offsets)[bucket];
|
||||
index_record->next = prev_bucket_head;
|
||||
(*hash_to_offsets)[bucket] = index_record;
|
||||
(*entries_per_bucket)[bucket]++;
|
||||
}
|
||||
|
||||
sub_index_size_ = 0;
|
||||
for (auto entry_count : *entries_per_bucket) {
|
||||
if (entry_count <= 1) {
|
||||
continue;
|
||||
}
|
||||
// Only buckets with more than 1 entry will have subindex.
|
||||
sub_index_size_ += VarintLength(entry_count);
|
||||
// total bytes needed to store these entries' in-file offsets.
|
||||
sub_index_size_ += entry_count * PlainTableIndex::kOffsetLen;
|
||||
}
|
||||
}
|
||||
|
||||
Slice PlainTableIndexBuilder::FillIndexes(
|
||||
const std::vector<IndexRecord*>& hash_to_offsets,
|
||||
const std::vector<uint32_t>& entries_per_bucket) {
|
||||
Log(options_.info_log, "Reserving %zu bytes for plain table's sub_index",
|
||||
sub_index_size_);
|
||||
auto total_allocate_size = GetTotalSize();
|
||||
char* allocated = arena_->AllocateAligned(
|
||||
total_allocate_size, huge_page_tlb_size_, options_.info_log.get());
|
||||
|
||||
auto temp_ptr = EncodeVarint32(allocated, index_size_);
|
||||
uint32_t* index =
|
||||
reinterpret_cast<uint32_t*>(EncodeVarint32(temp_ptr, num_prefixes_));
|
||||
char* sub_index = reinterpret_cast<char*>(index + index_size_);
|
||||
|
||||
size_t sub_index_offset = 0;
|
||||
for (uint32_t i = 0; i < index_size_; i++) {
|
||||
uint32_t num_keys_for_bucket = entries_per_bucket[i];
|
||||
switch (num_keys_for_bucket) {
|
||||
case 0:
|
||||
// No key for bucket
|
||||
index[i] = PlainTableIndex::kMaxFileSize;
|
||||
break;
|
||||
case 1:
|
||||
// point directly to the file offset
|
||||
index[i] = hash_to_offsets[i]->offset;
|
||||
break;
|
||||
default:
|
||||
// point to second level indexes.
|
||||
index[i] = sub_index_offset | PlainTableIndex::kSubIndexMask;
|
||||
char* prev_ptr = &sub_index[sub_index_offset];
|
||||
char* cur_ptr = EncodeVarint32(prev_ptr, num_keys_for_bucket);
|
||||
sub_index_offset += (cur_ptr - prev_ptr);
|
||||
char* sub_index_pos = &sub_index[sub_index_offset];
|
||||
IndexRecord* record = hash_to_offsets[i];
|
||||
int j;
|
||||
for (j = num_keys_for_bucket - 1; j >= 0 && record;
|
||||
j--, record = record->next) {
|
||||
EncodeFixed32(sub_index_pos + j * sizeof(uint32_t), record->offset);
|
||||
}
|
||||
assert(j == -1 && record == nullptr);
|
||||
sub_index_offset += PlainTableIndex::kOffsetLen * num_keys_for_bucket;
|
||||
assert(sub_index_offset <= sub_index_size_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(sub_index_offset == sub_index_size_);
|
||||
|
||||
Log(options_.info_log, "hash table size: %d, suffix_map length %zu",
|
||||
index_size_, sub_index_size_);
|
||||
return Slice(allocated, GetTotalSize());
|
||||
}
|
||||
|
||||
const std::string PlainTableIndexBuilder::kPlainTableIndexBlock =
|
||||
"PlainTableIndexBlock";
|
||||
}; // namespace rocksdb
|
||||
@@ -0,0 +1,221 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/murmurhash.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/histogram.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// PlainTableIndex contains buckets size of index_size_, each is a
|
||||
// 32-bit integer. The lower 31 bits contain an offset value (explained below)
|
||||
// and the first bit of the integer indicates type of the offset.
|
||||
//
|
||||
// +--------------+------------------------------------------------------+
|
||||
// | Flag (1 bit) | Offset to binary search buffer or file (31 bits) +
|
||||
// +--------------+------------------------------------------------------+
|
||||
//
|
||||
// Explanation for the "flag bit":
|
||||
//
|
||||
// 0 indicates that the bucket contains only one prefix (no conflict when
|
||||
// hashing this prefix), whose first row starts from this offset of the
|
||||
// file.
|
||||
// 1 indicates that the bucket contains more than one prefixes, or there
|
||||
// are too many rows for one prefix so we need a binary search for it. In
|
||||
// this case, the offset indicates the offset of sub_index_ holding the
|
||||
// binary search indexes of keys for those rows. Those binary search indexes
|
||||
// are organized in this way:
|
||||
//
|
||||
// The first 4 bytes, indicate how many indexes (N) are stored after it. After
|
||||
// it, there are N 32-bit integers, each points of an offset of the file,
|
||||
// which
|
||||
// points to starting of a row. Those offsets need to be guaranteed to be in
|
||||
// ascending order so the keys they are pointing to are also in ascending
|
||||
// order
|
||||
// to make sure we can use them to do binary searches. Below is visual
|
||||
// presentation of a bucket.
|
||||
//
|
||||
// <begin>
|
||||
// number_of_records: varint32
|
||||
// record 1 file offset: fixedint32
|
||||
// record 2 file offset: fixedint32
|
||||
// ....
|
||||
// record N file offset: fixedint32
|
||||
// <end>
|
||||
class PlainTableIndex {
|
||||
public:
|
||||
enum IndexSearchResult {
|
||||
kNoPrefixForBucket = 0,
|
||||
kDirectToFile = 1,
|
||||
kSubindex = 2
|
||||
};
|
||||
|
||||
explicit PlainTableIndex(Slice data) { InitFromRawData(data); }
|
||||
|
||||
PlainTableIndex()
|
||||
: index_size_(0),
|
||||
sub_index_size_(0),
|
||||
num_prefixes_(0),
|
||||
index_(nullptr),
|
||||
sub_index_(nullptr) {}
|
||||
|
||||
IndexSearchResult GetOffset(uint32_t prefix_hash,
|
||||
uint32_t* bucket_value) const;
|
||||
|
||||
Status InitFromRawData(Slice data);
|
||||
|
||||
const char* GetSubIndexBasePtrAndUpperBound(uint32_t offset,
|
||||
uint32_t* upper_bound) const {
|
||||
const char* index_ptr = &sub_index_[offset];
|
||||
return GetVarint32Ptr(index_ptr, index_ptr + 4, upper_bound);
|
||||
}
|
||||
|
||||
uint32_t GetIndexSize() const { return index_size_; }
|
||||
|
||||
uint32_t GetSubIndexSize() const { return sub_index_size_; }
|
||||
|
||||
uint32_t GetNumPrefixes() const { return num_prefixes_; }
|
||||
|
||||
static const uint64_t kMaxFileSize = (1u << 31) - 1;
|
||||
static const uint32_t kSubIndexMask = 0x80000000;
|
||||
static const size_t kOffsetLen = sizeof(uint32_t);
|
||||
|
||||
private:
|
||||
uint32_t index_size_;
|
||||
size_t sub_index_size_;
|
||||
uint32_t num_prefixes_;
|
||||
|
||||
uint32_t* index_;
|
||||
char* sub_index_;
|
||||
};
|
||||
|
||||
// PlainTableIndexBuilder is used to create plain table index.
|
||||
// After calling Finish(), it returns Slice, which is usually
|
||||
// used either to initialize PlainTableIndex or
|
||||
// to save index to sst file.
|
||||
// For more details about the index, please refer to:
|
||||
// https://github.com/facebook/rocksdb/wiki/PlainTable-Format
|
||||
// #wiki-in-memory-index-format
|
||||
class PlainTableIndexBuilder {
|
||||
public:
|
||||
PlainTableIndexBuilder(Arena* arena, const Options& options,
|
||||
uint32_t index_sparseness, double hash_table_ratio,
|
||||
double huge_page_tlb_size)
|
||||
: arena_(arena),
|
||||
options_(options),
|
||||
record_list_(kRecordsPerGroup),
|
||||
is_first_record_(true),
|
||||
due_index_(false),
|
||||
num_prefixes_(0),
|
||||
num_keys_per_prefix_(0),
|
||||
prev_key_prefix_hash_(0),
|
||||
index_sparseness_(index_sparseness),
|
||||
prefix_extractor_(options.prefix_extractor.get()),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
huge_page_tlb_size_(huge_page_tlb_size) {}
|
||||
|
||||
void AddKeyPrefix(Slice key_prefix_slice, uint64_t key_offset);
|
||||
|
||||
Slice Finish();
|
||||
|
||||
uint32_t GetTotalSize() const {
|
||||
return VarintLength(index_size_) + VarintLength(num_prefixes_) +
|
||||
PlainTableIndex::kOffsetLen * index_size_ + sub_index_size_;
|
||||
}
|
||||
|
||||
static const std::string kPlainTableIndexBlock;
|
||||
|
||||
private:
|
||||
struct IndexRecord {
|
||||
uint32_t hash; // hash of the prefix
|
||||
uint32_t offset; // offset of a row
|
||||
IndexRecord* next;
|
||||
};
|
||||
|
||||
// Helper class to track all the index records
|
||||
class IndexRecordList {
|
||||
public:
|
||||
explicit IndexRecordList(size_t num_records_per_group)
|
||||
: kNumRecordsPerGroup(num_records_per_group),
|
||||
current_group_(nullptr),
|
||||
num_records_in_current_group_(num_records_per_group) {}
|
||||
|
||||
~IndexRecordList() {
|
||||
for (size_t i = 0; i < groups_.size(); i++) {
|
||||
delete[] groups_[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AddRecord(murmur_t hash, uint32_t offset);
|
||||
|
||||
size_t GetNumRecords() const {
|
||||
return (groups_.size() - 1) * kNumRecordsPerGroup +
|
||||
num_records_in_current_group_;
|
||||
}
|
||||
IndexRecord* At(size_t index) {
|
||||
return &(groups_[index / kNumRecordsPerGroup]
|
||||
[index % kNumRecordsPerGroup]);
|
||||
}
|
||||
|
||||
private:
|
||||
IndexRecord* AllocateNewGroup() {
|
||||
IndexRecord* result = new IndexRecord[kNumRecordsPerGroup];
|
||||
groups_.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Each group in `groups_` contains fix-sized records (determined by
|
||||
// kNumRecordsPerGroup). Which can help us minimize the cost if resizing
|
||||
// occurs.
|
||||
const size_t kNumRecordsPerGroup;
|
||||
IndexRecord* current_group_;
|
||||
// List of arrays allocated
|
||||
std::vector<IndexRecord*> groups_;
|
||||
size_t num_records_in_current_group_;
|
||||
};
|
||||
|
||||
void AllocateIndex();
|
||||
|
||||
// Internal helper function to bucket index record list to hash buckets.
|
||||
void BucketizeIndexes(std::vector<IndexRecord*>* hash_to_offsets,
|
||||
std::vector<uint32_t>* entries_per_bucket);
|
||||
|
||||
// Internal helper class to fill the indexes and bloom filters to internal
|
||||
// data structures.
|
||||
Slice FillIndexes(const std::vector<IndexRecord*>& hash_to_offsets,
|
||||
const std::vector<uint32_t>& entries_per_bucket);
|
||||
|
||||
Arena* arena_;
|
||||
Options options_;
|
||||
HistogramImpl keys_per_prefix_hist_;
|
||||
IndexRecordList record_list_;
|
||||
bool is_first_record_;
|
||||
bool due_index_;
|
||||
uint32_t num_prefixes_;
|
||||
uint32_t num_keys_per_prefix_;
|
||||
|
||||
uint32_t prev_key_prefix_hash_;
|
||||
uint32_t index_sparseness_;
|
||||
uint32_t index_size_;
|
||||
size_t sub_index_size_;
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
double hash_table_ratio_;
|
||||
double huge_page_tlb_size_;
|
||||
|
||||
std::string prev_key_prefix_;
|
||||
|
||||
static const size_t kRecordsPerGroup = 256;
|
||||
};
|
||||
|
||||
}; // namespace rocksdb
|
||||
+136
-260
@@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "table/plain_table_reader.h"
|
||||
|
||||
#include <string>
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "rocksdb/statistics.h"
|
||||
|
||||
#include "table/block.h"
|
||||
#include "table/bloom_block.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/meta_blocks.h"
|
||||
@@ -39,15 +41,6 @@ namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
inline uint32_t GetSliceHash(const Slice& s) {
|
||||
return Hash(s.data(), s.size(), 397) ;
|
||||
}
|
||||
|
||||
inline uint32_t GetBucketIdFromHash(uint32_t hash, uint32_t num_buckets) {
|
||||
assert(num_buckets >= 0);
|
||||
return hash % num_buckets;
|
||||
}
|
||||
|
||||
// Safely getting a uint32_t element from a char array, where, starting from
|
||||
// `base`, every 4 bytes are considered as an fixed 32 bit integer.
|
||||
inline uint32_t GetFixed32Element(const char* base, size_t offset) {
|
||||
@@ -103,6 +96,7 @@ PlainTableReader::PlainTableReader(const Options& options,
|
||||
const TableProperties* table_properties)
|
||||
: internal_comparator_(icomparator),
|
||||
encoding_type_(encoding_type),
|
||||
full_scan_mode_(false),
|
||||
data_end_offset_(table_properties->data_size),
|
||||
user_key_len_(table_properties->fixed_key_len),
|
||||
prefix_extractor_(options.prefix_extractor.get()),
|
||||
@@ -126,8 +120,7 @@ Status PlainTableReader::Open(const Options& options,
|
||||
double hash_table_ratio, size_t index_sparseness,
|
||||
size_t huge_page_tlb_size, bool full_scan_mode) {
|
||||
assert(options.allow_mmap_reads);
|
||||
|
||||
if (file_size > kMaxFileSize) {
|
||||
if (file_size > PlainTableIndex::kMaxFileSize) {
|
||||
return Status::NotSupported("File is too large for PlainTableReader!");
|
||||
}
|
||||
|
||||
@@ -173,7 +166,6 @@ Status PlainTableReader::Open(const Options& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
// -- Populate Index
|
||||
if (!full_scan_mode) {
|
||||
s = new_reader->PopulateIndex(props, bloom_bits_per_key, hash_table_ratio,
|
||||
index_sparseness, huge_page_tlb_size);
|
||||
@@ -183,7 +175,7 @@ Status PlainTableReader::Open(const Options& options,
|
||||
} else {
|
||||
// Flag to indicate it is a full scan mode so that none of the indexes
|
||||
// can be used.
|
||||
new_reader->index_size_ = kFullScanModeFlag;
|
||||
new_reader->full_scan_mode_ = true;
|
||||
}
|
||||
|
||||
*table_reader = std::move(new_reader);
|
||||
@@ -203,79 +195,15 @@ Iterator* PlainTableReader::NewIterator(const ReadOptions& options,
|
||||
}
|
||||
}
|
||||
|
||||
struct PlainTableReader::IndexRecord {
|
||||
uint32_t hash; // hash of the prefix
|
||||
uint32_t offset; // offset of a row
|
||||
IndexRecord* next;
|
||||
};
|
||||
|
||||
// Helper class to track all the index records
|
||||
class PlainTableReader::IndexRecordList {
|
||||
public:
|
||||
explicit IndexRecordList(size_t num_records_per_group)
|
||||
: kNumRecordsPerGroup(num_records_per_group),
|
||||
current_group_(nullptr),
|
||||
num_records_in_current_group_(num_records_per_group) {}
|
||||
|
||||
~IndexRecordList() {
|
||||
for (size_t i = 0; i < groups_.size(); i++) {
|
||||
delete[] groups_[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AddRecord(murmur_t hash, uint32_t offset) {
|
||||
if (num_records_in_current_group_ == kNumRecordsPerGroup) {
|
||||
current_group_ = AllocateNewGroup();
|
||||
num_records_in_current_group_ = 0;
|
||||
}
|
||||
auto& new_record = current_group_[num_records_in_current_group_++];
|
||||
new_record.hash = hash;
|
||||
new_record.offset = offset;
|
||||
new_record.next = nullptr;
|
||||
}
|
||||
|
||||
size_t GetNumRecords() const {
|
||||
return (groups_.size() - 1) * kNumRecordsPerGroup +
|
||||
num_records_in_current_group_;
|
||||
}
|
||||
IndexRecord* At(size_t index) {
|
||||
return &(groups_[index / kNumRecordsPerGroup][index % kNumRecordsPerGroup]);
|
||||
}
|
||||
|
||||
private:
|
||||
IndexRecord* AllocateNewGroup() {
|
||||
IndexRecord* result = new IndexRecord[kNumRecordsPerGroup];
|
||||
groups_.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Each group in `groups_` contains fix-sized records (determined by
|
||||
// kNumRecordsPerGroup). Which can help us minimize the cost if resizing
|
||||
// occurs.
|
||||
const size_t kNumRecordsPerGroup;
|
||||
IndexRecord* current_group_;
|
||||
// List of arrays allocated
|
||||
std::vector<IndexRecord*> groups_;
|
||||
size_t num_records_in_current_group_;
|
||||
};
|
||||
|
||||
Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
int* num_prefixes,
|
||||
int bloom_bits_per_key,
|
||||
size_t index_sparseness) {
|
||||
Status PlainTableReader::PopulateIndexRecordList(
|
||||
PlainTableIndexBuilder* index_builder, vector<uint32_t>* prefix_hashes) {
|
||||
Slice prev_key_prefix_slice;
|
||||
uint32_t prev_key_prefix_hash = 0;
|
||||
uint32_t pos = data_start_offset_;
|
||||
int num_keys_per_prefix = 0;
|
||||
bool is_first_record = true;
|
||||
HistogramImpl keys_per_prefix_hist;
|
||||
// Need map to be ordered to make sure sub indexes generated
|
||||
// are in order.
|
||||
|
||||
*num_prefixes = 0;
|
||||
bool is_first_record = true;
|
||||
Slice key_prefix_slice;
|
||||
PlainTableKeyDecoder decoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get());
|
||||
bool due_index = false;
|
||||
while (pos < data_end_offset_) {
|
||||
uint32_t key_offset = pos;
|
||||
ParsedInternalKey key;
|
||||
@@ -285,152 +213,53 @@ Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
key_prefix_slice = GetPrefix(key);
|
||||
if (enable_bloom_) {
|
||||
// total order mode and bloom filter is enabled.
|
||||
bloom_.AddHash(GetSliceHash(key.user_key));
|
||||
}
|
||||
Slice key_prefix_slice = GetPrefix(key);
|
||||
|
||||
if (is_first_record || prev_key_prefix_slice != key_prefix_slice) {
|
||||
++(*num_prefixes);
|
||||
if (!is_first_record) {
|
||||
keys_per_prefix_hist.Add(num_keys_per_prefix);
|
||||
} else {
|
||||
if (is_first_record || prev_key_prefix_slice != key_prefix_slice) {
|
||||
if (!is_first_record) {
|
||||
prefix_hashes->push_back(GetSliceHash(prev_key_prefix_slice));
|
||||
}
|
||||
prev_key_prefix_slice = key_prefix_slice;
|
||||
}
|
||||
num_keys_per_prefix = 0;
|
||||
prev_key_prefix_slice = key_prefix_slice;
|
||||
prev_key_prefix_hash = GetSliceHash(key_prefix_slice);
|
||||
due_index = true;
|
||||
}
|
||||
|
||||
if (due_index) {
|
||||
if (!seekable) {
|
||||
return Status::Corruption("Key for a prefix is not seekable");
|
||||
}
|
||||
// Add an index key for every kIndexIntervalForSamePrefixKeys keys
|
||||
record_list->AddRecord(prev_key_prefix_hash, key_offset);
|
||||
due_index = false;
|
||||
index_builder->AddKeyPrefix(GetPrefix(key), key_offset);
|
||||
|
||||
if (!seekable && is_first_record) {
|
||||
return Status::Corruption("Key for a prefix is not seekable");
|
||||
}
|
||||
|
||||
num_keys_per_prefix++;
|
||||
if (index_sparseness == 0 || num_keys_per_prefix % index_sparseness == 0) {
|
||||
due_index = true;
|
||||
}
|
||||
is_first_record = false;
|
||||
}
|
||||
|
||||
keys_per_prefix_hist.Add(num_keys_per_prefix);
|
||||
Log(options_.info_log, "Number of Keys per prefix Histogram: %s",
|
||||
keys_per_prefix_hist.ToString().c_str());
|
||||
|
||||
return Status::OK();
|
||||
prefix_hashes->push_back(GetSliceHash(key_prefix_slice));
|
||||
auto s = index_.InitFromRawData(index_builder->Finish());
|
||||
return s;
|
||||
}
|
||||
|
||||
void PlainTableReader::AllocateIndexAndBloom(int num_prefixes,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t huge_page_tlb_size) {
|
||||
if (prefix_extractor_ != nullptr) {
|
||||
void PlainTableReader::AllocateAndFillBloom(int bloom_bits_per_key,
|
||||
int num_prefixes,
|
||||
size_t huge_page_tlb_size,
|
||||
vector<uint32_t>* prefix_hashes) {
|
||||
if (!IsTotalOrderMode()) {
|
||||
uint32_t bloom_total_bits = num_prefixes * bloom_bits_per_key;
|
||||
if (bloom_total_bits > 0) {
|
||||
enable_bloom_ = true;
|
||||
bloom_.SetTotalBits(&arena_, bloom_total_bits, options_.bloom_locality,
|
||||
huge_page_tlb_size, options_.info_log.get());
|
||||
FillBloom(prefix_hashes);
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix_extractor_ == nullptr || hash_table_ratio <= 0) {
|
||||
// Fall back to pure binary search if the user fails to specify a prefix
|
||||
// extractor.
|
||||
index_size_ = 1;
|
||||
} else {
|
||||
double hash_table_size_multipier = 1.0 / hash_table_ratio;
|
||||
index_size_ = num_prefixes * hash_table_size_multipier + 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t PlainTableReader::BucketizeIndexesAndFillBloom(
|
||||
IndexRecordList* record_list, std::vector<IndexRecord*>* hash_to_offsets,
|
||||
std::vector<uint32_t>* entries_per_bucket) {
|
||||
bool first = true;
|
||||
uint32_t prev_hash = 0;
|
||||
size_t num_records = record_list->GetNumRecords();
|
||||
for (size_t i = 0; i < num_records; i++) {
|
||||
IndexRecord* index_record = record_list->At(i);
|
||||
uint32_t cur_hash = index_record->hash;
|
||||
if (first || prev_hash != cur_hash) {
|
||||
prev_hash = cur_hash;
|
||||
first = false;
|
||||
if (enable_bloom_ && !IsTotalOrderMode()) {
|
||||
bloom_.AddHash(cur_hash);
|
||||
}
|
||||
}
|
||||
uint32_t bucket = GetBucketIdFromHash(cur_hash, index_size_);
|
||||
IndexRecord* prev_bucket_head = (*hash_to_offsets)[bucket];
|
||||
index_record->next = prev_bucket_head;
|
||||
(*hash_to_offsets)[bucket] = index_record;
|
||||
(*entries_per_bucket)[bucket]++;
|
||||
void PlainTableReader::FillBloom(vector<uint32_t>* prefix_hashes) {
|
||||
assert(bloom_.IsInitialized());
|
||||
for (auto prefix_hash : *prefix_hashes) {
|
||||
bloom_.AddHash(prefix_hash);
|
||||
}
|
||||
size_t sub_index_size = 0;
|
||||
for (auto entry_count : *entries_per_bucket) {
|
||||
if (entry_count <= 1) {
|
||||
continue;
|
||||
}
|
||||
// Only buckets with more than 1 entry will have subindex.
|
||||
sub_index_size += VarintLength(entry_count);
|
||||
// total bytes needed to store these entries' in-file offsets.
|
||||
sub_index_size += entry_count * kOffsetLen;
|
||||
}
|
||||
return sub_index_size;
|
||||
}
|
||||
|
||||
void PlainTableReader::FillIndexes(
|
||||
const size_t kSubIndexSize,
|
||||
const std::vector<IndexRecord*>& hash_to_offsets,
|
||||
const std::vector<uint32_t>& entries_per_bucket,
|
||||
size_t huge_page_tlb_size) {
|
||||
Log(options_.info_log, "Reserving %zu bytes for plain table's sub_index",
|
||||
kSubIndexSize);
|
||||
auto total_allocate_size = sizeof(uint32_t) * index_size_ + kSubIndexSize;
|
||||
char* allocated = arena_.AllocateAligned(
|
||||
total_allocate_size, huge_page_tlb_size, options_.info_log.get());
|
||||
index_ = reinterpret_cast<uint32_t*>(allocated);
|
||||
sub_index_ = allocated + sizeof(uint32_t) * index_size_;
|
||||
|
||||
size_t sub_index_offset = 0;
|
||||
for (int i = 0; i < index_size_; i++) {
|
||||
uint32_t num_keys_for_bucket = entries_per_bucket[i];
|
||||
switch (num_keys_for_bucket) {
|
||||
case 0:
|
||||
// No key for bucket
|
||||
index_[i] = data_end_offset_;
|
||||
break;
|
||||
case 1:
|
||||
// point directly to the file offset
|
||||
index_[i] = hash_to_offsets[i]->offset;
|
||||
break;
|
||||
default:
|
||||
// point to second level indexes.
|
||||
index_[i] = sub_index_offset | kSubIndexMask;
|
||||
char* prev_ptr = &sub_index_[sub_index_offset];
|
||||
char* cur_ptr = EncodeVarint32(prev_ptr, num_keys_for_bucket);
|
||||
sub_index_offset += (cur_ptr - prev_ptr);
|
||||
char* sub_index_pos = &sub_index_[sub_index_offset];
|
||||
IndexRecord* record = hash_to_offsets[i];
|
||||
int j;
|
||||
for (j = num_keys_for_bucket - 1; j >= 0 && record;
|
||||
j--, record = record->next) {
|
||||
EncodeFixed32(sub_index_pos + j * sizeof(uint32_t), record->offset);
|
||||
}
|
||||
assert(j == -1 && record == nullptr);
|
||||
sub_index_offset += kOffsetLen * num_keys_for_bucket;
|
||||
assert(sub_index_offset <= kSubIndexSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(sub_index_offset == kSubIndexSize);
|
||||
|
||||
Log(options_.info_log, "hash table size: %d, suffix_map length %zu",
|
||||
index_size_, kSubIndexSize);
|
||||
}
|
||||
|
||||
Status PlainTableReader::MmapDataFile() {
|
||||
@@ -446,57 +275,113 @@ Status PlainTableReader::PopulateIndex(TableProperties* props,
|
||||
assert(props != nullptr);
|
||||
table_properties_.reset(props);
|
||||
|
||||
BlockContents bloom_block_contents;
|
||||
auto s = ReadMetaBlock(file_.get(), file_size_, kPlainTableMagicNumber,
|
||||
options_.env, BloomBlockBuilder::kBloomBlock,
|
||||
&bloom_block_contents);
|
||||
bool index_in_file = s.ok();
|
||||
|
||||
BlockContents index_block_contents;
|
||||
s = ReadMetaBlock(file_.get(), file_size_, kPlainTableMagicNumber,
|
||||
options_.env, PlainTableIndexBuilder::kPlainTableIndexBlock,
|
||||
&index_block_contents);
|
||||
|
||||
index_in_file &= s.ok();
|
||||
|
||||
Slice* bloom_block;
|
||||
if (index_in_file) {
|
||||
bloom_block = &bloom_block_contents.data;
|
||||
} else {
|
||||
bloom_block = nullptr;
|
||||
}
|
||||
|
||||
// index_in_file == true only if there are kBloomBlock and
|
||||
// kPlainTableIndexBlock
|
||||
// in file
|
||||
|
||||
Slice* index_block;
|
||||
if (index_in_file) {
|
||||
index_block = &index_block_contents.data;
|
||||
} else {
|
||||
index_block = nullptr;
|
||||
}
|
||||
|
||||
if ((options_.prefix_extractor.get() == nullptr) && (hash_table_ratio != 0)) {
|
||||
// options.prefix_extractor is requried for a hash-based look-up.
|
||||
if (options_.prefix_extractor.get() == nullptr && hash_table_ratio != 0) {
|
||||
return Status::NotSupported(
|
||||
"PlainTable requires a prefix extractor enable prefix hash mode.");
|
||||
}
|
||||
|
||||
IndexRecordList record_list(kRecordsPerGroup);
|
||||
// First, read the whole file, for every kIndexIntervalForSamePrefixKeys rows
|
||||
// for a prefix (starting from the first one), generate a record of (hash,
|
||||
// offset) and append it to IndexRecordList, which is a data structure created
|
||||
// to store them.
|
||||
int num_prefixes;
|
||||
|
||||
// Allocate bloom filter here for total order mode.
|
||||
if (IsTotalOrderMode()) {
|
||||
uint32_t num_bloom_bits =
|
||||
table_properties_->num_entries * bloom_bits_per_key;
|
||||
if (num_bloom_bits > 0) {
|
||||
enable_bloom_ = true;
|
||||
bloom_.SetTotalBits(&arena_, num_bloom_bits, options_.bloom_locality,
|
||||
huge_page_tlb_size, options_.info_log.get());
|
||||
if (!index_in_file) {
|
||||
// Allocate bloom filter here for total order mode.
|
||||
if (IsTotalOrderMode()) {
|
||||
uint32_t num_bloom_bits =
|
||||
table_properties_->num_entries * bloom_bits_per_key;
|
||||
if (num_bloom_bits > 0) {
|
||||
enable_bloom_ = true;
|
||||
bloom_.SetTotalBits(&arena_, num_bloom_bits, options_.bloom_locality,
|
||||
huge_page_tlb_size, options_.info_log.get());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enable_bloom_ = true;
|
||||
auto num_blocks_property = props->user_collected_properties.find(
|
||||
PlainTablePropertyNames::kNumBloomBlocks);
|
||||
|
||||
uint32_t num_blocks = 0;
|
||||
if (num_blocks_property != props->user_collected_properties.end()) {
|
||||
Slice temp_slice(num_blocks_property->second);
|
||||
if (!GetVarint32(&temp_slice, &num_blocks)) {
|
||||
num_blocks = 0;
|
||||
}
|
||||
}
|
||||
// cast away const qualifier, because bloom_ won't be changed
|
||||
bloom_.SetRawData(
|
||||
const_cast<unsigned char*>(
|
||||
reinterpret_cast<const unsigned char*>(bloom_block->data())),
|
||||
bloom_block->size() * 8, num_blocks);
|
||||
}
|
||||
|
||||
PlainTableIndexBuilder index_builder(&arena_, options_, index_sparseness,
|
||||
hash_table_ratio, huge_page_tlb_size);
|
||||
|
||||
std::vector<uint32_t> prefix_hashes;
|
||||
if (!index_in_file) {
|
||||
Status s = PopulateIndexRecordList(&index_builder, &prefix_hashes);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
Status s = index_.InitFromRawData(*index_block);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
Status s = PopulateIndexRecordList(&record_list, &num_prefixes,
|
||||
bloom_bits_per_key, index_sparseness);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
if (!index_in_file) {
|
||||
// Calculated bloom filter size and allocate memory for
|
||||
// bloom filter based on the number of prefixes, then fill it.
|
||||
AllocateAndFillBloom(bloom_bits_per_key, index_.GetNumPrefixes(),
|
||||
huge_page_tlb_size, &prefix_hashes);
|
||||
}
|
||||
// Calculated hash table and bloom filter size and allocate memory for indexes
|
||||
// and bloom filter based on the number of prefixes.
|
||||
AllocateIndexAndBloom(num_prefixes, bloom_bits_per_key, hash_table_ratio,
|
||||
huge_page_tlb_size);
|
||||
|
||||
// Bucketize all the index records to a temp data structure, in which for
|
||||
// each bucket, we generate a linked list of IndexRecord, in reversed order.
|
||||
std::vector<IndexRecord*> hash_to_offsets(index_size_, nullptr);
|
||||
std::vector<uint32_t> entries_per_bucket(index_size_, 0);
|
||||
size_t sub_index_size_needed = BucketizeIndexesAndFillBloom(
|
||||
&record_list, &hash_to_offsets, &entries_per_bucket);
|
||||
// From the temp data structure, populate indexes.
|
||||
FillIndexes(sub_index_size_needed, hash_to_offsets, entries_per_bucket,
|
||||
huge_page_tlb_size);
|
||||
|
||||
// Fill two table properties.
|
||||
// TODO(sdong): after we have the feature of storing index in file, this
|
||||
// properties need to be populated to index_size instead.
|
||||
props->user_collected_properties["plain_table_hash_table_size"] =
|
||||
std::to_string(index_size_ * 4U);
|
||||
props->user_collected_properties["plain_table_sub_index_size"] =
|
||||
std::to_string(sub_index_size_needed);
|
||||
if (!index_in_file) {
|
||||
props->user_collected_properties["plain_table_hash_table_size"] =
|
||||
std::to_string(index_.GetIndexSize() * PlainTableIndex::kOffsetLen);
|
||||
props->user_collected_properties["plain_table_sub_index_size"] =
|
||||
std::to_string(index_.GetSubIndexSize());
|
||||
} else {
|
||||
props->user_collected_properties["plain_table_hash_table_size"] =
|
||||
std::to_string(0);
|
||||
props->user_collected_properties["plain_table_sub_index_size"] =
|
||||
std::to_string(0);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -505,24 +390,21 @@ Status PlainTableReader::GetOffset(const Slice& target, const Slice& prefix,
|
||||
uint32_t prefix_hash, bool& prefix_matched,
|
||||
uint32_t* offset) const {
|
||||
prefix_matched = false;
|
||||
int bucket = GetBucketIdFromHash(prefix_hash, index_size_);
|
||||
uint32_t bucket_value = index_[bucket];
|
||||
if (bucket_value == data_end_offset_) {
|
||||
uint32_t prefix_index_offset;
|
||||
auto res = index_.GetOffset(prefix_hash, &prefix_index_offset);
|
||||
if (res == PlainTableIndex::kNoPrefixForBucket) {
|
||||
*offset = data_end_offset_;
|
||||
return Status::OK();
|
||||
} else if ((bucket_value & kSubIndexMask) == 0) {
|
||||
// point directly to the file
|
||||
*offset = bucket_value;
|
||||
} else if (res == PlainTableIndex::kDirectToFile) {
|
||||
*offset = prefix_index_offset;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// point to sub-index, need to do a binary search
|
||||
uint32_t upper_bound;
|
||||
const char* base_ptr =
|
||||
index_.GetSubIndexBasePtrAndUpperBound(prefix_index_offset, &upper_bound);
|
||||
uint32_t low = 0;
|
||||
uint64_t prefix_index_offset = bucket_value ^ kSubIndexMask;
|
||||
|
||||
const char* index_ptr = &sub_index_[prefix_index_offset];
|
||||
uint32_t upper_bound = 0;
|
||||
const char* base_ptr = GetVarint32Ptr(index_ptr, index_ptr + 4, &upper_bound);
|
||||
uint32_t high = upper_bound;
|
||||
ParsedInternalKey mid_key;
|
||||
ParsedInternalKey parsed_target;
|
||||
@@ -592,9 +474,6 @@ bool PlainTableReader::MatchBloom(uint32_t hash) const {
|
||||
return !enable_bloom_ || bloom_.MayContainHash(hash);
|
||||
}
|
||||
|
||||
Slice PlainTableReader::GetPrefix(const ParsedInternalKey& target) const {
|
||||
return GetPrefixFromUserKey(target.user_key);
|
||||
}
|
||||
|
||||
Status PlainTableReader::Next(PlainTableKeyDecoder* decoder, uint32_t* offset,
|
||||
ParsedInternalKey* parsed_key,
|
||||
@@ -649,8 +528,7 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target,
|
||||
Slice prefix_slice;
|
||||
uint32_t prefix_hash;
|
||||
if (IsTotalOrderMode()) {
|
||||
if (index_size_ == kFullScanModeFlag) {
|
||||
// Full Scan Mode
|
||||
if (full_scan_mode_) {
|
||||
status_ =
|
||||
Status::InvalidArgument("Get() is not allowed in full scan mode.");
|
||||
}
|
||||
@@ -681,7 +559,6 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target,
|
||||
if (!ParseInternalKey(target, &parsed_target)) {
|
||||
return Status::Corruption(Slice());
|
||||
}
|
||||
|
||||
Slice found_value;
|
||||
PlainTableKeyDecoder decoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get());
|
||||
@@ -746,13 +623,12 @@ void PlainTableIterator::Seek(const Slice& target) {
|
||||
// If the user doesn't set prefix seek option and we are not able to do a
|
||||
// total Seek(). assert failure.
|
||||
if (!use_prefix_seek_) {
|
||||
if (table_->index_size_ == PlainTableReader::kFullScanModeFlag) {
|
||||
// Full Scan Mode.
|
||||
if (table_->full_scan_mode_) {
|
||||
status_ =
|
||||
Status::InvalidArgument("Seek() is not allowed in full scan mode.");
|
||||
offset_ = next_offset_ = table_->data_end_offset_;
|
||||
return;
|
||||
} else if (table_->index_size_ > 1) {
|
||||
} else if (table_->GetIndexSize() > 1) {
|
||||
assert(false);
|
||||
status_ = Status::NotSupported(
|
||||
"PlainTable cannot issue non-prefix seek unless in total order "
|
||||
|
||||
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user