mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bba6595b1f | ||
|
|
444cf88a56 | ||
|
|
fd2044883a | ||
|
|
c2be2cba04 | ||
|
|
292c2b3357 | ||
|
|
95a8213f75 | ||
|
|
36409e0016 | ||
|
|
be96f2498e | ||
|
|
fe4a449472 | ||
|
|
f0b0b28f9a | ||
|
|
39190588e1 | ||
|
|
7845fd9db9 | ||
|
|
f837f5b1c9 | ||
|
|
b4ad5e89ae | ||
|
|
1e4375d2ef | ||
|
|
beeb74be6f | ||
|
|
b572e81f94 | ||
|
|
82b7e37f6e | ||
|
|
7caadf2e52 | ||
|
|
8cbe5bb56b | ||
|
|
b4fab3be2a | ||
|
|
138a8eee8e | ||
|
|
fe25070242 | ||
|
|
f03b2df010 | ||
|
|
068a819ac9 | ||
|
|
79d8dad331 | ||
|
|
d4eec30ed0 | ||
|
|
8ace6b0f91 | ||
|
|
17991cd5a0 | ||
|
|
994575c134 | ||
|
|
7e91b86f4d | ||
|
|
100fa8e013 | ||
|
|
1ca86f0391 | ||
|
|
a1d38a41fd | ||
|
|
39c14891b6 | ||
|
|
56305221c4 | ||
|
|
c17607a251 | ||
|
|
e44976b199 | ||
|
|
7e2c1ba173 | ||
|
|
cb8a7302e4 | ||
|
|
2fb361ad98 | ||
|
|
e56ce03691 | ||
|
|
b37fda842d | ||
|
|
30f1b97a06 | ||
|
|
70e87f7866 |
No files matched your search
+4
-5
@@ -1,4 +1,5 @@
|
||||
build_config.mk
|
||||
|
||||
*.a
|
||||
*.arc
|
||||
*.d
|
||||
@@ -9,12 +10,10 @@ build_config.mk
|
||||
*.so
|
||||
*.so.*
|
||||
*_test
|
||||
db_bench
|
||||
db_repl_stress
|
||||
db_stress
|
||||
*_bench
|
||||
*_stress
|
||||
|
||||
ldb
|
||||
leveldb_server
|
||||
leveldb_shell
|
||||
manifest_dump
|
||||
sst_dump
|
||||
util/build_version.cc
|
||||
|
||||
@@ -36,6 +36,7 @@ VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
TESTS = \
|
||||
table_stats_collector_test \
|
||||
arena_test \
|
||||
auto_roll_logger_test \
|
||||
block_test \
|
||||
@@ -59,6 +60,7 @@ TESTS = \
|
||||
merge_test \
|
||||
redis_test \
|
||||
reduce_levels_test \
|
||||
simple_table_db_test \
|
||||
skiplist_test \
|
||||
stringappend_test \
|
||||
table_test \
|
||||
@@ -72,10 +74,11 @@ TOOLS = \
|
||||
sst_dump \
|
||||
db_stress \
|
||||
ldb \
|
||||
db_repl_stress
|
||||
db_repl_stress \
|
||||
blob_store_bench
|
||||
|
||||
PROGRAMS = db_bench signal_test $(TESTS) $(TOOLS)
|
||||
BENCHMARKS = db_bench_sqlite3 db_bench_tree_db
|
||||
BENCHMARKS = db_bench_sqlite3 db_bench_tree_db table_reader_bench
|
||||
|
||||
# The library name is configurable since we are maintaining libraries of both
|
||||
# debug/release mode.
|
||||
@@ -85,7 +88,9 @@ MEMENVLIBRARY = libmemenv.a
|
||||
|
||||
default: all
|
||||
|
||||
# Should we build shared libraries?
|
||||
#-----------------------------------------------
|
||||
# Create platform independent shared libraries.
|
||||
#-----------------------------------------------
|
||||
ifneq ($(PLATFORM_SHARED_EXT),)
|
||||
|
||||
ifneq ($(PLATFORM_SHARED_VERSIONED),true)
|
||||
@@ -112,7 +117,7 @@ $(SHARED3):
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
|
||||
all: $(SHARED) $(LIBRARY) $(PROGRAMS)
|
||||
all: $(LIBRARY) $(PROGRAMS)
|
||||
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests \
|
||||
release tags valgrind_check whitebox_crash_test
|
||||
@@ -181,6 +186,9 @@ db_stress: tools/db_stress.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
db_repl_stress: tools/db_repl_stress.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) tools/db_repl_stress.o $(LIBOBJECTS) $(TESTUTIL) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
blob_store_bench: tools/blob_store_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) tools/blob_store_bench.o $(LIBOBJECTS) $(TESTUTIL) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
db_bench_sqlite3: doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) -lsqlite3 $(COVERAGEFLAGS)
|
||||
|
||||
@@ -193,6 +201,9 @@ signal_test: util/signal_test.o $(LIBOBJECTS)
|
||||
arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
table_stats_collector_test: db/table_stats_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/table_stats_collector_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
bloom_test: util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
@@ -226,9 +237,18 @@ 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)
|
||||
|
||||
simple_table_db_test: db/simple_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/simple_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
table_reader_bench: table/table_reader_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) table/table_reader_bench.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
perf_context_test: db/perf_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/perf_context_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
prefix_test: db/prefix_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/prefix_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
* Detailed instructions on how to compile using fbcode and jemalloc
|
||||
|
||||
* Latest release is 2.4.fb
|
||||
* Latest release is 2.5.fb
|
||||
@@ -175,6 +175,17 @@ EOF
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS ${SNAPPY_LDFLAGS:-./snappy/libs/libsnappy.a}"
|
||||
fi
|
||||
|
||||
|
||||
# Test whether gflags library is installed
|
||||
# http://code.google.com/p/gflags/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <gflags/gflags.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS"
|
||||
fi
|
||||
|
||||
# Test whether zlib library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -18,11 +18,15 @@ SNAPPY_LIBS=" $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/7518bbe/lib/libsnappy.a"
|
||||
LIBEVENT_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/libevent/libevent-1.4.14b/91ddd43/include"
|
||||
LIBEVENT_LIBS=" -L $TOOLCHAIN_LIB_BASE/libevent/libevent-1.4.14b/91ddd43/lib"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/include"
|
||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/lib/libgflags.a"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=" -msse -msse4.2 "
|
||||
|
||||
CC="$TOOLCHAIN_EXECUTABLES/clang/clang-3.1/6ca8a59/bin/clang $CLANG_INCLUDES"
|
||||
CXX="$TOOLCHAIN_EXECUTABLES/clang/clang-3.1/6ca8a59/bin/clang++ $CLANG_INCLUDES $JINCLUDE $SNAPPY_INCLUDE $LIBEVENT_INCLUDE"
|
||||
CXX="$TOOLCHAIN_EXECUTABLES/clang/clang-3.1/6ca8a59/bin/clang++ $CLANG_INCLUDES $JINCLUDE $SNAPPY_INCLUDE $LIBEVENT_INCLUDE $GFLAGS_INCLUDE"
|
||||
AR=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ar
|
||||
RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib
|
||||
|
||||
@@ -33,7 +37,7 @@ CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC
|
||||
|
||||
EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a"
|
||||
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $TOOLCHAIN_LIB_BASE/libunwind/libunwind-1.0.1/350336c/lib/libunwind.a"
|
||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $LIBEVENT_LIBS"
|
||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $LIBEVENT_LIBS $GFLAGS_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,$GLIBC_RUNTIME_PATH/lib/ld-linux-x86-64.so.2"
|
||||
EXEC_LDFLAGS+=" -B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin"
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ if test "$USE_HDFS"; then
|
||||
JINCLUDE="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux"
|
||||
GLIBC_RUNTIME_PATH="/usr/local/fbcode/gcc-4.7.1-glibc-2.14.1"
|
||||
HDFSLIB=" -Wl,--no-whole-archive hdfs/libhdfs.a -L$JAVA_HOME/jre/lib/amd64 "
|
||||
HDFSLIB+=" -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib "
|
||||
HDFSLIB+=" -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib "
|
||||
HDFSLIB+=" -ldl -lverify -ljava -ljvm "
|
||||
fi
|
||||
|
||||
@@ -31,11 +31,15 @@ ZLIB_LIBS=" $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/91ddd43/lib/libz.a"
|
||||
LIBEVENT_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/libevent/libevent-1.4.14b/91ddd43/include"
|
||||
LIBEVENT_LIBS=" -L $TOOLCHAIN_LIB_BASE/libevent/libevent-1.4.14b/91ddd43/lib"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/include"
|
||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/lib/libgflags.a"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=" -msse -msse4.2 "
|
||||
|
||||
CC="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1-glibc-2.14.1/bin/gcc"
|
||||
CXX="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1-glibc-2.14.1/bin/g++ $JINCLUDE $SNAPPY_INCLUDE $ZLIB_INCLUDE $LIBEVENT_INCLUDE"
|
||||
CXX="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1-glibc-2.14.1/bin/g++ $JINCLUDE $SNAPPY_INCLUDE $ZLIB_INCLUDE $LIBEVENT_INCLUDE $GFLAGS_INCLUDE"
|
||||
AR=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ar
|
||||
RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib
|
||||
|
||||
@@ -44,7 +48,7 @@ CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC
|
||||
|
||||
EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a"
|
||||
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $TOOLCHAIN_LIB_BASE/libunwind/libunwind-1.0.1/350336c/lib/libunwind.a"
|
||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $ZLIB_LIBS $LIBEVENT_LIBS"
|
||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $ZLIB_LIBS $LIBEVENT_LIBS $GFLAGS_LIBS"
|
||||
|
||||
PLATFORM_LDFLAGS="-L$TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/lib -L$TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/lib"
|
||||
|
||||
|
||||
+14
-2
@@ -15,12 +15,23 @@
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TableFactory;
|
||||
|
||||
TableBuilder* GetTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) {
|
||||
return options.table_factory->GetTableBuilder(options, file,
|
||||
compression_type);
|
||||
}
|
||||
|
||||
Status BuildTable(const std::string& dbname,
|
||||
Env* env,
|
||||
const Options& options,
|
||||
@@ -52,8 +63,9 @@ Status BuildTable(const std::string& dbname,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
TableBuilder* builder = new TableBuilder(options, file.get(), 0,
|
||||
enable_compression);
|
||||
|
||||
TableBuilder* builder = GetTableBuilder(options, file.get(),
|
||||
options.compression);
|
||||
|
||||
// the first key is the smallest key
|
||||
Slice key = iter->key();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -20,6 +21,12 @@ class EnvOptions;
|
||||
class Iterator;
|
||||
class TableCache;
|
||||
class VersionEdit;
|
||||
class TableBuilder;
|
||||
class WritableFile;
|
||||
|
||||
|
||||
extern TableBuilder* GetTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to meta->number. On success, the rest of
|
||||
|
||||
@@ -554,6 +554,11 @@ void leveldb_options_set_WAL_ttl_seconds(leveldb_options_t* opt, uint64_t ttl) {
|
||||
opt->rep.WAL_ttl_seconds = ttl;
|
||||
}
|
||||
|
||||
void leveldb_options_set_WAL_size_limit_MB(
|
||||
leveldb_options_t* opt, uint64_t limit) {
|
||||
opt->rep.WAL_size_limit_MB = limit;
|
||||
}
|
||||
|
||||
leveldb_comparator_t* leveldb_comparator_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
|
||||
+13
-2
@@ -15,12 +15,12 @@
|
||||
#include <sys/types.h>
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_format.h"
|
||||
#include "db/version_set.h"
|
||||
#include "table/table.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -221,10 +221,15 @@ TEST(CorruptionTest, NewFileErrorDuringWrite) {
|
||||
const int num = 3 + (Options().write_buffer_size / kValueSize);
|
||||
std::string value_storage;
|
||||
Status s;
|
||||
for (int i = 0; s.ok() && i < num; i++) {
|
||||
bool failed = false;
|
||||
for (int i = 0; i < num; i++) {
|
||||
WriteBatch batch;
|
||||
batch.Put("a", Value(100, &value_storage));
|
||||
s = db_->Write(WriteOptions(), &batch);
|
||||
if (!s.ok()) {
|
||||
failed = true;
|
||||
}
|
||||
ASSERT_TRUE(!failed || !s.ok());
|
||||
}
|
||||
ASSERT_TRUE(!s.ok());
|
||||
ASSERT_GE(env_.num_writable_file_errors_, 1);
|
||||
@@ -338,8 +343,14 @@ TEST(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
// Write must eventually fail because of corrupted table
|
||||
Status s;
|
||||
std::string tmp1, tmp2;
|
||||
bool failed = false;
|
||||
for (int i = 0; i < 10000 && s.ok(); i++) {
|
||||
s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2));
|
||||
if (!s.ok()) {
|
||||
failed = true;
|
||||
}
|
||||
// if one write failed, every subsequent write must fail, too
|
||||
ASSERT_TRUE(!failed || !s.ok()) << "write did not fail in a corrupted db";
|
||||
}
|
||||
ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db";
|
||||
}
|
||||
|
||||
+445
-612
File diff suppressed because it is too large.
Load diff
@@ -91,29 +91,4 @@ Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
return AppendSortedWalsOfType(options_.wal_dir, files, kAliveLogFile);
|
||||
}
|
||||
|
||||
Status DBImpl::DeleteWalFiles(const VectorLogPtr& files) {
|
||||
Status s;
|
||||
std::string archivedir = ArchivalDirectory(options_.wal_dir);
|
||||
std::string files_not_deleted;
|
||||
for (const auto& wal : files) {
|
||||
/* Try deleting in the dir that pathname points to for the logfile.
|
||||
This may fail if we try to delete a log file which was live when captured
|
||||
but is archived now. Try deleting it from archive also
|
||||
*/
|
||||
Status st = env_->DeleteFile(options_.wal_dir + "/" + wal->PathName());
|
||||
if (!st.ok()) {
|
||||
if (wal->Type() == kAliveLogFile &&
|
||||
env_->DeleteFile(LogFileName(archivedir, wal->LogNumber())).ok()) {
|
||||
continue;
|
||||
}
|
||||
files_not_deleted.append(wal->PathName());
|
||||
}
|
||||
}
|
||||
if (!files_not_deleted.empty()) {
|
||||
return Status::IOError("Deleted all requested files except: " +
|
||||
files_not_deleted);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
}
|
||||
+279
-74
@@ -13,15 +13,15 @@
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -30,26 +30,28 @@
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/prefix_filter_iterator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_stats_collector.h"
|
||||
#include "db/transaction_log_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/transaction_log_impl.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "port/port.h"
|
||||
#include "table/block.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/table.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "util/auto_roll_logger.h"
|
||||
#include "util/build_version.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -99,6 +101,13 @@ struct DBImpl::CompactionState {
|
||||
: compaction(c),
|
||||
total_bytes(0) {
|
||||
}
|
||||
|
||||
// Create a client visible context of this compaction
|
||||
CompactionFilter::Context GetFilterContext() {
|
||||
CompactionFilter::Context context;
|
||||
context.is_full_compaction = compaction->IsFullCompaction();
|
||||
return context;
|
||||
}
|
||||
};
|
||||
|
||||
struct DBImpl::DeletionState {
|
||||
@@ -164,9 +173,8 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
if (result.soft_rate_limit > result.hard_rate_limit) {
|
||||
result.soft_rate_limit = result.hard_rate_limit;
|
||||
}
|
||||
if (result.compaction_filter &&
|
||||
result.compaction_filter_factory->CreateCompactionFilter().get()) {
|
||||
Log(result.info_log, "Both filter and factory specified. Using filter");
|
||||
if (result.compaction_filter) {
|
||||
Log(result.info_log, "Compaction filter specified, ignore factory");
|
||||
}
|
||||
if (result.prefix_extractor) {
|
||||
// If a prefix extractor has been supplied and a PrefixHashRepFactory is
|
||||
@@ -189,9 +197,47 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
// Use dbname as default
|
||||
result.wal_dir = dbname;
|
||||
}
|
||||
|
||||
// -- Sanitize the table stats collector
|
||||
// All user defined stats collectors will be wrapped by
|
||||
// UserKeyTableStatsCollector since for them they only have the knowledge of
|
||||
// the user keys; internal keys are invisible to them.
|
||||
auto& collectors = result.table_stats_collectors;
|
||||
for (size_t i = 0; i < result.table_stats_collectors.size(); ++i) {
|
||||
assert(collectors[i]);
|
||||
collectors[i] =
|
||||
std::make_shared<UserKeyTableStatsCollector>(collectors[i]);
|
||||
}
|
||||
|
||||
// Add collector to collect internal key statistics
|
||||
collectors.push_back(
|
||||
std::make_shared<InternalKeyStatsCollector>()
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CompressionType GetCompressionType(const Options& options, int level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
// disable compression
|
||||
return kNoCompression;
|
||||
}
|
||||
// If the use has specified a different compression level for each level,
|
||||
// then pick the compresison for that level.
|
||||
if (!options.compression_per_level.empty()) {
|
||||
const int n = options.compression_per_level.size() - 1;
|
||||
// It is possible for level_ to be -1; in that case, we use level
|
||||
// 0's compression. This occurs mostly in backwards compatibility
|
||||
// situations when the builder doesn't know what level the file
|
||||
// belongs to. Likewise, if level_ is beyond the end of the
|
||||
// specified compression levels, use the last value.
|
||||
return options.compression_per_level[std::max(0, std::min(level, n))];
|
||||
} else {
|
||||
return options.compression;
|
||||
}
|
||||
}
|
||||
|
||||
DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
: env_(options.env),
|
||||
dbname_(dbname),
|
||||
@@ -218,6 +264,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
delete_obsolete_files_last_run_(0),
|
||||
purge_wal_files_last_run_(0),
|
||||
last_stats_dump_time_microsec_(0),
|
||||
default_interval_to_delete_obsolete_WAL_(600),
|
||||
stall_level0_slowdown_(0),
|
||||
stall_memtable_compaction_(0),
|
||||
stall_level0_num_files_(0),
|
||||
@@ -228,7 +275,6 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
flush_on_destroy_(false),
|
||||
stats_(options.num_levels),
|
||||
delayed_writes_(0),
|
||||
last_flushed_sequence_(0),
|
||||
storage_options_(options),
|
||||
bg_work_gate_closed_(false),
|
||||
refitting_level_(false) {
|
||||
@@ -265,6 +311,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
}
|
||||
last_log_ts = 0;
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
}
|
||||
|
||||
DBImpl::~DBImpl() {
|
||||
@@ -287,6 +334,7 @@ DBImpl::~DBImpl() {
|
||||
|
||||
if (mem_ != nullptr) mem_->Unref();
|
||||
imm_.UnrefAll();
|
||||
LogFlush(options_.info_log);
|
||||
}
|
||||
|
||||
// Do not flush and close database elegantly. Simulate a crash.
|
||||
@@ -308,6 +356,7 @@ void DBImpl::TEST_Destroy_DBImpl() {
|
||||
bg_compaction_scheduled_ += LargeNumber;
|
||||
|
||||
mutex_.Unlock();
|
||||
LogFlush(options_.info_log);
|
||||
|
||||
// force release the lock file.
|
||||
if (db_lock_ != nullptr) {
|
||||
@@ -362,7 +411,7 @@ void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
}
|
||||
|
||||
const Status DBImpl::CreateArchivalDirectory() {
|
||||
if (options_.WAL_ttl_seconds > 0) {
|
||||
if (options_.WAL_ttl_seconds > 0 || options_.WAL_size_limit_MB > 0) {
|
||||
std::string archivalPath = ArchivalDirectory(options_.wal_dir);
|
||||
return env_->CreateDirIfMissing(archivalPath);
|
||||
}
|
||||
@@ -449,7 +498,7 @@ void DBImpl::FindObsoleteFiles(DeletionState& deletion_state) {
|
||||
Status DBImpl::DeleteLogFile(uint64_t number) {
|
||||
Status s;
|
||||
auto filename = LogFileName(options_.wal_dir, number);
|
||||
if (options_.WAL_ttl_seconds > 0) {
|
||||
if (options_.WAL_ttl_seconds > 0 || options_.WAL_size_limit_MB > 0) {
|
||||
s = env_->RenameFile(filename,
|
||||
ArchivedLogFileName(options_.wal_dir, number));
|
||||
|
||||
@@ -568,34 +617,128 @@ void DBImpl::DeleteObsoleteFiles() {
|
||||
EvictObsoleteFiles(deletion_state);
|
||||
}
|
||||
|
||||
// 1. Go through all archived files and
|
||||
// a. if ttl is enabled, delete outdated files
|
||||
// b. if archive size limit is enabled, delete empty files,
|
||||
// compute file number and size.
|
||||
// 2. If size limit is enabled:
|
||||
// a. compute how many files should be deleted
|
||||
// b. get sorted non-empty archived logs
|
||||
// c. delete what should be deleted
|
||||
void DBImpl::PurgeObsoleteWALFiles() {
|
||||
bool const ttl_enabled = options_.WAL_ttl_seconds > 0;
|
||||
bool const size_limit_enabled = options_.WAL_size_limit_MB > 0;
|
||||
if (!ttl_enabled && !size_limit_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t current_time;
|
||||
Status s = env_->GetCurrentTime(¤t_time);
|
||||
uint64_t now_seconds = static_cast<uint64_t>(current_time);
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't get current time: %s", s.ToString().c_str());
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
uint64_t const now_seconds = static_cast<uint64_t>(current_time);
|
||||
uint64_t const time_to_check = (ttl_enabled && !size_limit_enabled) ?
|
||||
options_.WAL_ttl_seconds / 2 : default_interval_to_delete_obsolete_WAL_;
|
||||
|
||||
if (options_.WAL_ttl_seconds != ULONG_MAX && options_.WAL_ttl_seconds > 0) {
|
||||
if (purge_wal_files_last_run_ + options_.WAL_ttl_seconds > now_seconds) {
|
||||
return;
|
||||
}
|
||||
std::vector<std::string> wal_files;
|
||||
std::string archival_dir = ArchivalDirectory(options_.wal_dir);
|
||||
env_->GetChildren(archival_dir, &wal_files);
|
||||
for (const auto& f : wal_files) {
|
||||
uint64_t file_m_time;
|
||||
const std::string file_path = archival_dir + "/" + f;
|
||||
const Status s = env_->GetFileModificationTime(file_path, &file_m_time);
|
||||
if (s.ok() && (now_seconds - file_m_time > options_.WAL_ttl_seconds)) {
|
||||
Status status = env_->DeleteFile(file_path);
|
||||
if (!status.ok()) {
|
||||
Log(options_.info_log,
|
||||
"Failed Deleting a WAL file Error : i%s",
|
||||
status.ToString().c_str());
|
||||
if (purge_wal_files_last_run_ + time_to_check > now_seconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
purge_wal_files_last_run_ = now_seconds;
|
||||
|
||||
std::string archival_dir = ArchivalDirectory(options_.wal_dir);
|
||||
std::vector<std::string> files;
|
||||
s = env_->GetChildren(archival_dir, &files);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't get archive files: %s", s.ToString().c_str());
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t log_files_num = 0;
|
||||
uint64_t log_file_size = 0;
|
||||
|
||||
for (auto& f : files) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == kLogFile) {
|
||||
std::string const file_path = archival_dir + "/" + f;
|
||||
if (ttl_enabled) {
|
||||
uint64_t file_m_time;
|
||||
Status const s = env_->GetFileModificationTime(file_path,
|
||||
&file_m_time);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't get file mod time: %s: %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
} // Ignore errors.
|
||||
if (now_seconds - file_m_time > options_.WAL_ttl_seconds) {
|
||||
Status const s = env_->DeleteFile(file_path);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't delete file: %s: %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (size_limit_enabled) {
|
||||
uint64_t file_size;
|
||||
Status const s = env_->GetFileSize(file_path, &file_size);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't get file size: %s: %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
return;
|
||||
} else {
|
||||
if (file_size > 0) {
|
||||
log_file_size = std::max(log_file_size, file_size);
|
||||
++log_files_num;
|
||||
} else {
|
||||
Status s = env_->DeleteFile(file_path);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't delete file: %s: %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == log_files_num || !size_limit_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t const files_keep_num = options_.WAL_size_limit_MB *
|
||||
1024 * 1024 / log_file_size;
|
||||
if (log_files_num <= files_keep_num) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t files_del_num = log_files_num - files_keep_num;
|
||||
VectorLogPtr archived_logs;
|
||||
AppendSortedWalsOfType(archival_dir, archived_logs, kArchivedLogFile);
|
||||
|
||||
if (files_del_num > archived_logs.size()) {
|
||||
Log(options_.info_log, "Trying to delete more archived log files than "
|
||||
"exist. Deleting all");
|
||||
files_del_num = archived_logs.size();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < files_del_num; ++i) {
|
||||
std::string const file_path = archived_logs[i]->PathName();
|
||||
Status const s = DeleteFile(file_path);
|
||||
if (!s.ok()) {
|
||||
Log(options_.info_log, "Can't delete file: %s: %s",
|
||||
file_path.c_str(), s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
purge_wal_files_last_run_ = now_seconds;
|
||||
}
|
||||
|
||||
// If externalTable is set, then apply recovered transactions
|
||||
@@ -697,9 +840,6 @@ Status DBImpl::Recover(VersionEdit* edit, MemTable* external_table,
|
||||
if (s.ok()) {
|
||||
if (versions_->LastSequence() < max_sequence) {
|
||||
versions_->SetLastSequence(max_sequence);
|
||||
last_flushed_sequence_ = max_sequence;
|
||||
} else {
|
||||
last_flushed_sequence_ = versions_->LastSequence();
|
||||
}
|
||||
SetTickerCount(options_.statistics, SEQUENCE_NUMBER,
|
||||
versions_->LastSequence());
|
||||
@@ -830,6 +970,7 @@ Status DBImpl::WriteLevel0TableForRecovery(MemTable* mem, VersionEdit* edit) {
|
||||
table_cache_.get(), iter, &meta,
|
||||
user_comparator(), newest_snapshot,
|
||||
earliest_seqno_in_memtable, true);
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
@@ -870,6 +1011,9 @@ Status DBImpl::WriteLevel0Table(std::vector<MemTable*> &mems, VersionEdit* edit,
|
||||
|
||||
std::vector<Iterator*> list;
|
||||
for (MemTable* m : mems) {
|
||||
Log(options_.info_log,
|
||||
"Flushing memtable with log file: %lu\n",
|
||||
m->GetLogNumber());
|
||||
list.push_back(m->NewIterator());
|
||||
}
|
||||
Iterator* iter = NewMergingIterator(&internal_comparator_, &list[0],
|
||||
@@ -894,6 +1038,7 @@ Status DBImpl::WriteLevel0Table(std::vector<MemTable*> &mems, VersionEdit* edit,
|
||||
table_cache_.get(), iter, &meta,
|
||||
user_comparator(), newest_snapshot,
|
||||
earliest_seqno_in_memtable, enable_compression);
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
}
|
||||
base->Unref();
|
||||
@@ -963,11 +1108,22 @@ Status DBImpl::FlushMemTableToOutputFile(bool* madeProgress) {
|
||||
}
|
||||
|
||||
// record the logfile_number_ before we release the mutex
|
||||
// entries mems are (implicitly) sorted in ascending order by their created
|
||||
// time. We will use the first memtable's `edit` to keep the meta info for
|
||||
// this flush.
|
||||
MemTable* m = mems[0];
|
||||
VersionEdit* edit = m->GetEdits();
|
||||
edit->SetPrevLogNumber(0);
|
||||
edit->SetLogNumber(m->GetNextLogNumber()); // Earlier logs no longer needed
|
||||
auto to_delete = m->GetLogNumber();
|
||||
// SetLogNumber(log_num) indicates logs with number smaller than log_num
|
||||
// will no longer be picked up for recovery.
|
||||
edit->SetLogNumber(
|
||||
mems.back()->GetNextLogNumber()
|
||||
);
|
||||
|
||||
std::vector<uint64_t> logs_to_delete;
|
||||
for (auto mem : mems) {
|
||||
logs_to_delete.push_back(mem->GetLogNumber());
|
||||
}
|
||||
|
||||
// This will release and re-acquire the mutex.
|
||||
Status s = WriteLevel0Table(mems, edit, &file_number);
|
||||
@@ -993,12 +1149,18 @@ Status DBImpl::FlushMemTableToOutputFile(bool* madeProgress) {
|
||||
// should store the file number in the shared state, and retry
|
||||
// However, for now, PurgeObsoleteFiles will take care of that
|
||||
// anyways.
|
||||
if (options_.purge_log_after_memtable_flush &&
|
||||
!disable_delete_obsolete_files_ &&
|
||||
to_delete > 0) {
|
||||
mutex_.Unlock();
|
||||
DeleteLogFile(to_delete);
|
||||
mutex_.Lock();
|
||||
bool should_delete_log = options_.purge_log_after_memtable_flush &&
|
||||
!disable_delete_obsolete_files_;
|
||||
if (should_delete_log) {
|
||||
for (auto log_num : logs_to_delete) {
|
||||
if (log_num < 0) {
|
||||
continue;
|
||||
}
|
||||
mutex_.Unlock();
|
||||
DeleteLogFile(log_num);
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -1024,6 +1186,7 @@ void DBImpl::CompactRange(const Slice* begin, const Slice* end,
|
||||
if (reduce_level) {
|
||||
ReFitLevel(max_level_with_files, target_level);
|
||||
}
|
||||
LogFlush(options_.info_log);
|
||||
}
|
||||
|
||||
// return the same level if it cannot be moved
|
||||
@@ -1115,7 +1278,7 @@ Status DBImpl::Flush(const FlushOptions& options) {
|
||||
return status;
|
||||
}
|
||||
|
||||
SequenceNumber DBImpl::GetLatestSequenceNumber() {
|
||||
SequenceNumber DBImpl::GetLatestSequenceNumber() const {
|
||||
return versions_->LastSequence();
|
||||
}
|
||||
|
||||
@@ -1123,7 +1286,7 @@ Status DBImpl::GetUpdatesSince(SequenceNumber seq,
|
||||
unique_ptr<TransactionLogIterator>* iter) {
|
||||
|
||||
RecordTick(options_.statistics, GET_UPDATES_SINCE_CALLS);
|
||||
if (seq > last_flushed_sequence_) {
|
||||
if (seq > versions_->LastSequence()) {
|
||||
return Status::IOError("Requested sequence not yet written in the db");
|
||||
}
|
||||
// Get all sorted Wal Files.
|
||||
@@ -1145,7 +1308,7 @@ Status DBImpl::GetUpdatesSince(SequenceNumber seq,
|
||||
storage_options_,
|
||||
seq,
|
||||
std::move(wal_files),
|
||||
&last_flushed_sequence_));
|
||||
this));
|
||||
return (*iter)->status();
|
||||
}
|
||||
|
||||
@@ -1482,6 +1645,7 @@ void DBImpl::BackgroundCallFlush() {
|
||||
Log(options_.info_log, "Waiting after background flush error: %s",
|
||||
s.ToString().c_str());
|
||||
mutex_.Unlock();
|
||||
LogFlush(options_.info_log);
|
||||
env_->SleepForMicroseconds(1000000);
|
||||
mutex_.Lock();
|
||||
}
|
||||
@@ -1519,6 +1683,7 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
Log(options_.info_log, "Waiting after background compaction error: %s",
|
||||
s.ToString().c_str());
|
||||
mutex_.Unlock();
|
||||
LogFlush(options_.info_log);
|
||||
env_->SleepForMicroseconds(1000000);
|
||||
mutex_.Lock();
|
||||
}
|
||||
@@ -1529,6 +1694,7 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
mutex_.Unlock();
|
||||
PurgeObsoleteFiles(deletion_state);
|
||||
EvictObsoleteFiles(deletion_state);
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
|
||||
}
|
||||
@@ -1614,7 +1780,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
MaybeScheduleFlushOrCompaction(); // do more compaction work in parallel.
|
||||
CompactionState* compact = new CompactionState(c.get());
|
||||
status = DoCompactionWork(compact);
|
||||
CleanupCompaction(compact);
|
||||
CleanupCompaction(compact, status);
|
||||
versions_->ReleaseCompactionFiles(c.get(), status);
|
||||
c->ReleaseInputs();
|
||||
FindObsoleteFiles(deletion_state);
|
||||
@@ -1663,7 +1829,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::CleanupCompaction(CompactionState* compact) {
|
||||
void DBImpl::CleanupCompaction(CompactionState* compact, Status status) {
|
||||
mutex_.AssertHeld();
|
||||
if (compact->builder != nullptr) {
|
||||
// May happen if we get a shutdown call in the middle of compaction
|
||||
@@ -1675,6 +1841,12 @@ void DBImpl::CleanupCompaction(CompactionState* compact) {
|
||||
for (size_t i = 0; i < compact->outputs.size(); i++) {
|
||||
const CompactionState::Output& out = compact->outputs[i];
|
||||
pending_outputs_.erase(out.number);
|
||||
|
||||
// If this file was inserted into the table cache then remove
|
||||
// them here because this compaction was not committed.
|
||||
if (!status.ok()) {
|
||||
table_cache_->Evict(out.number);
|
||||
}
|
||||
}
|
||||
delete compact;
|
||||
}
|
||||
@@ -1736,10 +1908,14 @@ Status DBImpl::OpenCompactionOutputFile(CompactionState* compact) {
|
||||
compact->outfile->SetPreallocationBlockSize(
|
||||
1.1 * versions_->MaxFileSizeForLevel(compact->compaction->output_level()));
|
||||
|
||||
compact->builder.reset(new TableBuilder(options_, compact->outfile.get(),
|
||||
compact->compaction->output_level(),
|
||||
compact->compaction->enable_compression()));
|
||||
CompressionType compression_type = GetCompressionType(
|
||||
options_, compact->compaction->output_level(),
|
||||
compact->compaction->enable_compression());
|
||||
|
||||
compact->builder.reset(
|
||||
GetTableBuilder(options_, compact->outfile.get(), compression_type));
|
||||
}
|
||||
LogFlush(options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1865,6 +2041,7 @@ inline SequenceNumber DBImpl::findEarliestVisibleSnapshot(
|
||||
}
|
||||
|
||||
Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
assert(compact);
|
||||
int64_t imm_micros = 0; // Micros spent doing imm_ compactions
|
||||
Log(options_.info_log,
|
||||
"Compacting %d@%d + %d@%d files, score %.2f slots available %d",
|
||||
@@ -1925,15 +2102,18 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
auto compaction_filter = options_.compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (!compaction_filter) {
|
||||
compaction_filter_from_factory = std::move(
|
||||
options_.compaction_filter_factory->CreateCompactionFilter());
|
||||
auto context = compact->GetFilterContext();
|
||||
compaction_filter_from_factory =
|
||||
options_.compaction_filter_factory->CreateCompactionFilter(context);
|
||||
compaction_filter = compaction_filter_from_factory.get();
|
||||
}
|
||||
|
||||
for (; input->Valid() && !shutting_down_.Acquire_Load(); ) {
|
||||
// Prioritize immutable compaction work
|
||||
// TODO: remove memtable flush from normal compaction work
|
||||
if (imm_.imm_flush_needed.NoBarrier_Load() != nullptr) {
|
||||
const uint64_t imm_start = env_->NowMicros();
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
if (imm_.IsFlushPending(options_.min_write_buffer_number_to_merge)) {
|
||||
FlushMemTableToOutputFile();
|
||||
@@ -1988,9 +2168,9 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
compaction_filter_value.clear();
|
||||
bool to_delete =
|
||||
compaction_filter->Filter(compact->compaction->level(),
|
||||
ikey.user_key, value,
|
||||
&compaction_filter_value,
|
||||
&value_changed);
|
||||
ikey.user_key, value,
|
||||
&compaction_filter_value,
|
||||
&value_changed);
|
||||
if (to_delete) {
|
||||
// make a copy of the original key
|
||||
delete_key.assign(key.data(), key.data() + key.size());
|
||||
@@ -2227,6 +2407,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
stats.bytes_written += compact->outputs[i].file_size;
|
||||
}
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
stats_[compact->compaction->output_level()].Add(stats);
|
||||
|
||||
@@ -2286,7 +2467,7 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
// Collect together all needed child iterators for mem
|
||||
std::vector<Iterator*> list;
|
||||
mem_->Ref();
|
||||
list.push_back(mem_->NewIterator(options.prefix));
|
||||
list.push_back(mem_->NewIterator(options));
|
||||
|
||||
cleanup->mem.push_back(mem_);
|
||||
|
||||
@@ -2296,7 +2477,7 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
for (unsigned int i = 0; i < immutables.size(); i++) {
|
||||
MemTable* m = immutables[i];
|
||||
m->Ref();
|
||||
list.push_back(m->NewIterator(options.prefix));
|
||||
list.push_back(m->NewIterator(options));
|
||||
cleanup->mem.push_back(m);
|
||||
}
|
||||
|
||||
@@ -2311,6 +2492,7 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
|
||||
mutex_.Unlock();
|
||||
LogFlush(options_.info_log);
|
||||
return internal_iter;
|
||||
}
|
||||
|
||||
@@ -2385,6 +2567,7 @@ Status DBImpl::GetImpl(const ReadOptions& options,
|
||||
current->Unref();
|
||||
mutex_.Unlock();
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
// Note, tickers are atomic now - no lock protection needed any more.
|
||||
RecordTick(options_.statistics, NUMBER_KEYS_READ);
|
||||
RecordTick(options_.statistics, BYTES_READ, value->size());
|
||||
@@ -2463,6 +2646,7 @@ std::vector<Status> DBImpl::MultiGet(const ReadOptions& options,
|
||||
current->Unref();
|
||||
mutex_.Unlock();
|
||||
|
||||
LogFlush(options_.info_log);
|
||||
RecordTick(options_.statistics, NUMBER_MULTIGET_CALLS);
|
||||
RecordTick(options_.statistics, NUMBER_MULTIGET_KEYS_READ, numKeys);
|
||||
RecordTick(options_.statistics, NUMBER_MULTIGET_BYTES_READ, bytesRead);
|
||||
@@ -2576,7 +2760,10 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
}
|
||||
|
||||
if (!options.disableWAL) {
|
||||
StopWatchNano timer(env_);
|
||||
StartPerfTimer(&timer);
|
||||
status = log_->AddRecord(WriteBatchInternal::Contents(updates));
|
||||
BumpPerfTime(&perf_context.wal_write_time, &timer);
|
||||
if (status.ok() && options.sync) {
|
||||
if (options_.use_fsync) {
|
||||
StopWatch(env_, options_.statistics, WAL_FILE_SYNC_MICROS);
|
||||
@@ -2599,14 +2786,17 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
}
|
||||
SetTickerCount(options_.statistics, SEQUENCE_NUMBER, last_sequence);
|
||||
}
|
||||
LogFlush(options_.info_log);
|
||||
mutex_.Lock();
|
||||
if (status.ok()) {
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
last_flushed_sequence_ = current_sequence;
|
||||
}
|
||||
}
|
||||
if (updates == &tmp_batch_) tmp_batch_.Clear();
|
||||
}
|
||||
if (options_.paranoid_checks && !status.ok() && bg_error_.ok()) {
|
||||
bg_error_ = status; // stop compaction & fail any further writes
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Writer* ready = writers_.front();
|
||||
@@ -2870,9 +3060,12 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
if (force) {
|
||||
imm_.FlushRequested();
|
||||
}
|
||||
mem_ = new MemTable(internal_comparator_, mem_rep_factory_,
|
||||
NumberLevels(), options_);
|
||||
mem_ = new MemTable(
|
||||
internal_comparator_, mem_rep_factory_, NumberLevels(), options_);
|
||||
mem_->Ref();
|
||||
Log(options_.info_log,
|
||||
"New memtable created with log file: #%lu\n",
|
||||
logfile_number_);
|
||||
mem_->SetLogNumber(logfile_number_);
|
||||
force = false; // Do not force another compaction if have room
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
@@ -3123,25 +3316,38 @@ inline void DBImpl::DelayLoggingAndReset() {
|
||||
Status DBImpl::DeleteFile(std::string name) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (!ParseFileName(name, &number, &type) ||
|
||||
(type != kTableFile)) {
|
||||
Log(options_.info_log, "DeleteFile #%lld FAILED. Invalid file name\n",
|
||||
static_cast<unsigned long long>(number));
|
||||
WalFileType log_type;
|
||||
if (!ParseFileName(name, &number, &type, &log_type) ||
|
||||
(type != kTableFile && type != kLogFile)) {
|
||||
Log(options_.info_log, "DeleteFile %s failed.\n", name.c_str());
|
||||
return Status::InvalidArgument("Invalid file name");
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (type == kLogFile) {
|
||||
// Only allow deleting archived log files
|
||||
if (log_type != kArchivedLogFile) {
|
||||
Log(options_.info_log, "DeleteFile %s failed.\n", name.c_str());
|
||||
return Status::NotSupported("Delete only supported for archived logs");
|
||||
}
|
||||
status = env_->DeleteFile(options_.wal_dir + "/" + name.c_str());
|
||||
if (!status.ok()) {
|
||||
Log(options_.info_log, "DeleteFile %s failed.\n", name.c_str());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int level;
|
||||
FileMetaData metadata;
|
||||
int maxlevel = NumberLevels();
|
||||
VersionEdit edit(maxlevel);
|
||||
DeletionState deletion_state;
|
||||
Status status;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
status = versions_->GetMetadataForFile(number, &level, &metadata);
|
||||
if (!status.ok()) {
|
||||
Log(options_.info_log, "DeleteFile #%lld FAILED. File not found\n",
|
||||
static_cast<unsigned long long>(number));
|
||||
Log(options_.info_log, "DeleteFile %s failed. File not found\n",
|
||||
name.c_str());
|
||||
return Status::InvalidArgument("File not found");
|
||||
}
|
||||
assert((level > 0) && (level < maxlevel));
|
||||
@@ -3149,8 +3355,7 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
// If the file is being compacted no need to delete.
|
||||
if (metadata.being_compacted) {
|
||||
Log(options_.info_log,
|
||||
"DeleteFile #%lld Skipped. File about to be compacted\n",
|
||||
static_cast<unsigned long long>(number));
|
||||
"DeleteFile %s Skipped. File about to be compacted\n", name.c_str());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -3160,8 +3365,7 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
for (int i = level + 1; i < maxlevel; i++) {
|
||||
if (versions_->NumLevelFiles(i) != 0) {
|
||||
Log(options_.info_log,
|
||||
"DeleteFile #%lld FAILED. File not in last level\n",
|
||||
static_cast<unsigned long long>(number));
|
||||
"DeleteFile %s FAILED. File not in last level\n", name.c_str());
|
||||
return Status::InvalidArgument("File not in last level");
|
||||
}
|
||||
}
|
||||
@@ -3171,6 +3375,7 @@ Status DBImpl::DeleteFile(std::string name) {
|
||||
FindObsoleteFiles(deletion_state);
|
||||
}
|
||||
} // lock released here
|
||||
LogFlush(options_.info_log);
|
||||
|
||||
if (status.ok()) {
|
||||
// remove files outside the db-lock
|
||||
|
||||
+20
-7
@@ -73,8 +73,7 @@ class DBImpl : public DB {
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable = true);
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files);
|
||||
virtual Status DeleteWalFiles(const VectorLogPtr& files);
|
||||
virtual SequenceNumber GetLatestSequenceNumber();
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const;
|
||||
virtual Status GetUpdatesSince(SequenceNumber seq_number,
|
||||
unique_ptr<TransactionLogIterator>* iter);
|
||||
virtual Status DeleteFile(std::string name);
|
||||
@@ -117,6 +116,11 @@ class DBImpl : public DB {
|
||||
// get total level0 file size. Only for testing.
|
||||
uint64_t TEST_GetLevel0TotalSize() { return versions_->NumLevelBytes(0);}
|
||||
|
||||
void TEST_SetDefaultTimeToCheck(uint64_t default_interval_to_delete_obsolete_WAL)
|
||||
{
|
||||
default_interval_to_delete_obsolete_WAL_ = default_interval_to_delete_obsolete_WAL;
|
||||
}
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
@@ -195,7 +199,7 @@ class DBImpl : public DB {
|
||||
void BackgroundCallFlush();
|
||||
Status BackgroundCompaction(bool* madeProgress,DeletionState& deletion_state);
|
||||
Status BackgroundFlush(bool* madeProgress);
|
||||
void CleanupCompaction(CompactionState* compact);
|
||||
void CleanupCompaction(CompactionState* compact, Status status);
|
||||
Status DoCompactionWork(CompactionState* compact);
|
||||
|
||||
Status OpenCompactionOutputFile(CompactionState* compact);
|
||||
@@ -324,6 +328,10 @@ class DBImpl : public DB {
|
||||
// last time stats were dumped to LOG
|
||||
std::atomic<uint64_t> last_stats_dump_time_microsec_;
|
||||
|
||||
// obsolete files will be deleted every this seconds if ttl deletion is
|
||||
// enabled and archive size_limit is disabled.
|
||||
uint64_t default_interval_to_delete_obsolete_WAL_;
|
||||
|
||||
// These count the number of microseconds for which MakeRoomForWrite stalls.
|
||||
uint64_t stall_level0_slowdown_;
|
||||
uint64_t stall_memtable_compaction_;
|
||||
@@ -403,10 +411,6 @@ class DBImpl : public DB {
|
||||
// count of the number of contiguous delaying writes
|
||||
int delayed_writes_;
|
||||
|
||||
// store the last flushed sequence.
|
||||
// Used by transaction log iterator.
|
||||
SequenceNumber last_flushed_sequence_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions storage_options_;
|
||||
|
||||
@@ -445,4 +449,13 @@ extern Options SanitizeOptions(const std::string& db,
|
||||
const InternalFilterPolicy* ipolicy,
|
||||
const Options& src);
|
||||
|
||||
|
||||
// Determine compression type, based on user options, level of the output
|
||||
// file and whether compression is disabled.
|
||||
// If enable_compression is false, then compression is always disabled no
|
||||
// matter what the values of the other two parameters are.
|
||||
// Otherwise, the compression type is determined based on options and level.
|
||||
CompressionType GetCompressionType(const Options& options, int level,
|
||||
const bool enable_compression);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -29,11 +29,10 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "port/port.h"
|
||||
#include "table/block.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/table.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
+418
-113
@@ -20,7 +20,7 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/table.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -101,6 +101,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
// Force write to manifest files to fail while this pointer is non-nullptr
|
||||
port::AtomicPointer manifest_write_error_;
|
||||
|
||||
// Force write to log files to fail while this pointer is non-nullptr
|
||||
port::AtomicPointer log_write_error_;
|
||||
|
||||
bool count_random_reads_;
|
||||
anon::AtomicCounter random_read_counter_;
|
||||
|
||||
@@ -113,6 +116,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
count_random_reads_ = false;
|
||||
manifest_sync_error_.Release_Store(nullptr);
|
||||
manifest_write_error_.Release_Store(nullptr);
|
||||
log_write_error_.Release_Store(nullptr);
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r,
|
||||
@@ -168,6 +172,24 @@ class SpecialEnv : public EnvWrapper {
|
||||
}
|
||||
}
|
||||
};
|
||||
class LogFile : public WritableFile {
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
unique_ptr<WritableFile> base_;
|
||||
public:
|
||||
LogFile(SpecialEnv* env, unique_ptr<WritableFile>&& b)
|
||||
: env_(env), base_(std::move(b)) { }
|
||||
Status Append(const Slice& data) {
|
||||
if (env_->log_write_error_.Acquire_Load() != nullptr) {
|
||||
return Status::IOError("simulated writer error");
|
||||
} else {
|
||||
return base_->Append(data);
|
||||
}
|
||||
}
|
||||
Status Close() { return base_->Close(); }
|
||||
Status Flush() { return base_->Flush(); }
|
||||
Status Sync() { return base_->Sync(); }
|
||||
};
|
||||
|
||||
if (non_writable_.Acquire_Load() != nullptr) {
|
||||
return Status::IOError("simulated write error");
|
||||
@@ -179,6 +201,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
r->reset(new SSTableFile(this, std::move(*r)));
|
||||
} else if (strstr(f.c_str(), "MANIFEST") != nullptr) {
|
||||
r->reset(new ManifestFile(this, std::move(*r)));
|
||||
} else if (strstr(f.c_str(), "log") != nullptr) {
|
||||
r->reset(new LogFile(this, std::move(*r)));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -238,6 +262,7 @@ class DBTest {
|
||||
kDeletesFilterFirst,
|
||||
kPrefixHashRep,
|
||||
kUniversalCompaction,
|
||||
kCompressedBlockCache,
|
||||
kEnd
|
||||
};
|
||||
int option_config_;
|
||||
@@ -364,6 +389,9 @@ class DBTest {
|
||||
case kUniversalCompaction:
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
break;
|
||||
case kCompressedBlockCache:
|
||||
options.block_cache_compressed = NewLRUCache(8*1024*1024);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -414,11 +442,11 @@ class DBTest {
|
||||
return DB::Open(opts, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
Status Put(const Slice& k, const Slice& v, WriteOptions wo = WriteOptions()) {
|
||||
if (kMergePut == option_config_ ) {
|
||||
return db_->Merge(WriteOptions(), k, v);
|
||||
return db_->Merge(wo, k, v);
|
||||
} else {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
return db_->Put(wo, k, v);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +456,7 @@ class DBTest {
|
||||
|
||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::string result;
|
||||
Status s = db_->Get(options, k, &result);
|
||||
@@ -631,7 +660,7 @@ class DBTest {
|
||||
}
|
||||
|
||||
std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
|
||||
const SequenceNumber seq) {
|
||||
const SequenceNumber seq) {
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(seq, &iter);
|
||||
ASSERT_OK(status);
|
||||
@@ -1729,6 +1758,90 @@ TEST(DBTest, CompactionsGenerateMultipleFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, CompressedCache) {
|
||||
int num_iter = 80;
|
||||
|
||||
// Run this test three iterations.
|
||||
// Iteration 1: only a uncompressed block cache
|
||||
// Iteration 2: only a compressed block cache
|
||||
// Iteration 3: both block cache and compressed cache
|
||||
for (int iter = 0; iter < 3; iter++) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 64*1024; // small write buffer
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
options.block_cache = NewLRUCache(8*1024);
|
||||
options.block_cache_compressed = nullptr;
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
options.no_block_cache = true;
|
||||
options.block_cache = nullptr;
|
||||
options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
options.block_cache = NewLRUCache(1024);
|
||||
options.block_cache_compressed = NewLRUCache(8*1024);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
Reopen(&options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Write 8MB (80 values, each 100K)
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
std::vector<std::string> values;
|
||||
std::string str;
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
if (i % 4 == 0) { // high compression ratio
|
||||
str = RandomString(&rnd, 1000);
|
||||
}
|
||||
values.push_back(str);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
|
||||
// flush all data from memtable so that reads are from block cache
|
||||
dbfull()->Flush(FlushOptions());
|
||||
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
|
||||
// check that we triggered the appropriate code paths in the cache
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
ASSERT_GT(options.statistics.get()->getTickerCount(BLOCK_CACHE_MISS),
|
||||
0);
|
||||
ASSERT_EQ(options.statistics.get()->getTickerCount
|
||||
(BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
ASSERT_EQ(options.statistics.get()->getTickerCount(BLOCK_CACHE_MISS),
|
||||
0);
|
||||
ASSERT_GT(options.statistics.get()->getTickerCount
|
||||
(BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
ASSERT_GT(options.statistics.get()->getTickerCount(BLOCK_CACHE_MISS),
|
||||
0);
|
||||
ASSERT_GT(options.statistics.get()->getTickerCount
|
||||
(BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, CompactionTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100<<10; //100KB
|
||||
@@ -2282,6 +2395,71 @@ TEST(DBTest, RepeatedWritesToSameKey) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, InPlaceUpdate) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
|
||||
// Update key with values of smaller size
|
||||
Reopen(&options);
|
||||
int numValues = 10;
|
||||
for (int i = numValues; i > 0; i--) {
|
||||
std::string value = DummyString(i, 'a');
|
||||
ASSERT_OK(Put("key", value));
|
||||
ASSERT_EQ(value, Get("key"));
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
Iterator* iter = dbfull()->TEST_NewInternalIterator();
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(iter->status().ok(), true);
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
count++;
|
||||
// All updates with the same sequence number.
|
||||
ASSERT_EQ(ikey.sequence, (unsigned)1);
|
||||
iter->Next();
|
||||
}
|
||||
// Only 1 instance for that key.
|
||||
ASSERT_EQ(count, 1);
|
||||
delete iter;
|
||||
|
||||
// Update key with values of larger size
|
||||
DestroyAndReopen(&options);
|
||||
numValues = 10;
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
std::string value = DummyString(i, 'a');
|
||||
ASSERT_OK(Put("key", value));
|
||||
ASSERT_EQ(value, Get("key"));
|
||||
}
|
||||
|
||||
count = 0;
|
||||
iter = dbfull()->TEST_NewInternalIterator();
|
||||
iter->SeekToFirst();
|
||||
ASSERT_EQ(iter->status().ok(), true);
|
||||
int seq = numValues;
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
count++;
|
||||
// No inplace updates. All updates are puts with new seq number
|
||||
ASSERT_EQ(ikey.sequence, (unsigned)seq--);
|
||||
iter->Next();
|
||||
}
|
||||
// All 10 updates exist in the internal iterator
|
||||
ASSERT_EQ(count, numValues);
|
||||
delete iter;
|
||||
|
||||
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// This is a static filter used for filtering
|
||||
// kvs during the compaction process.
|
||||
static int cfilter_count;
|
||||
@@ -2341,7 +2519,7 @@ class ChangeFilter : public CompactionFilter {
|
||||
class KeepFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter() override {
|
||||
CreateCompactionFilter(const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(new KeepFilter());
|
||||
}
|
||||
|
||||
@@ -2353,7 +2531,7 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter() override {
|
||||
CreateCompactionFilter(const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
||||
}
|
||||
|
||||
@@ -2367,7 +2545,7 @@ class ChangeFilterFactory : public CompactionFilterFactory {
|
||||
explicit ChangeFilterFactory(int argv) : argv_(argv) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter() override {
|
||||
CreateCompactionFilter(const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(new ChangeFilter(argv_));
|
||||
}
|
||||
|
||||
@@ -3295,6 +3473,49 @@ TEST(DBTest, ManifestWriteError) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, PutFailsParanoid) {
|
||||
// Test the following:
|
||||
// (a) A random put fails in paranoid mode (simulate by sync fail)
|
||||
// (b) All other puts have to fail, even if writes would succeed
|
||||
// (c) All of that should happen ONLY if paranoid_checks = true
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(&options);
|
||||
Status s;
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.Release_Store(env_);
|
||||
s = Put("foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.Release_Store(nullptr);
|
||||
s = Put("foo3", "bar3");
|
||||
// the next put should fail, too
|
||||
ASSERT_TRUE(!s.ok());
|
||||
// but we're still able to read
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// do the same thing with paranoid checks off
|
||||
options.paranoid_checks = false;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.Release_Store(env_);
|
||||
s = Put("foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.Release_Store(nullptr);
|
||||
s = Put("foo3", "bar3");
|
||||
// the next put should NOT fail
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
TEST(DBTest, FilesDeletedAfterCompaction) {
|
||||
do {
|
||||
ASSERT_OK(Put("foo", "v2"));
|
||||
@@ -3590,28 +3811,28 @@ std::vector<std::uint64_t> ListLogFiles(Env* env, const std::string& path) {
|
||||
return std::move(log_files);
|
||||
}
|
||||
|
||||
TEST(DBTest, WALArchival) {
|
||||
TEST(DBTest, WALArchivalTtl) {
|
||||
do {
|
||||
std::string value(1024, '1');
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.WAL_ttl_seconds = 1000;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
|
||||
// TEST : Create DB with a ttl.
|
||||
// TEST : Create DB with a ttl and no size limit.
|
||||
// Put some keys. Count the log files present in the DB just after insert.
|
||||
// Re-open db. Causes deletion/archival to take place.
|
||||
// Assert that the files moved under "/archive".
|
||||
// Reopen db with small ttl.
|
||||
// Assert that archive was removed.
|
||||
|
||||
std::string archiveDir = ArchivalDirectory(dbname_);
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(Put(Key(10*i+j), value));
|
||||
ASSERT_OK(Put(Key(10 * i + j), DummyString(1024)));
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logFiles = ListLogFiles(env_, dbname_);
|
||||
std::vector<uint64_t> log_files = ListLogFiles(env_, dbname_);
|
||||
|
||||
options.create_if_missing = false;
|
||||
Reopen(&options);
|
||||
@@ -3619,56 +3840,106 @@ TEST(DBTest, WALArchival) {
|
||||
std::vector<uint64_t> logs = ListLogFiles(env_, archiveDir);
|
||||
std::set<uint64_t> archivedFiles(logs.begin(), logs.end());
|
||||
|
||||
for (auto& log : logFiles) {
|
||||
for (auto& log : log_files) {
|
||||
ASSERT_TRUE(archivedFiles.find(log) != archivedFiles.end());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logFiles = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(logFiles.size() > 0);
|
||||
std::vector<uint64_t> log_files = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(log_files.size() > 0);
|
||||
|
||||
options.WAL_ttl_seconds = 1;
|
||||
env_->SleepForMicroseconds(2*1000*1000);
|
||||
env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
Reopen(&options);
|
||||
|
||||
logFiles = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(logFiles.size() == 0);
|
||||
log_files = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(log_files.empty());
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, WALClear) {
|
||||
uint64_t GetLogDirSize(std::string dir_path, SpecialEnv* env) {
|
||||
uint64_t dir_size = 0;
|
||||
std::vector<std::string> files;
|
||||
env->GetChildren(dir_path, &files);
|
||||
for (auto& f : files) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == kLogFile) {
|
||||
std::string const file_path = dir_path + "/" + f;
|
||||
uint64_t file_size;
|
||||
env->GetFileSize(file_path, &file_size);
|
||||
dir_size += file_size;
|
||||
}
|
||||
}
|
||||
return dir_size;
|
||||
}
|
||||
|
||||
TEST(DBTest, WALArchivalSizeLimit) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.WAL_ttl_seconds = 1;
|
||||
options.WAL_ttl_seconds = 0;
|
||||
options.WAL_size_limit_MB = 1000;
|
||||
|
||||
for (int j = 0; j < 10; ++j)
|
||||
for (int i = 0; i < 10; ++i)
|
||||
ASSERT_OK(Put(Key(10*i+j), DummyString(1024)));
|
||||
// TEST : Create DB with huge size limit and no ttl.
|
||||
// Put some keys. Count the archived log files present in the DB
|
||||
// just after insert. Assert that there are many enough.
|
||||
// Change size limit. Re-open db.
|
||||
// Assert that archive is not greater than WAL_size_limit_MB.
|
||||
// Set ttl and time_to_check_ to small values. Re-open db.
|
||||
// Assert that there are no archived logs left.
|
||||
|
||||
DestroyAndReopen(&options);
|
||||
for (int i = 0; i < 128 * 128; ++i) {
|
||||
ASSERT_OK(Put(Key(i), DummyString(1024)));
|
||||
}
|
||||
Reopen(&options);
|
||||
|
||||
std::string archive_dir = ArchivalDirectory(dbname_);
|
||||
std::vector<std::uint64_t> log_files = ListLogFiles(env_, archive_dir);
|
||||
ASSERT_TRUE(!log_files.empty());
|
||||
env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
ASSERT_TRUE(log_files.size() > 2);
|
||||
|
||||
options.WAL_size_limit_MB = 8;
|
||||
Reopen(&options);
|
||||
dbfull()->TEST_PurgeObsoleteteWAL();
|
||||
|
||||
uint64_t archive_size = GetLogDirSize(archive_dir, env_);
|
||||
ASSERT_TRUE(archive_size <= options.WAL_size_limit_MB * 1024 * 1024);
|
||||
|
||||
options.WAL_ttl_seconds = 1;
|
||||
dbfull()->TEST_SetDefaultTimeToCheck(1);
|
||||
env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
Reopen(&options);
|
||||
dbfull()->TEST_PurgeObsoleteteWAL();
|
||||
|
||||
log_files = ListLogFiles(env_, archive_dir);
|
||||
ASSERT_TRUE(log_files.empty());
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
void ExpectRecords(
|
||||
const int expected_no_records,
|
||||
std::unique_ptr<TransactionLogIterator>& iter) {
|
||||
int i = 0;
|
||||
SequenceNumber ReadRecords(
|
||||
std::unique_ptr<TransactionLogIterator>& iter,
|
||||
int& count) {
|
||||
count = 0;
|
||||
SequenceNumber lastSequence = 0;
|
||||
BatchResult res;
|
||||
while (iter->Valid()) {
|
||||
BatchResult res = iter->GetBatch();
|
||||
res = iter->GetBatch();
|
||||
ASSERT_TRUE(res.sequence > lastSequence);
|
||||
++i;
|
||||
++count;
|
||||
lastSequence = res.sequence;
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(i, expected_no_records);
|
||||
return res.sequence;
|
||||
}
|
||||
|
||||
void ExpectRecords(
|
||||
const int expected_no_records,
|
||||
std::unique_ptr<TransactionLogIterator>& iter) {
|
||||
int num_records;
|
||||
ReadRecords(iter, num_records);
|
||||
ASSERT_EQ(num_records, expected_no_records);
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIterator) {
|
||||
@@ -3755,6 +4026,35 @@ TEST(DBTest, TransactionLogIteratorCheckAfterRestart) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorCorruptedLog) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
Put("key"+std::to_string(i), DummyString(10));
|
||||
}
|
||||
dbfull()->Flush(FlushOptions());
|
||||
// Corrupt this log to create a gap
|
||||
rocksdb::VectorLogPtr wal_files;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
|
||||
const auto logfilePath = dbname_ + "/" + wal_files.front()->PathName();
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
truncate(logfilePath.c_str(), wal_files.front()->SizeFileBytes() / 2));
|
||||
// Insert a new entry to a new log file
|
||||
Put("key1025", DummyString(10));
|
||||
// Try to read from the beginning. Should stop before the gap and read less
|
||||
// than 1025 entries
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
int count;
|
||||
int last_sequence_read = ReadRecords(iter, count);
|
||||
ASSERT_LT(last_sequence_read, 1025);
|
||||
// Try to read past the gap, should be able to seek to key1025
|
||||
auto iter2 = OpenTransactionLogIter(last_sequence_read + 1);
|
||||
ExpectRecords(1, iter2);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorBatchOperations) {
|
||||
do {
|
||||
Options options = OptionsForLogIterTest();
|
||||
@@ -4104,11 +4404,11 @@ class ModelDB: public DB {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status DeleteWalFiles(const VectorLogPtr& files) {
|
||||
virtual Status DeleteFile(std::string name) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() {
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const {
|
||||
return 0;
|
||||
}
|
||||
virtual Status GetUpdatesSince(rocksdb::SequenceNumber,
|
||||
@@ -4390,85 +4690,90 @@ void PrefixScanInit(DBTest *dbtest) {
|
||||
}
|
||||
|
||||
TEST(DBTest, PrefixScan) {
|
||||
ReadOptions ro = ReadOptions();
|
||||
int count;
|
||||
Slice prefix;
|
||||
Slice key;
|
||||
char buf[100];
|
||||
Iterator* iter;
|
||||
snprintf(buf, sizeof(buf), "03______:");
|
||||
prefix = Slice(buf, 8);
|
||||
key = Slice(buf, 9);
|
||||
auto prefix_extractor = NewFixedPrefixTransform(8);
|
||||
auto memtable_factory =
|
||||
std::make_shared<PrefixHashRepFactory>(prefix_extractor);
|
||||
|
||||
// db configs
|
||||
env_->count_random_reads_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.block_cache = NewLRUCache(0); // Prevent cache hits
|
||||
options.filter_policy = NewBloomFilterPolicy(10);
|
||||
options.prefix_extractor = prefix_extractor;
|
||||
options.whole_key_filtering = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_compactions = 2;
|
||||
options.create_if_missing = true;
|
||||
options.disable_seek_compaction = true;
|
||||
options.memtable_factory = memtable_factory;
|
||||
|
||||
// prefix specified, with blooms: 2 RAND I/Os
|
||||
// SeekToFirst
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
ro.prefix = &prefix;
|
||||
iter = db_->NewIterator(ro);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
assert(iter->key().starts_with(prefix));
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
||||
|
||||
// prefix specified, with blooms: 2 RAND I/Os
|
||||
// Seek
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
ro.prefix = &prefix;
|
||||
iter = db_->NewIterator(ro);
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
assert(iter->key().starts_with(prefix));
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
||||
|
||||
// no prefix specified: 11 RAND I/Os
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
||||
if (! iter->key().starts_with(prefix)) {
|
||||
break;
|
||||
for (int it = 0; it < 2; ++it) {
|
||||
ReadOptions ro = ReadOptions();
|
||||
int count;
|
||||
Slice prefix;
|
||||
Slice key;
|
||||
char buf[100];
|
||||
Iterator* iter;
|
||||
snprintf(buf, sizeof(buf), "03______:");
|
||||
prefix = Slice(buf, 8);
|
||||
key = Slice(buf, 9);
|
||||
auto prefix_extractor = NewFixedPrefixTransform(8);
|
||||
// db configs
|
||||
env_->count_random_reads_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.block_cache = NewLRUCache(0); // Prevent cache hits
|
||||
options.filter_policy = NewBloomFilterPolicy(10);
|
||||
options.prefix_extractor = prefix_extractor;
|
||||
options.whole_key_filtering = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_compactions = 2;
|
||||
options.create_if_missing = true;
|
||||
options.disable_seek_compaction = true;
|
||||
if (it == 0) {
|
||||
options.memtable_factory = std::make_shared<PrefixHashRepNoLockFactory>(
|
||||
prefix_extractor);
|
||||
} else {
|
||||
options.memtable_factory = std::make_shared<PrefixHashRepFactory>(
|
||||
prefix_extractor);
|
||||
}
|
||||
count++;
|
||||
|
||||
// prefix specified, with blooms: 2 RAND I/Os
|
||||
// SeekToFirst
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
ro.prefix = &prefix;
|
||||
iter = db_->NewIterator(ro);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
assert(iter->key().starts_with(prefix));
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
||||
|
||||
// prefix specified, with blooms: 2 RAND I/Os
|
||||
// Seek
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
ro.prefix = &prefix;
|
||||
iter = db_->NewIterator(ro);
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
assert(iter->key().starts_with(prefix));
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 2);
|
||||
|
||||
// no prefix specified: 11 RAND I/Os
|
||||
DestroyAndReopen(&options);
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
||||
if (! iter->key().starts_with(prefix)) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 11);
|
||||
Close();
|
||||
delete options.filter_policy;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(count, 2);
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), 11);
|
||||
Close();
|
||||
delete options.filter_policy;
|
||||
}
|
||||
|
||||
std::string MakeKey(unsigned int num) {
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
+39
-1
@@ -15,6 +15,7 @@
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
@@ -36,6 +37,8 @@ class DeleteFileTest {
|
||||
options_.write_buffer_size = 1024*1024*1000;
|
||||
options_.target_file_size_base = 1024*1024*1000;
|
||||
options_.max_bytes_for_level_base = 1024*1024*1000;
|
||||
options_.WAL_ttl_seconds = 300; // Used to test log files
|
||||
options_.WAL_size_limit_MB = 1024; // Used to test log files
|
||||
dbname_ = test::TmpDir() + "/deletefile_test";
|
||||
DestroyDB(dbname_, options_);
|
||||
numlevels_ = 7;
|
||||
@@ -153,7 +156,6 @@ TEST(DeleteFileTest, AddKeysAndQueryLevels) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
|
||||
TEST(DeleteFileTest, DeleteFileWithIterator) {
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
@@ -184,6 +186,42 @@ TEST(DeleteFileTest, DeleteFileWithIterator) {
|
||||
delete it;
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST(DeleteFileTest, DeleteLogFiles) {
|
||||
AddKeys(10, 0);
|
||||
VectorLogPtr logfiles;
|
||||
db_->GetSortedWalFiles(logfiles);
|
||||
ASSERT_GT(logfiles.size(), 0UL);
|
||||
// Take the last log file which is expected to be alive and try to delete it
|
||||
// Should not succeed because live logs are not allowed to be deleted
|
||||
std::unique_ptr<LogFile> alive_log = std::move(logfiles.back());
|
||||
ASSERT_EQ(alive_log->Type(), kAliveLogFile);
|
||||
ASSERT_TRUE(env_->FileExists(dbname_ + "/" + alive_log->PathName()));
|
||||
fprintf(stdout, "Deleting alive log file %s\n",
|
||||
alive_log->PathName().c_str());
|
||||
ASSERT_TRUE(!db_->DeleteFile(alive_log->PathName()).ok());
|
||||
ASSERT_TRUE(env_->FileExists(dbname_ + "/" + alive_log->PathName()));
|
||||
logfiles.clear();
|
||||
|
||||
// Call Flush to bring about a new working log file and add more keys
|
||||
// Call Flush again to flush out memtable and move alive log to archived log
|
||||
// and try to delete the archived log file
|
||||
FlushOptions fopts;
|
||||
db_->Flush(fopts);
|
||||
AddKeys(10, 0);
|
||||
db_->Flush(fopts);
|
||||
db_->GetSortedWalFiles(logfiles);
|
||||
ASSERT_GT(logfiles.size(), 0UL);
|
||||
std::unique_ptr<LogFile> archived_log = std::move(logfiles.front());
|
||||
ASSERT_EQ(archived_log->Type(), kArchivedLogFile);
|
||||
ASSERT_TRUE(env_->FileExists(dbname_ + "/" + archived_log->PathName()));
|
||||
fprintf(stdout, "Deleting archived log file %s\n",
|
||||
archived_log->PathName().c_str());
|
||||
ASSERT_OK(db_->DeleteFile(archived_log->PathName()));
|
||||
ASSERT_TRUE(!env_->FileExists(dbname_ + "/" + archived_log->PathName()));
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
} //namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+21
-3
@@ -92,7 +92,7 @@ std::string LockFileName(const std::string& dbname) {
|
||||
}
|
||||
|
||||
std::string TempFileName(const std::string& dbname, uint64_t number) {
|
||||
assert(number > 0);
|
||||
assert(number >= 0);
|
||||
return MakeFileName(dbname, number, "dbtmp");
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ std::string IdentityFileName(const std::string& dbname) {
|
||||
// Disregards / at the beginning
|
||||
bool ParseFileName(const std::string& fname,
|
||||
uint64_t* number,
|
||||
FileType* type) {
|
||||
FileType* type,
|
||||
WalFileType* log_type) {
|
||||
Slice rest(fname);
|
||||
if (fname.length() > 1 && fname[0] == '/') {
|
||||
rest.remove_prefix(1);
|
||||
@@ -194,6 +195,17 @@ bool ParseFileName(const std::string& fname,
|
||||
} else {
|
||||
// Avoid strtoull() to keep filename format independent of the
|
||||
// current locale
|
||||
bool archive_dir_found = false;
|
||||
if (rest.starts_with(ARCHIVAL_DIR)) {
|
||||
if (rest.size() <= ARCHIVAL_DIR.size()) {
|
||||
return false;
|
||||
}
|
||||
rest.remove_prefix(ARCHIVAL_DIR.size() + 1); // Add 1 to remove / also
|
||||
if (log_type) {
|
||||
*log_type = kArchivedLogFile;
|
||||
}
|
||||
archive_dir_found = true;
|
||||
}
|
||||
uint64_t num;
|
||||
if (!ConsumeDecimalNumber(&rest, &num)) {
|
||||
return false;
|
||||
@@ -201,6 +213,11 @@ bool ParseFileName(const std::string& fname,
|
||||
Slice suffix = rest;
|
||||
if (suffix == Slice(".log")) {
|
||||
*type = kLogFile;
|
||||
if (log_type && !archive_dir_found) {
|
||||
*log_type = kAliveLogFile;
|
||||
}
|
||||
} else if (archive_dir_found) {
|
||||
return false; // Archive dir can contain only log files
|
||||
} else if (suffix == Slice(".sst")) {
|
||||
*type = kTableFile;
|
||||
} else if (suffix == Slice(".dbtmp")) {
|
||||
@@ -234,7 +251,8 @@ Status SetCurrentFile(Env* env, const std::string& dbname,
|
||||
Status SetIdentityFile(Env* env, const std::string& dbname) {
|
||||
std::string id = env->GenerateUniqueId();
|
||||
assert(!id.empty());
|
||||
std::string tmp = TempFileName(dbname, id.size());
|
||||
// Reserve the filename dbname/000000.dbtmp for the temporary identity file
|
||||
std::string tmp = TempFileName(dbname, 0);
|
||||
Status s = WriteStringToFileSync(env, id, tmp);
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp, IdentityFileName(dbname));
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@
|
||||
#include <string>
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -93,7 +94,8 @@ extern std::string IdentityFileName(const std::string& dbname);
|
||||
// filename was successfully parsed, returns true. Else return false.
|
||||
extern bool ParseFileName(const std::string& filename,
|
||||
uint64_t* number,
|
||||
FileType* type);
|
||||
FileType* type,
|
||||
WalFileType* log_type = nullptr);
|
||||
|
||||
// Make the CURRENT file point to the descriptor file with the
|
||||
// specified number.
|
||||
|
||||
+99
-15
@@ -17,8 +17,18 @@
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/murmurhash.h"
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<rocksdb::Slice> {
|
||||
size_t operator()(const rocksdb::Slice& slice) const {
|
||||
return MurmurHash(slice.data(), slice.size(), 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
@@ -35,7 +45,10 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
edit_(numlevel),
|
||||
first_seqno_(0),
|
||||
mem_next_logfile_number_(0),
|
||||
mem_logfile_number_(0) { }
|
||||
mem_logfile_number_(0),
|
||||
locks_(options.inplace_update_support
|
||||
? options.inplace_update_num_locks
|
||||
: 0) { }
|
||||
|
||||
MemTable::~MemTable() {
|
||||
assert(refs_ == 0);
|
||||
@@ -71,11 +84,16 @@ static const char* EncodeKey(std::string* scratch, const Slice& target) {
|
||||
|
||||
class MemTableIterator: public Iterator {
|
||||
public:
|
||||
explicit MemTableIterator(MemTableRep* table)
|
||||
: iter_(table->GetIterator()) { }
|
||||
|
||||
MemTableIterator(MemTableRep* table, const Slice* prefix)
|
||||
: iter_(table->GetPrefixIterator(*prefix)) { }
|
||||
MemTableIterator(MemTableRep* table, const ReadOptions& options)
|
||||
: iter_() {
|
||||
if (options.prefix) {
|
||||
iter_ = table->GetPrefixIterator(*options.prefix);
|
||||
} else if (options.prefix_seek) {
|
||||
iter_ = table->GetDynamicPrefixIterator();
|
||||
} else {
|
||||
iter_ = table->GetIterator();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool Valid() const { return iter_->Valid(); }
|
||||
virtual void Seek(const Slice& k) { iter_->Seek(EncodeKey(&tmp_, k)); }
|
||||
@@ -102,12 +120,12 @@ class MemTableIterator: public Iterator {
|
||||
void operator=(const MemTableIterator&);
|
||||
};
|
||||
|
||||
Iterator* MemTable::NewIterator(const Slice* prefix) {
|
||||
if (prefix) {
|
||||
return new MemTableIterator(table_.get(), prefix);
|
||||
} else {
|
||||
return new MemTableIterator(table_.get());
|
||||
}
|
||||
Iterator* MemTable::NewIterator(const ReadOptions& options) {
|
||||
return new MemTableIterator(table_.get(), options);
|
||||
}
|
||||
|
||||
port::RWMutex* MemTable::GetLock(const Slice& key) {
|
||||
return &locks_[std::hash<Slice>()(key) % locks_.size()];
|
||||
}
|
||||
|
||||
void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
@@ -169,14 +187,16 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
// all entries with overly large sequence numbers.
|
||||
const char* entry = iter->key();
|
||||
uint32_t key_length;
|
||||
const char* key_ptr = GetVarint32Ptr(entry, entry+5, &key_length);
|
||||
const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
|
||||
if (comparator_.comparator.user_comparator()->Compare(
|
||||
Slice(key_ptr, key_length - 8),
|
||||
key.user_key()) == 0) {
|
||||
Slice(key_ptr, key_length - 8), key.user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
case kTypeValue: {
|
||||
if (options.inplace_update_support) {
|
||||
GetLock(key.user_key())->ReadLock();
|
||||
}
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
*s = Status::OK();
|
||||
if (merge_in_progress) {
|
||||
@@ -189,6 +209,9 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
} else {
|
||||
value->assign(v.data(), v.size());
|
||||
}
|
||||
if (options.inplace_update_support) {
|
||||
GetLock(key.user_key())->Unlock();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case kTypeDeletion: {
|
||||
@@ -243,4 +266,65 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MemTable::Update(SequenceNumber seq, ValueType type,
|
||||
const Slice& key,
|
||||
const Slice& value) {
|
||||
LookupKey lkey(key, seq);
|
||||
Slice memkey = lkey.memtable_key();
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> iter(
|
||||
table_.get()->GetIterator(lkey.user_key()));
|
||||
iter->Seek(memkey.data());
|
||||
|
||||
if (iter->Valid()) {
|
||||
// entry format is:
|
||||
// klength varint32
|
||||
// userkey char[klength-8]
|
||||
// tag uint64
|
||||
// vlength varint32
|
||||
// value char[vlength]
|
||||
// Check that it belongs to same user key. We do not check the
|
||||
// sequence number since the Seek() call above should have skipped
|
||||
// all entries with overly large sequence numbers.
|
||||
const char* entry = iter->key();
|
||||
uint32_t key_length;
|
||||
const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
|
||||
if (comparator_.comparator.user_comparator()->Compare(
|
||||
Slice(key_ptr, key_length - 8), lkey.user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
case kTypeValue: {
|
||||
uint32_t vlength;
|
||||
GetVarint32Ptr(key_ptr + key_length,
|
||||
key_ptr + key_length+5, &vlength);
|
||||
// Update value, if newValue size <= curValue size
|
||||
if (value.size() <= vlength) {
|
||||
char* p = EncodeVarint32(const_cast<char*>(key_ptr) + key_length,
|
||||
value.size());
|
||||
WriteLock wl(GetLock(lkey.user_key()));
|
||||
memcpy(p, value.data(), value.size());
|
||||
assert(
|
||||
(p + value.size()) - entry ==
|
||||
(unsigned) (VarintLength(key_length) +
|
||||
key_length +
|
||||
VarintLength(value.size()) +
|
||||
value.size())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
// If the latest value is kTypeDeletion, kTypeMerge or kTypeLogData
|
||||
// then we probably don't have enough space to update in-place
|
||||
// Maybe do something later
|
||||
// Return false, and do normal Add()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key doesn't exist
|
||||
return false;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
+24
-4
@@ -65,10 +65,14 @@ class MemTable {
|
||||
// iterator are internal keys encoded by AppendInternalKey in the
|
||||
// db/dbformat.{h,cc} module.
|
||||
//
|
||||
// If a prefix is supplied, it is passed to the underlying MemTableRep as a
|
||||
// hint that the iterator only need to support access to keys with that
|
||||
// prefix.
|
||||
Iterator* NewIterator(const Slice* prefix = nullptr);
|
||||
// If options.prefix is supplied, it is passed to the underlying MemTableRep
|
||||
// as a hint that the iterator only need to support access to keys with that
|
||||
// specific prefix.
|
||||
// If options.prefix is not supplied and options.prefix_seek is set, the
|
||||
// iterator is not bound to a specific prefix. However, the semantics of
|
||||
// Seek is changed - the result might only include keys with the same prefix
|
||||
// as the seek-key.
|
||||
Iterator* NewIterator(const ReadOptions& options = ReadOptions());
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
// specified sequence number and with the specified type.
|
||||
@@ -88,6 +92,16 @@ class MemTable {
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
std::deque<std::string>* operands, const Options& options);
|
||||
|
||||
// Update the value and return status ok,
|
||||
// if key exists in current memtable
|
||||
// if new sizeof(new_value) <= sizeof(old_value) &&
|
||||
// old_value for that key is a put i.e. kTypeValue
|
||||
// else return false, and status - NotUpdatable()
|
||||
// else return false, and status - NotFound()
|
||||
bool Update(SequenceNumber seq, ValueType type,
|
||||
const Slice& key,
|
||||
const Slice& value);
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
|
||||
@@ -144,9 +158,15 @@ class MemTable {
|
||||
// memtable flush is done)
|
||||
uint64_t mem_logfile_number_;
|
||||
|
||||
// rw locks for inplace updates
|
||||
std::vector<port::RWMutex> locks_;
|
||||
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&);
|
||||
void operator=(const MemTable&);
|
||||
|
||||
// Get the lock associated for the key
|
||||
port::RWMutex* GetLock(const Slice& key);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+15
-17
@@ -96,15 +96,12 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// flush was sucessful
|
||||
bool first = true;
|
||||
for (MemTable* m : mems) {
|
||||
|
||||
for (size_t i = 0; i < mems.size(); ++i) {
|
||||
// All the edits are associated with the first memtable of this batch.
|
||||
assert(first || m->GetEdits()->NumEntries() == 0);
|
||||
first = false;
|
||||
assert(i == 0 || mems[i]->GetEdits()->NumEntries() == 0);
|
||||
|
||||
m->flush_completed_ = true;
|
||||
m->file_number_ = file_number;
|
||||
mems[i]->flush_completed_ = true;
|
||||
mems[i]->file_number_ = file_number;
|
||||
}
|
||||
|
||||
// if some other thread is already commiting, then return
|
||||
@@ -124,22 +121,21 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
if (!m->flush_completed_) {
|
||||
break;
|
||||
}
|
||||
first = true;
|
||||
|
||||
Log(info_log,
|
||||
"Level-0 commit table #%llu: started",
|
||||
(unsigned long long)m->file_number_);
|
||||
Log(info_log, "Level-0 commit table #%lu started", m->file_number_);
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(&m->edit_, mu);
|
||||
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables has been flushed.
|
||||
do {
|
||||
if (s.ok()) { // commit new state
|
||||
Log(info_log, "Level-0 commit table #%llu: done %s",
|
||||
(unsigned long long)m->file_number_,
|
||||
first ? "": "bulk");
|
||||
Log(info_log,
|
||||
"Level-0 commit table #%lu: memtable #%lu done",
|
||||
m->file_number_,
|
||||
mem_id);
|
||||
memlist_.remove(m);
|
||||
assert(m->file_number_ > 0);
|
||||
|
||||
@@ -152,8 +148,10 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
size_--;
|
||||
} else {
|
||||
//commit failed. setup state so that we can flush again.
|
||||
Log(info_log, "Level-0 commit table #%llu: failed",
|
||||
(unsigned long long)m->file_number_);
|
||||
Log(info_log,
|
||||
"Level-0 commit table #%lu: memtable #%lu failed",
|
||||
m->file_number_,
|
||||
mem_id);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
@@ -163,7 +161,7 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
imm_flush_needed.Release_Store((void *)1);
|
||||
s = Status::IOError("Unable to commit flushed memtable");
|
||||
}
|
||||
first = false;
|
||||
++mem_id;
|
||||
} while (!memlist_.empty() && (m = memlist_.back()) &&
|
||||
m->file_number_ == file_number);
|
||||
}
|
||||
|
||||
+2
-1
@@ -54,7 +54,8 @@ class MemTableList {
|
||||
// not yet started.
|
||||
bool IsFlushPending(int min_write_buffer_number_to_merge);
|
||||
|
||||
// Returns the earliest memtables that needs to be flushed.
|
||||
// Returns the earliest memtables that needs to be flushed. The returned
|
||||
// memtables are guaranteed to be in the ascending order of created time.
|
||||
void PickMemtablesToFlush(std::vector<MemTable*>* mems);
|
||||
|
||||
// Commit a successful flush in the manifest file
|
||||
|
||||
+17
-14
@@ -25,16 +25,6 @@ int FLAGS_min_write_buffer_number_to_merge = 7;
|
||||
// Path to the database on file system
|
||||
const std::string kDbName = rocksdb::test::TmpDir() + "/perf_context_test";
|
||||
|
||||
void SeekToFirst(rocksdb::Iterator* iter) {
|
||||
// std::cout << "Press a key to continue:";
|
||||
// std::string s;
|
||||
// std::cin >> s;
|
||||
iter->SeekToFirst();
|
||||
// std::cout << "Press a key to continue:";
|
||||
// std::string s2;
|
||||
// std::cin >> s2;
|
||||
}
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
std::shared_ptr<DB> OpenDb() {
|
||||
@@ -100,10 +90,7 @@ TEST(PerfContextTest, SeekIntoDeletion) {
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
//CALLGRIND_ZERO_STATS;
|
||||
SeekToFirst(iter.get());
|
||||
//iter->SeekToFirst();
|
||||
//CALLGRIND_DUMP_STATS;
|
||||
iter->SeekToFirst();
|
||||
hist_seek_to_first.Add(perf_context.user_key_comparison_count);
|
||||
auto elapsed_nanos = timer.ElapsedNanos();
|
||||
|
||||
@@ -257,13 +244,29 @@ TEST(PerfContextTest, SeekKeyComparison) {
|
||||
std::random_shuffle(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
HistogramImpl hist_put_time;
|
||||
HistogramImpl hist_wal_time;
|
||||
HistogramImpl hist_time_diff;
|
||||
|
||||
SetPerfLevel(kEnableTime);
|
||||
StopWatchNano timer(Env::Default());
|
||||
for (const int i : keys) {
|
||||
std::string key = "k" + std::to_string(i);
|
||||
std::string value = "v" + std::to_string(i);
|
||||
|
||||
perf_context.Reset();
|
||||
timer.Start();
|
||||
db->Put(write_options, key, value);
|
||||
auto put_time = timer.ElapsedNanos();
|
||||
hist_put_time.Add(put_time);
|
||||
hist_wal_time.Add(perf_context.wal_write_time);
|
||||
hist_time_diff.Add(put_time - perf_context.wal_write_time);
|
||||
}
|
||||
|
||||
std::cout << "Put time:\n" << hist_put_time.ToString()
|
||||
<< "WAL time:\n" << hist_wal_time.ToString()
|
||||
<< "time diff:\n" << hist_time_diff.ToString();
|
||||
|
||||
HistogramImpl hist_seek;
|
||||
HistogramImpl hist_next;
|
||||
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "util/histogram.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
DEFINE_bool(use_prefix_hash_memtable, true, "");
|
||||
DEFINE_bool(use_nolock_version, true, "");
|
||||
DEFINE_bool(trigger_deadlock, false,
|
||||
"issue delete in range scan to trigger PrefixHashMap deadlock");
|
||||
DEFINE_uint64(bucket_count, 100000, "number of buckets");
|
||||
DEFINE_uint64(num_locks, 10001, "number of locks");
|
||||
DEFINE_bool(random_prefix, false, "randomize prefix");
|
||||
DEFINE_uint64(total_prefixes, 1000, "total number of prefixes");
|
||||
DEFINE_uint64(items_per_prefix, 10, "total number of values per prefix");
|
||||
DEFINE_int64(write_buffer_size, 1000000000, "");
|
||||
DEFINE_int64(max_write_buffer_number, 8, "");
|
||||
DEFINE_int64(min_write_buffer_number_to_merge, 7, "");
|
||||
|
||||
// Path to the database on file system
|
||||
const std::string kDbName = rocksdb::test::TmpDir() + "/prefix_test";
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct TestKey {
|
||||
uint64_t prefix;
|
||||
uint64_t sorted;
|
||||
|
||||
TestKey(uint64_t prefix, uint64_t sorted) : prefix(prefix), sorted(sorted) {}
|
||||
};
|
||||
|
||||
// return a slice backed by test_key
|
||||
inline Slice TestKeyToSlice(const TestKey& test_key) {
|
||||
return Slice((const char*)&test_key, sizeof(test_key));
|
||||
}
|
||||
|
||||
inline const TestKey* SliceToTestKey(const Slice& slice) {
|
||||
assert(slice.size() == sizeof(TestKey));
|
||||
return (const TestKey*)slice.data();
|
||||
}
|
||||
|
||||
class TestKeyComparator : public Comparator {
|
||||
public:
|
||||
virtual int Compare(const Slice& a, const Slice& b) const {
|
||||
const TestKey* key_a = SliceToTestKey(a);
|
||||
const TestKey* key_b = SliceToTestKey(b);
|
||||
if (key_a->prefix != key_b->prefix) {
|
||||
if (key_a->prefix < key_b->prefix) return -1;
|
||||
if (key_a->prefix > key_b->prefix) return 1;
|
||||
assert(false);
|
||||
} else {
|
||||
if (key_a->sorted < key_b->sorted) return -1;
|
||||
if (key_a->sorted > key_b->sorted) return 1;
|
||||
if (key_a->sorted == key_b->sorted) return 0;
|
||||
assert(false);
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "TestKeyComparator";
|
||||
}
|
||||
|
||||
virtual void FindShortestSeparator(
|
||||
std::string* start,
|
||||
const Slice& limit) const {
|
||||
}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* key) const {}
|
||||
|
||||
};
|
||||
|
||||
class PrefixTest {
|
||||
public:
|
||||
std::shared_ptr<DB> OpenDb() {
|
||||
DB* db;
|
||||
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
options.max_write_buffer_number = FLAGS_max_write_buffer_number;
|
||||
options.min_write_buffer_number_to_merge =
|
||||
FLAGS_min_write_buffer_number_to_merge;
|
||||
|
||||
options.comparator = new TestKeyComparator();
|
||||
if (FLAGS_use_prefix_hash_memtable) {
|
||||
auto prefix_extractor = NewFixedPrefixTransform(8);
|
||||
options.prefix_extractor = prefix_extractor;
|
||||
if (FLAGS_use_nolock_version) {
|
||||
options.memtable_factory =
|
||||
std::make_shared<rocksdb::PrefixHashRepNoLockFactory>(
|
||||
prefix_extractor, FLAGS_bucket_count);
|
||||
} else {
|
||||
options.memtable_factory =
|
||||
std::make_shared<rocksdb::PrefixHashRepFactory>(
|
||||
prefix_extractor, FLAGS_bucket_count, FLAGS_num_locks);
|
||||
}
|
||||
}
|
||||
|
||||
Status s = DB::Open(options, kDbName, &db);
|
||||
ASSERT_OK(s);
|
||||
return std::shared_ptr<DB>(db);
|
||||
}
|
||||
~PrefixTest() {
|
||||
delete options.comparator;
|
||||
}
|
||||
protected:
|
||||
Options options;
|
||||
};
|
||||
|
||||
TEST(PrefixTest, DynamicPrefixIterator) {
|
||||
|
||||
DestroyDB(kDbName, Options());
|
||||
auto db = OpenDb();
|
||||
WriteOptions write_options;
|
||||
ReadOptions read_options;
|
||||
|
||||
std::vector<uint64_t> prefixes;
|
||||
for (uint64_t i = 0; i < FLAGS_total_prefixes; ++i) {
|
||||
prefixes.push_back(i);
|
||||
}
|
||||
|
||||
if (FLAGS_random_prefix) {
|
||||
std::random_shuffle(prefixes.begin(), prefixes.end());
|
||||
}
|
||||
|
||||
// insert x random prefix, each with y continuous element.
|
||||
for (auto prefix : prefixes) {
|
||||
for (uint64_t sorted = 0; sorted < FLAGS_items_per_prefix; sorted++) {
|
||||
TestKey test_key(prefix, sorted);
|
||||
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
std::string value = "v" + std::to_string(sorted);
|
||||
|
||||
ASSERT_OK(db->Put(write_options, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
// test seek existing keys
|
||||
HistogramImpl hist_seek_time;
|
||||
HistogramImpl hist_seek_comparison;
|
||||
|
||||
if (FLAGS_use_prefix_hash_memtable) {
|
||||
read_options.prefix_seek = true;
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
|
||||
|
||||
for (auto prefix : prefixes) {
|
||||
TestKey test_key(prefix, FLAGS_items_per_prefix / 2);
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
std::string value = "v" + std::to_string(0);
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
uint64_t total_keys = 0;
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
if (FLAGS_trigger_deadlock) {
|
||||
std::cout << "Behold the deadlock!\n";
|
||||
db->Delete(write_options, iter->key());
|
||||
}
|
||||
auto test_key = SliceToTestKey(iter->key());
|
||||
if (test_key->prefix != prefix) break;
|
||||
total_keys++;
|
||||
}
|
||||
hist_seek_time.Add(timer.ElapsedNanos());
|
||||
hist_seek_comparison.Add(perf_context.user_key_comparison_count);
|
||||
ASSERT_EQ(total_keys, FLAGS_items_per_prefix - FLAGS_items_per_prefix/2);
|
||||
}
|
||||
|
||||
std::cout << "Seek key comparison: \n"
|
||||
<< hist_seek_comparison.ToString()
|
||||
<< "Seek time: \n"
|
||||
<< hist_seek_time.ToString();
|
||||
|
||||
// test non-existing keys
|
||||
HistogramImpl hist_no_seek_time;
|
||||
HistogramImpl hist_no_seek_comparison;
|
||||
|
||||
for (auto prefix = FLAGS_total_prefixes;
|
||||
prefix < FLAGS_total_prefixes + 100;
|
||||
prefix++) {
|
||||
TestKey test_key(prefix, 0);
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
iter->Seek(key);
|
||||
hist_no_seek_time.Add(timer.ElapsedNanos());
|
||||
hist_no_seek_comparison.Add(perf_context.user_key_comparison_count);
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
std::cout << "non-existing Seek key comparison: \n"
|
||||
<< hist_no_seek_comparison.ToString()
|
||||
<< "non-existing Seek time: \n"
|
||||
<< hist_no_seek_time.ToString();
|
||||
}
|
||||
|
||||
TEST(PrefixTest, PrefixHash) {
|
||||
|
||||
DestroyDB(kDbName, Options());
|
||||
auto db = OpenDb();
|
||||
WriteOptions write_options;
|
||||
ReadOptions read_options;
|
||||
|
||||
std::vector<uint64_t> prefixes;
|
||||
for (uint64_t i = 0; i < FLAGS_total_prefixes; ++i) {
|
||||
prefixes.push_back(i);
|
||||
}
|
||||
|
||||
if (FLAGS_random_prefix) {
|
||||
std::random_shuffle(prefixes.begin(), prefixes.end());
|
||||
}
|
||||
|
||||
// insert x random prefix, each with y continuous element.
|
||||
HistogramImpl hist_put_time;
|
||||
HistogramImpl hist_put_comparison;
|
||||
|
||||
for (auto prefix : prefixes) {
|
||||
for (uint64_t sorted = 0; sorted < FLAGS_items_per_prefix; sorted++) {
|
||||
TestKey test_key(prefix, sorted);
|
||||
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
std::string value = "v" + std::to_string(sorted);
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
ASSERT_OK(db->Put(write_options, key, value));
|
||||
hist_put_time.Add(timer.ElapsedNanos());
|
||||
hist_put_comparison.Add(perf_context.user_key_comparison_count);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Put key comparison: \n" << hist_put_comparison.ToString()
|
||||
<< "Put time: \n" << hist_put_time.ToString();
|
||||
|
||||
|
||||
// test seek existing keys
|
||||
HistogramImpl hist_seek_time;
|
||||
HistogramImpl hist_seek_comparison;
|
||||
|
||||
for (auto prefix : prefixes) {
|
||||
TestKey test_key(prefix, 0);
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
std::string value = "v" + std::to_string(0);
|
||||
|
||||
Slice key_prefix;
|
||||
if (FLAGS_use_prefix_hash_memtable) {
|
||||
key_prefix = options.prefix_extractor->Transform(key);
|
||||
read_options.prefix = &key_prefix;
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
uint64_t total_keys = 0;
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
if (FLAGS_trigger_deadlock) {
|
||||
std::cout << "Behold the deadlock!\n";
|
||||
db->Delete(write_options, iter->key());
|
||||
}
|
||||
auto test_key = SliceToTestKey(iter->key());
|
||||
if (test_key->prefix != prefix) break;
|
||||
total_keys++;
|
||||
}
|
||||
hist_seek_time.Add(timer.ElapsedNanos());
|
||||
hist_seek_comparison.Add(perf_context.user_key_comparison_count);
|
||||
ASSERT_EQ(total_keys, FLAGS_items_per_prefix);
|
||||
}
|
||||
|
||||
std::cout << "Seek key comparison: \n"
|
||||
<< hist_seek_comparison.ToString()
|
||||
<< "Seek time: \n"
|
||||
<< hist_seek_time.ToString();
|
||||
|
||||
// test non-existing keys
|
||||
HistogramImpl hist_no_seek_time;
|
||||
HistogramImpl hist_no_seek_comparison;
|
||||
|
||||
for (auto prefix = FLAGS_total_prefixes;
|
||||
prefix < FLAGS_total_prefixes + 100;
|
||||
prefix++) {
|
||||
TestKey test_key(prefix, 0);
|
||||
Slice key = TestKeyToSlice(test_key);
|
||||
|
||||
if (FLAGS_use_prefix_hash_memtable) {
|
||||
Slice key_prefix = options.prefix_extractor->Transform(key);
|
||||
read_options.prefix = &key_prefix;
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db->NewIterator(read_options));
|
||||
|
||||
perf_context.Reset();
|
||||
StopWatchNano timer(Env::Default(), true);
|
||||
iter->Seek(key);
|
||||
hist_no_seek_time.Add(timer.ElapsedNanos());
|
||||
hist_no_seek_comparison.Add(perf_context.user_key_comparison_count);
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
std::cout << "non-existing Seek key comparison: \n"
|
||||
<< hist_no_seek_comparison.ToString()
|
||||
<< "non-existing Seek time: \n"
|
||||
<< hist_no_seek_time.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
std::cout << kDbName << "\n";
|
||||
|
||||
rocksdb::test::RunAllTests();
|
||||
return 0;
|
||||
}
|
||||
+1
-1
@@ -208,7 +208,7 @@ class Repairer {
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
status = WriteBatchInternal::InsertInto(&batch, mem);
|
||||
status = WriteBatchInternal::InsertInto(&batch, mem, &options_);
|
||||
if (status.ok()) {
|
||||
counter += WriteBatchInternal::Count(&batch);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
// 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.
|
||||
// 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 <algorithm>
|
||||
#include <set>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/db_statistics.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// SimpleTable is a simple table format for UNIT TEST ONLY. It is not built
|
||||
// as production quality.
|
||||
// SimpleTable requires the input key size to be fixed 16 bytes, value cannot
|
||||
// be longer than 150000 bytes and stored data on disk in this format:
|
||||
// +--------------------------------------------+ <= key1 offset
|
||||
// | key1 | value_size (4 bytes) | |
|
||||
// +----------------------------------------+ |
|
||||
// | value1 |
|
||||
// | |
|
||||
// +----------------------------------------+---+ <= key2 offset
|
||||
// | key2 | value_size (4 bytes) | |
|
||||
// +----------------------------------------+ |
|
||||
// | value2 |
|
||||
// | |
|
||||
// | ...... |
|
||||
// +-----------------+--------------------------+ <= index_block_offset
|
||||
// | key1 | key1 offset (8 bytes) |
|
||||
// +-----------------+--------------------------+
|
||||
// | key2 | key2 offset (8 bytes) |
|
||||
// +-----------------+--------------------------+
|
||||
// | key3 | key3 offset (8 bytes) |
|
||||
// +-----------------+--------------------------+
|
||||
// | ...... |
|
||||
// +-----------------+------------+-------------+
|
||||
// | index_block_offset (8 bytes) |
|
||||
// +------------------------------+
|
||||
|
||||
// SimpleTable is a simple table format for UNIT TEST ONLY. It is not built
|
||||
// as production quality.
|
||||
class SimpleTableReader: public TableReader {
|
||||
public:
|
||||
// Attempt to open the table that is stored in bytes [0..file_size)
|
||||
// of "file", and read the metadata entries necessary to allow
|
||||
// retrieving data from the table.
|
||||
//
|
||||
// If successful, returns ok and sets "*table" to the newly opened
|
||||
// table. The client should delete "*table" when no longer needed.
|
||||
// If there was an error while initializing the table, sets "*table"
|
||||
// to nullptr and returns a non-ok status. Does not take ownership of
|
||||
// "*source", but the client must ensure that "source" remains live
|
||||
// for the duration of the returned table's lifetime.
|
||||
//
|
||||
// *file must remain live while this Table is in use.
|
||||
static Status Open(const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader);
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_prefix) override;
|
||||
|
||||
Iterator* NewIterator(const ReadOptions&) override;
|
||||
|
||||
Status Get(
|
||||
const ReadOptions&, const Slice& key, void* arg,
|
||||
bool (*handle_result)(void* arg, const Slice& k, const Slice& v, bool),
|
||||
void (*mark_key_may_exist)(void*) = nullptr) override;
|
||||
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) override;
|
||||
|
||||
bool TEST_KeyInCache(const ReadOptions& options, const Slice& key) override;
|
||||
|
||||
void SetupForCompaction() override;
|
||||
|
||||
TableStats& GetTableStats() override;
|
||||
|
||||
~SimpleTableReader();
|
||||
|
||||
private:
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
|
||||
explicit SimpleTableReader(Rep* rep) {
|
||||
rep_ = rep;
|
||||
}
|
||||
friend class TableCache;
|
||||
friend class SimpleTableIterator;
|
||||
|
||||
Status GetOffset(const Slice& target, uint64_t* offset);
|
||||
|
||||
// No copying allowed
|
||||
explicit SimpleTableReader(const TableReader&) = delete;
|
||||
void operator=(const TableReader&) = delete;
|
||||
};
|
||||
|
||||
// Iterator to iterate SimpleTable
|
||||
class SimpleTableIterator: public Iterator {
|
||||
public:
|
||||
explicit SimpleTableIterator(SimpleTableReader* table);
|
||||
~SimpleTableIterator();
|
||||
|
||||
bool Valid() const;
|
||||
|
||||
void SeekToFirst();
|
||||
|
||||
void SeekToLast();
|
||||
|
||||
void Seek(const Slice& target);
|
||||
|
||||
void Next();
|
||||
|
||||
void Prev();
|
||||
|
||||
Slice key() const;
|
||||
|
||||
Slice value() const;
|
||||
|
||||
Status status() const;
|
||||
|
||||
private:
|
||||
SimpleTableReader* table_;
|
||||
uint64_t offset_;
|
||||
uint64_t next_offset_;
|
||||
Slice key_;
|
||||
Slice value_;
|
||||
char tmp_str_[4];
|
||||
char* key_str_;
|
||||
char* value_str_;
|
||||
int value_str_len_;
|
||||
Status status_;
|
||||
// No copying allowed
|
||||
SimpleTableIterator(const SimpleTableIterator&) = delete;
|
||||
void operator=(const Iterator&) = delete;
|
||||
};
|
||||
|
||||
struct SimpleTableReader::Rep {
|
||||
~Rep() {
|
||||
}
|
||||
Rep(const EnvOptions& storage_options, uint64_t index_start_offset,
|
||||
int num_entries) :
|
||||
soptions(storage_options), index_start_offset(index_start_offset),
|
||||
num_entries(num_entries) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
const EnvOptions& soptions;
|
||||
Status status;
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
uint64_t index_start_offset;
|
||||
int num_entries;
|
||||
TableStats table_stats;
|
||||
|
||||
const static int user_key_size = 16;
|
||||
const static int offset_length = 8;
|
||||
const static int key_footer_len = 8;
|
||||
|
||||
static int GetInternalKeyLength() {
|
||||
return user_key_size + key_footer_len;
|
||||
}
|
||||
};
|
||||
|
||||
SimpleTableReader::~SimpleTableReader() {
|
||||
delete rep_;
|
||||
}
|
||||
|
||||
Status SimpleTableReader::Open(const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file,
|
||||
uint64_t size,
|
||||
unique_ptr<TableReader>* table_reader) {
|
||||
char footer_space[Rep::offset_length];
|
||||
Slice footer_input;
|
||||
Status s = file->Read(size - Rep::offset_length, Rep::offset_length,
|
||||
&footer_input, footer_space);
|
||||
if (s.ok()) {
|
||||
uint64_t index_start_offset = DecodeFixed64(footer_space);
|
||||
|
||||
int num_entries = (size - Rep::offset_length - index_start_offset)
|
||||
/ (Rep::GetInternalKeyLength() + Rep::offset_length);
|
||||
SimpleTableReader::Rep* rep = new SimpleTableReader::Rep(soptions,
|
||||
index_start_offset,
|
||||
num_entries);
|
||||
|
||||
rep->file = std::move(file);
|
||||
rep->options = options;
|
||||
table_reader->reset(new SimpleTableReader(rep));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void SimpleTableReader::SetupForCompaction() {
|
||||
}
|
||||
|
||||
TableStats& SimpleTableReader::GetTableStats() {
|
||||
return rep_->table_stats;
|
||||
}
|
||||
|
||||
bool SimpleTableReader::PrefixMayMatch(const Slice& internal_prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Iterator* SimpleTableReader::NewIterator(const ReadOptions& options) {
|
||||
return new SimpleTableIterator(this);
|
||||
}
|
||||
|
||||
Status SimpleTableReader::GetOffset(const Slice& target, uint64_t* offset) {
|
||||
uint32_t left = 0;
|
||||
uint32_t right = rep_->num_entries - 1;
|
||||
char key_chars[Rep::GetInternalKeyLength()];
|
||||
Slice tmp_slice;
|
||||
|
||||
uint32_t target_offset = 0;
|
||||
while (left <= right) {
|
||||
uint32_t mid = (left + right + 1) / 2;
|
||||
|
||||
uint64_t offset_to_read = rep_->index_start_offset
|
||||
+ (Rep::GetInternalKeyLength() + Rep::offset_length) * mid;
|
||||
Status s = rep_->file->Read(offset_to_read, Rep::GetInternalKeyLength(),
|
||||
&tmp_slice, key_chars);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
int compare_result = rep_->options.comparator->Compare(tmp_slice, target);
|
||||
|
||||
if (compare_result < 0) {
|
||||
if (left == right) {
|
||||
target_offset = right + 1;
|
||||
break;
|
||||
}
|
||||
left = mid;
|
||||
} else {
|
||||
if (left == right) {
|
||||
target_offset = left;
|
||||
break;
|
||||
}
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (target_offset >= (uint32_t) rep_->num_entries) {
|
||||
*offset = rep_->index_start_offset;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
char value_offset_chars[Rep::offset_length];
|
||||
|
||||
int64_t offset_for_value_offset = rep_->index_start_offset
|
||||
+ (Rep::GetInternalKeyLength() + Rep::offset_length) * target_offset
|
||||
+ Rep::GetInternalKeyLength();
|
||||
Status s = rep_->file->Read(offset_for_value_offset, Rep::offset_length,
|
||||
&tmp_slice, value_offset_chars);
|
||||
if (s.ok()) {
|
||||
*offset = DecodeFixed64(value_offset_chars);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status SimpleTableReader::Get(
|
||||
const ReadOptions& options, const Slice& k, void* arg,
|
||||
bool (*saver)(void*, const Slice&, const Slice&, bool),
|
||||
void (*mark_key_may_exist)(void*)) {
|
||||
Status s;
|
||||
SimpleTableIterator* iter = new SimpleTableIterator(this);
|
||||
for (iter->Seek(k); iter->Valid(); iter->Next()) {
|
||||
if (!(*saver)(arg, iter->key(), iter->value(), true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s = iter->status();
|
||||
delete iter;
|
||||
return s;
|
||||
}
|
||||
|
||||
bool SimpleTableReader::TEST_KeyInCache(const ReadOptions& options,
|
||||
const Slice& key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t SimpleTableReader::ApproximateOffsetOf(const Slice& key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SimpleTableIterator::SimpleTableIterator(SimpleTableReader* table) :
|
||||
table_(table) {
|
||||
key_str_ = new char[SimpleTableReader::Rep::GetInternalKeyLength()];
|
||||
value_str_len_ = -1;
|
||||
SeekToFirst();
|
||||
}
|
||||
|
||||
SimpleTableIterator::~SimpleTableIterator() {
|
||||
delete[] key_str_;
|
||||
if (value_str_len_ >= 0) {
|
||||
delete[] value_str_;
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleTableIterator::Valid() const {
|
||||
return offset_ < table_->rep_->index_start_offset && offset_ >= 0;
|
||||
}
|
||||
|
||||
void SimpleTableIterator::SeekToFirst() {
|
||||
next_offset_ = 0;
|
||||
Next();
|
||||
}
|
||||
|
||||
void SimpleTableIterator::SeekToLast() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void SimpleTableIterator::Seek(const Slice& target) {
|
||||
Status s = table_->GetOffset(target, &next_offset_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
Next();
|
||||
}
|
||||
|
||||
void SimpleTableIterator::Next() {
|
||||
offset_ = next_offset_;
|
||||
if (offset_ >= table_->rep_->index_start_offset) {
|
||||
return;
|
||||
}
|
||||
Slice result;
|
||||
int internal_key_size = SimpleTableReader::Rep::GetInternalKeyLength();
|
||||
|
||||
Status s = table_->rep_->file->Read(next_offset_, internal_key_size, &result,
|
||||
key_str_);
|
||||
next_offset_ += internal_key_size;
|
||||
key_ = result;
|
||||
|
||||
Slice value_size_slice;
|
||||
s = table_->rep_->file->Read(next_offset_, 4, &value_size_slice, tmp_str_);
|
||||
next_offset_ += 4;
|
||||
uint32_t value_size = DecodeFixed32(tmp_str_);
|
||||
|
||||
Slice value_slice;
|
||||
if ((int) value_size > value_str_len_) {
|
||||
if (value_str_len_ >= 0) {
|
||||
delete[] value_str_;
|
||||
}
|
||||
value_str_ = new char[value_size];
|
||||
value_str_len_ = value_size;
|
||||
}
|
||||
s = table_->rep_->file->Read(next_offset_, value_size, &value_slice,
|
||||
value_str_);
|
||||
next_offset_ += value_size;
|
||||
value_ = value_slice;
|
||||
}
|
||||
|
||||
void SimpleTableIterator::Prev() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
Slice SimpleTableIterator::key() const {
|
||||
Log(table_->rep_->options.info_log, "key!!!!");
|
||||
return key_;
|
||||
}
|
||||
|
||||
Slice SimpleTableIterator::value() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
Status SimpleTableIterator::status() const {
|
||||
return status_;
|
||||
}
|
||||
|
||||
class SimpleTableBuilder: public TableBuilder {
|
||||
public:
|
||||
// Create a builder that will store the contents of the table it is
|
||||
// building in *file. Does not close the file. It is up to the
|
||||
// caller to close the file after calling Finish(). The output file
|
||||
// will be part of level specified by 'level'. A value of -1 means
|
||||
// that the caller does not know which level the output file will reside.
|
||||
SimpleTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type);
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~SimpleTableBuilder();
|
||||
|
||||
// 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 Rep;
|
||||
Rep* rep_;
|
||||
|
||||
// No copying allowed
|
||||
SimpleTableBuilder(const SimpleTableBuilder&) = delete;
|
||||
void operator=(const SimpleTableBuilder&) = delete;
|
||||
};
|
||||
|
||||
struct SimpleTableBuilder::Rep {
|
||||
Options options;
|
||||
WritableFile* file;
|
||||
uint64_t offset = 0;
|
||||
Status status;
|
||||
|
||||
uint64_t num_entries = 0;
|
||||
|
||||
bool closed = false; // Either Finish() or Abandon() has been called.
|
||||
|
||||
const static int user_key_size = 16;
|
||||
const static int offset_length = 8;
|
||||
const static int key_footer_len = 8;
|
||||
|
||||
static int GetInternalKeyLength() {
|
||||
return user_key_size + key_footer_len;
|
||||
}
|
||||
|
||||
std::string index;
|
||||
|
||||
Rep(const Options& opt, WritableFile* f) :
|
||||
options(opt), file(f) {
|
||||
}
|
||||
~Rep() {
|
||||
}
|
||||
};
|
||||
|
||||
SimpleTableBuilder::SimpleTableBuilder(const Options& options,
|
||||
WritableFile* file,
|
||||
CompressionType compression_type) :
|
||||
rep_(new SimpleTableBuilder::Rep(options, file)) {
|
||||
}
|
||||
|
||||
SimpleTableBuilder::~SimpleTableBuilder() {
|
||||
delete (rep_);
|
||||
}
|
||||
|
||||
void SimpleTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
assert((int ) key.size() == Rep::GetInternalKeyLength());
|
||||
|
||||
// Update index
|
||||
rep_->index.append(key.data(), key.size());
|
||||
PutFixed64(&(rep_->index), rep_->offset);
|
||||
|
||||
// Write key-value pair
|
||||
rep_->file->Append(key);
|
||||
rep_->offset += Rep::GetInternalKeyLength();
|
||||
|
||||
std::string size;
|
||||
int value_size = value.size();
|
||||
PutFixed32(&size, value_size);
|
||||
Slice sizeSlice(size);
|
||||
rep_->file->Append(sizeSlice);
|
||||
rep_->file->Append(value);
|
||||
rep_->offset += value_size + 4;
|
||||
|
||||
rep_->num_entries++;
|
||||
}
|
||||
|
||||
Status SimpleTableBuilder::status() const {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SimpleTableBuilder::Finish() {
|
||||
Rep* r = rep_;
|
||||
assert(!r->closed);
|
||||
r->closed = true;
|
||||
|
||||
uint64_t index_offset = rep_->offset;
|
||||
Slice index_slice(rep_->index);
|
||||
rep_->file->Append(index_slice);
|
||||
rep_->offset += index_slice.size();
|
||||
|
||||
std::string index_offset_str;
|
||||
PutFixed64(&index_offset_str, index_offset);
|
||||
Slice foot_slice(index_offset_str);
|
||||
rep_->file->Append(foot_slice);
|
||||
rep_->offset += foot_slice.size();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void SimpleTableBuilder::Abandon() {
|
||||
rep_->closed = true;
|
||||
}
|
||||
|
||||
uint64_t SimpleTableBuilder::NumEntries() const {
|
||||
return rep_->num_entries;
|
||||
}
|
||||
|
||||
uint64_t SimpleTableBuilder::FileSize() const {
|
||||
return rep_->offset;
|
||||
}
|
||||
|
||||
class SimpleTableFactory: public TableFactory {
|
||||
public:
|
||||
~SimpleTableFactory() {
|
||||
}
|
||||
SimpleTableFactory() {
|
||||
}
|
||||
const char* Name() const override {
|
||||
return "SimpleTable";
|
||||
}
|
||||
Status GetTableReader(const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file,
|
||||
uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader) const;
|
||||
|
||||
TableBuilder* GetTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) const;
|
||||
};
|
||||
|
||||
Status SimpleTableFactory::GetTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader) const {
|
||||
|
||||
return SimpleTableReader::Open(options, soptions, std::move(file), file_size,
|
||||
table_reader);
|
||||
}
|
||||
|
||||
TableBuilder* SimpleTableFactory::GetTableBuilder(
|
||||
const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) const {
|
||||
return new SimpleTableBuilder(options, file, compression_type);
|
||||
}
|
||||
|
||||
class SimpleTableDBTest {
|
||||
protected:
|
||||
public:
|
||||
std::string dbname_;
|
||||
Env* env_;
|
||||
DB* db_;
|
||||
|
||||
Options last_options_;
|
||||
|
||||
SimpleTableDBTest() :
|
||||
env_(Env::Default()) {
|
||||
dbname_ = test::TmpDir() + "/simple_table_db_test";
|
||||
ASSERT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
Reopen();
|
||||
}
|
||||
|
||||
~SimpleTableDBTest() {
|
||||
delete db_;
|
||||
ASSERT_OK(DestroyDB(dbname_, Options()));
|
||||
}
|
||||
|
||||
// Return the current option configuration.
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
options.table_factory.reset(new SimpleTableFactory());
|
||||
return options;
|
||||
}
|
||||
|
||||
DBImpl* dbfull() {
|
||||
return reinterpret_cast<DBImpl*>(db_);
|
||||
}
|
||||
|
||||
void Reopen(Options* options = nullptr) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void Close() {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
void DestroyAndReopen(Options* options = nullptr) {
|
||||
//Destroy using last options
|
||||
Destroy(&last_options_);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void Destroy(Options* options) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
ASSERT_OK(DestroyDB(dbname_, *options));
|
||||
}
|
||||
|
||||
Status PureReopen(Options* options, DB** db) {
|
||||
return DB::Open(*options, dbname_, db);
|
||||
}
|
||||
|
||||
Status TryReopen(Options* options = nullptr) {
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
Options opts;
|
||||
if (options != nullptr) {
|
||||
opts = *options;
|
||||
} else {
|
||||
opts = CurrentOptions();
|
||||
opts.create_if_missing = true;
|
||||
}
|
||||
last_options_ = opts;
|
||||
|
||||
return DB::Open(opts, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
}
|
||||
|
||||
Status Delete(const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), k);
|
||||
}
|
||||
|
||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) {
|
||||
ReadOptions options;
|
||||
options.snapshot = snapshot;
|
||||
std::string result;
|
||||
Status s = db_->Get(options, k, &result);
|
||||
if (s.IsNotFound()) {
|
||||
result = "NOT_FOUND";
|
||||
} else if (!s.ok()) {
|
||||
result = s.ToString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int NumTableFilesAtLevel(int level) {
|
||||
std::string property;
|
||||
ASSERT_TRUE(
|
||||
db_->GetProperty("rocksdb.num-files-at-level" + NumberToString(level),
|
||||
&property));
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel() {
|
||||
std::string result;
|
||||
int last_non_zero_offset = 0;
|
||||
for (int level = 0; level < db_->NumberLevels(); level++) {
|
||||
int f = NumTableFilesAtLevel(level);
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
|
||||
result += buf;
|
||||
if (f > 0) {
|
||||
last_non_zero_offset = result.size();
|
||||
}
|
||||
}
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string IterStatus(Iterator* iter) {
|
||||
std::string result;
|
||||
if (iter->Valid()) {
|
||||
result = iter->key().ToString() + "->" + iter->value().ToString();
|
||||
} else {
|
||||
result = "(invalid)";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SimpleTableDBTest, Empty) {
|
||||
ASSERT_TRUE(db_ != nullptr);
|
||||
ASSERT_EQ("NOT_FOUND", Get("0000000000000foo"));
|
||||
}
|
||||
|
||||
TEST(SimpleTableDBTest, ReadWrite) {
|
||||
ASSERT_OK(Put("0000000000000foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("0000000000000foo"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("0000000000000foo", "v3"));
|
||||
ASSERT_EQ("v3", Get("0000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
|
||||
TEST(SimpleTableDBTest, Flush) {
|
||||
ASSERT_OK(Put("0000000000000foo", "v1"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("0000000000000foo", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v3", Get("0000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
|
||||
TEST(SimpleTableDBTest, Flush2) {
|
||||
ASSERT_OK(Put("0000000000000bar", "b"));
|
||||
ASSERT_OK(Put("0000000000000foo", "v1"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
ASSERT_OK(Put("0000000000000foo", "v2"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v2", Get("0000000000000foo"));
|
||||
|
||||
ASSERT_OK(Put("0000000000000eee", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v3", Get("0000000000000eee"));
|
||||
|
||||
ASSERT_OK(Delete("0000000000000bar"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("NOT_FOUND", Get("0000000000000bar"));
|
||||
|
||||
ASSERT_OK(Put("0000000000000eee", "v5"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v5", Get("0000000000000eee"));
|
||||
}
|
||||
|
||||
static std::string Key(int i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "key_______%06d", i);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static std::string RandomString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
test::RandomString(rnd, len, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
TEST(SimpleTableDBTest, CompactionTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100 << 10; //100KB
|
||||
options.num_levels = 3;
|
||||
options.max_mem_compaction_level = 0;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
Reopen(&options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
||||
num++) {
|
||||
std::vector<std::string> values;
|
||||
// Write 120KB (12 values, each 10K)
|
||||
for (int i = 0; i < 12; i++) {
|
||||
values.push_back(RandomString(&rnd, 10000));
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), num + 1);
|
||||
}
|
||||
|
||||
//generate one more file in level-0, and should trigger level-0 compaction
|
||||
std::vector<std::string> values;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
values.push_back(RandomString(&rnd, 10000));
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return rocksdb::test::RunAllTests();
|
||||
}
|
||||
+23
-21
@@ -12,15 +12,15 @@
|
||||
#include "db/filename.h"
|
||||
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "table/table.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static void DeleteEntry(const Slice& key, void* value) {
|
||||
Table* table = reinterpret_cast<Table*>(value);
|
||||
delete table;
|
||||
TableReader* table_reader = reinterpret_cast<TableReader*>(value);
|
||||
delete table_reader;
|
||||
}
|
||||
|
||||
static void UnrefEntry(void* arg1, void* arg2) {
|
||||
@@ -63,7 +63,7 @@ Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
}
|
||||
std::string fname = TableFileName(dbname_, file_number);
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
unique_ptr<Table> table;
|
||||
unique_ptr<TableReader> table_reader;
|
||||
s = env_->NewRandomAccessFile(fname, &file, toptions);
|
||||
RecordTick(options_->statistics, NO_FILE_OPENS);
|
||||
if (s.ok()) {
|
||||
@@ -71,17 +71,19 @@ Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
file->Hint(RandomAccessFile::RANDOM);
|
||||
}
|
||||
StopWatch sw(env_, options_->statistics, TABLE_OPEN_IO_MICROS);
|
||||
s = Table::Open(*options_, toptions, std::move(file), file_size, &table);
|
||||
s = options_->table_factory->GetTableReader(*options_, toptions,
|
||||
std::move(file), file_size,
|
||||
&table_reader);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
assert(table == nullptr);
|
||||
assert(table_reader == nullptr);
|
||||
RecordTick(options_->statistics, NO_FILE_ERRORS);
|
||||
// We do not cache error results so that if the error is transient,
|
||||
// or somebody repairs the file, we recover automatically.
|
||||
} else {
|
||||
assert(file.get() == nullptr);
|
||||
*handle = cache_->Insert(key, table.release(), 1, &DeleteEntry);
|
||||
*handle = cache_->Insert(key, table_reader.release(), 1, &DeleteEntry);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -91,10 +93,10 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
const EnvOptions& toptions,
|
||||
uint64_t file_number,
|
||||
uint64_t file_size,
|
||||
Table** tableptr,
|
||||
TableReader** table_reader_ptr,
|
||||
bool for_compaction) {
|
||||
if (tableptr != nullptr) {
|
||||
*tableptr = nullptr;
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
|
||||
Cache::Handle* handle = nullptr;
|
||||
@@ -104,16 +106,16 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
|
||||
Table* table =
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
Iterator* result = table->NewIterator(options);
|
||||
TableReader* table_reader =
|
||||
reinterpret_cast<TableReader*>(cache_->Value(handle));
|
||||
Iterator* result = table_reader->NewIterator(options);
|
||||
result->RegisterCleanup(&UnrefEntry, cache_.get(), handle);
|
||||
if (tableptr != nullptr) {
|
||||
*tableptr = table;
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = table_reader;
|
||||
}
|
||||
|
||||
if (for_compaction) {
|
||||
table->SetupForCompaction();
|
||||
table_reader->SetupForCompaction();
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -132,9 +134,9 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
&handle, table_io,
|
||||
options.read_tier == kBlockCacheTier);
|
||||
if (s.ok()) {
|
||||
Table* t =
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
s = t->InternalGet(options, k, arg, saver, mark_key_may_exist);
|
||||
TableReader* t =
|
||||
reinterpret_cast<TableReader*>(cache_->Value(handle));
|
||||
s = t->Get(options, k, arg, saver, mark_key_may_exist);
|
||||
cache_->Release(handle);
|
||||
} else if (options.read_tier && s.IsIncomplete()) {
|
||||
// Couldnt find Table in cache but treat as kFound if no_io set
|
||||
@@ -154,8 +156,8 @@ bool TableCache::PrefixMayMatch(const ReadOptions& options,
|
||||
file_size, &handle, table_io);
|
||||
bool may_match = true;
|
||||
if (s.ok()) {
|
||||
Table* t =
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
TableReader* t =
|
||||
reinterpret_cast<TableReader*>(cache_->Value(handle));
|
||||
may_match = t->PrefixMayMatch(internal_prefix);
|
||||
cache_->Release(handle);
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "port/port.h"
|
||||
#include "table/table.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -39,7 +39,7 @@ class TableCache {
|
||||
const EnvOptions& toptions,
|
||||
uint64_t file_number,
|
||||
uint64_t file_size,
|
||||
Table** tableptr = nullptr,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
bool for_compaction = false);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.
|
||||
|
||||
#include "db/table_stats_collector.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status InternalKeyStatsCollector::Add(const Slice& key, const Slice& value) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(key, &ikey)) {
|
||||
return Status::InvalidArgument("Invalid internal key");
|
||||
}
|
||||
|
||||
if (ikey.type == ValueType::kTypeDeletion) {
|
||||
++deleted_keys_;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status InternalKeyStatsCollector::Finish(
|
||||
TableStats::UserCollectedStats* stats) {
|
||||
assert(stats);
|
||||
assert(stats->find(InternalKeyTableStatsNames::kDeletedKeys) == stats->end());
|
||||
std::string val;
|
||||
|
||||
PutVarint64(&val, deleted_keys_);
|
||||
stats->insert(std::make_pair(InternalKeyTableStatsNames::kDeletedKeys, val));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status UserKeyTableStatsCollector::Add(const Slice& key, const Slice& value) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(key, &ikey)) {
|
||||
return Status::InvalidArgument("Invalid internal key");
|
||||
}
|
||||
|
||||
return collector_->Add(ikey.user_key, value);
|
||||
}
|
||||
|
||||
Status UserKeyTableStatsCollector::Finish(
|
||||
TableStats::UserCollectedStats* stats) {
|
||||
return collector_->Finish(stats);
|
||||
}
|
||||
|
||||
const std::string InternalKeyTableStatsNames::kDeletedKeys
|
||||
= "rocksdb.deleted.keys";
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
//
|
||||
// This file defines a collection of statistics collectors.
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/table_stats.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct InternalKeyTableStatsNames {
|
||||
static const std::string kDeletedKeys;
|
||||
};
|
||||
|
||||
// Collecting the statistics for internal keys. Visible only by internal
|
||||
// rocksdb modules.
|
||||
class InternalKeyStatsCollector : public TableStatsCollector {
|
||||
public:
|
||||
virtual Status Add(const Slice& key, const Slice& value);
|
||||
virtual Status Finish(TableStats::UserCollectedStats* stats);
|
||||
virtual const char* Name() const { return "InternalKeyStatsCollector"; }
|
||||
|
||||
private:
|
||||
uint64_t deleted_keys_ = 0;
|
||||
};
|
||||
|
||||
// When rocksdb creates a new table, it will encode all "user keys" into
|
||||
// "internal keys", which contains meta information of a given entry.
|
||||
//
|
||||
// This class extracts user key from the encoded internal key when Add() is
|
||||
// invoked.
|
||||
class UserKeyTableStatsCollector : public TableStatsCollector {
|
||||
public:
|
||||
explicit UserKeyTableStatsCollector(TableStatsCollector* collector):
|
||||
UserKeyTableStatsCollector(
|
||||
std::shared_ptr<TableStatsCollector>(collector)
|
||||
) {
|
||||
}
|
||||
|
||||
explicit UserKeyTableStatsCollector(
|
||||
std::shared_ptr<TableStatsCollector> collector) : collector_(collector) {
|
||||
}
|
||||
virtual ~UserKeyTableStatsCollector() { }
|
||||
virtual Status Add(const Slice& key, const Slice& value);
|
||||
virtual Status Finish(TableStats::UserCollectedStats* stats);
|
||||
virtual const char* Name() const { return collector_->Name(); }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<TableStatsCollector> collector_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,258 @@
|
||||
// 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.
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/table_stats_collector.h"
|
||||
#include "rocksdb/table_stats.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TableStatsTest {
|
||||
private:
|
||||
unique_ptr<TableReader> table_reader_;
|
||||
};
|
||||
|
||||
// TODO(kailiu) the following classes should be moved to some more general
|
||||
// places, so that other tests can also make use of them.
|
||||
// `FakeWritableFile` and `FakeRandomeAccessFile` bypass the real file system
|
||||
// and therefore enable us to quickly setup the tests.
|
||||
class FakeWritableFile : public WritableFile {
|
||||
public:
|
||||
~FakeWritableFile() { }
|
||||
|
||||
const std::string& contents() const { return contents_; }
|
||||
|
||||
virtual Status Close() { return Status::OK(); }
|
||||
virtual Status Flush() { return Status::OK(); }
|
||||
virtual Status Sync() { return Status::OK(); }
|
||||
|
||||
virtual Status Append(const Slice& data) {
|
||||
contents_.append(data.data(), data.size());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string contents_;
|
||||
};
|
||||
|
||||
|
||||
class FakeRandomeAccessFile : public RandomAccessFile {
|
||||
public:
|
||||
explicit FakeRandomeAccessFile(const Slice& contents)
|
||||
: contents_(contents.data(), contents.size()) {
|
||||
}
|
||||
|
||||
virtual ~FakeRandomeAccessFile() { }
|
||||
|
||||
uint64_t Size() const { return contents_.size(); }
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
if (offset > contents_.size()) {
|
||||
return Status::InvalidArgument("invalid Read offset");
|
||||
}
|
||||
if (offset + n > contents_.size()) {
|
||||
n = contents_.size() - offset;
|
||||
}
|
||||
memcpy(scratch, &contents_[offset], n);
|
||||
*result = Slice(scratch, n);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string contents_;
|
||||
};
|
||||
|
||||
|
||||
class DumbLogger : public Logger {
|
||||
public:
|
||||
virtual void Logv(const char* format, va_list ap) { }
|
||||
virtual size_t GetLogFileSize() const { return 0; }
|
||||
};
|
||||
|
||||
// Utilities test functions
|
||||
void MakeBuilder(
|
||||
const Options& options,
|
||||
std::unique_ptr<FakeWritableFile>* writable,
|
||||
std::unique_ptr<TableBuilder>* builder) {
|
||||
writable->reset(new FakeWritableFile);
|
||||
builder->reset(
|
||||
options.table_factory->GetTableBuilder(options, writable->get(),
|
||||
options.compression));
|
||||
}
|
||||
|
||||
void OpenTable(
|
||||
const Options& options,
|
||||
const std::string& contents,
|
||||
std::unique_ptr<TableReader>* table_reader) {
|
||||
std::unique_ptr<RandomAccessFile> file(new FakeRandomeAccessFile(contents));
|
||||
auto s = options.table_factory->GetTableReader(
|
||||
options,
|
||||
EnvOptions(),
|
||||
std::move(file),
|
||||
contents.size(),
|
||||
table_reader
|
||||
);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
// Collects keys that starts with "A" in a table.
|
||||
class RegularKeysStartWithA: public TableStatsCollector {
|
||||
public:
|
||||
const char* Name() const { return "RegularKeysStartWithA"; }
|
||||
|
||||
Status Finish(TableStats::UserCollectedStats* stats) {
|
||||
std::string encoded;
|
||||
PutVarint32(&encoded, count_);
|
||||
*stats = TableStats::UserCollectedStats {
|
||||
{ "TableStatsTest", "Rocksdb" },
|
||||
{ "Count", encoded }
|
||||
};
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Add(const Slice& user_key, const Slice& value) {
|
||||
// simply asssume all user keys are not empty.
|
||||
if (user_key.data()[0] == 'A') {
|
||||
++count_;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t count_ = 0;
|
||||
};
|
||||
|
||||
TEST(TableStatsTest, CustomizedTableStatsCollector) {
|
||||
Options options;
|
||||
|
||||
// make sure the entries will be inserted with order.
|
||||
std::map<std::string, std::string> kvs = {
|
||||
{"About", "val5"}, // starts with 'A'
|
||||
{"Abstract", "val2"}, // starts with 'A'
|
||||
{"Around", "val7"}, // starts with 'A'
|
||||
{"Beyond", "val3"},
|
||||
{"Builder", "val1"},
|
||||
{"Cancel", "val4"},
|
||||
{"Find", "val6"},
|
||||
};
|
||||
|
||||
// Test stats collectors with internal keys or regular keys
|
||||
for (bool encode_as_internal : { true, false }) {
|
||||
// -- Step 1: build table
|
||||
auto collector = new RegularKeysStartWithA();
|
||||
if (encode_as_internal) {
|
||||
options.table_stats_collectors = {
|
||||
std::make_shared<UserKeyTableStatsCollector>(collector)
|
||||
};
|
||||
} else {
|
||||
options.table_stats_collectors.resize(1);
|
||||
options.table_stats_collectors[0].reset(collector);
|
||||
}
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
std::unique_ptr<FakeWritableFile> writable;
|
||||
MakeBuilder(options, &writable, &builder);
|
||||
|
||||
for (const auto& kv : kvs) {
|
||||
if (encode_as_internal) {
|
||||
InternalKey ikey(kv.first, 0, ValueType::kTypeValue);
|
||||
builder->Add(ikey.Encode(), kv.second);
|
||||
} else {
|
||||
builder->Add(kv.first, kv.second);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(builder->Finish());
|
||||
|
||||
// -- Step 2: Open table
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
OpenTable(options, writable->contents(), &table_reader);
|
||||
const auto& stats = table_reader->GetTableStats().user_collected_stats;
|
||||
|
||||
ASSERT_EQ("Rocksdb", stats.at("TableStatsTest"));
|
||||
|
||||
uint32_t starts_with_A = 0;
|
||||
Slice key(stats.at("Count"));
|
||||
ASSERT_TRUE(GetVarint32(&key, &starts_with_A));
|
||||
ASSERT_EQ(3u, starts_with_A);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TableStatsTest, InternalKeyStatsCollector) {
|
||||
InternalKey keys[] = {
|
||||
InternalKey("A", 0, ValueType::kTypeValue),
|
||||
InternalKey("B", 0, ValueType::kTypeValue),
|
||||
InternalKey("C", 0, ValueType::kTypeValue),
|
||||
InternalKey("W", 0, ValueType::kTypeDeletion),
|
||||
InternalKey("X", 0, ValueType::kTypeDeletion),
|
||||
InternalKey("Y", 0, ValueType::kTypeDeletion),
|
||||
InternalKey("Z", 0, ValueType::kTypeDeletion),
|
||||
};
|
||||
|
||||
for (bool sanitized : { false, true }) {
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
std::unique_ptr<FakeWritableFile> writable;
|
||||
Options options;
|
||||
if (sanitized) {
|
||||
options.table_stats_collectors = {
|
||||
std::make_shared<RegularKeysStartWithA>()
|
||||
};
|
||||
// with sanitization, even regular stats collector will be able to
|
||||
// handle internal keys.
|
||||
auto comparator = options.comparator;
|
||||
// HACK: Set options.info_log to avoid writing log in
|
||||
// SanitizeOptions().
|
||||
options.info_log = std::make_shared<DumbLogger>();
|
||||
options = SanitizeOptions(
|
||||
"db", // just a place holder
|
||||
nullptr, // with skip internal key comparator
|
||||
nullptr, // don't care filter policy
|
||||
options
|
||||
);
|
||||
options.comparator = comparator;
|
||||
} else {
|
||||
options.table_stats_collectors = {
|
||||
std::make_shared<InternalKeyStatsCollector>()
|
||||
};
|
||||
}
|
||||
|
||||
MakeBuilder(options, &writable, &builder);
|
||||
for (const auto& k : keys) {
|
||||
builder->Add(k.Encode(), "val");
|
||||
}
|
||||
|
||||
ASSERT_OK(builder->Finish());
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
OpenTable(options, writable->contents(), &table_reader);
|
||||
const auto& stats = table_reader->GetTableStats().user_collected_stats;
|
||||
|
||||
uint64_t deleted = 0;
|
||||
Slice key(stats.at(InternalKeyTableStatsNames::kDeletedKeys));
|
||||
ASSERT_TRUE(GetVarint64(&key, &deleted));
|
||||
ASSERT_EQ(4u, deleted);
|
||||
|
||||
if (sanitized) {
|
||||
uint32_t starts_with_A = 0;
|
||||
Slice key(stats.at("Count"));
|
||||
ASSERT_TRUE(GetVarint32(&key, &starts_with_A));
|
||||
ASSERT_EQ(1u, starts_with_A);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return rocksdb::test::RunAllTests();
|
||||
}
|
||||
+59
-42
@@ -14,7 +14,7 @@ TransactionLogIteratorImpl::TransactionLogIteratorImpl(
|
||||
const EnvOptions& soptions,
|
||||
const SequenceNumber seq,
|
||||
std::unique_ptr<VectorLogPtr> files,
|
||||
SequenceNumber const * const lastFlushedSequence) :
|
||||
DBImpl const * const dbimpl) :
|
||||
dir_(dir),
|
||||
options_(options),
|
||||
soptions_(soptions),
|
||||
@@ -24,10 +24,10 @@ TransactionLogIteratorImpl::TransactionLogIteratorImpl(
|
||||
isValid_(false),
|
||||
currentFileIndex_(0),
|
||||
currentBatchSeq_(0),
|
||||
currentBatchCount_(0),
|
||||
lastFlushedSequence_(lastFlushedSequence) {
|
||||
assert(startingSequenceNumber_ <= *lastFlushedSequence_);
|
||||
currentLastSeq_(0),
|
||||
dbimpl_(dbimpl) {
|
||||
assert(files_ != nullptr);
|
||||
assert(dbimpl_ != nullptr);
|
||||
|
||||
reporter_.env = options_->env;
|
||||
reporter_.info_log = options_->info_log.get();
|
||||
@@ -77,7 +77,7 @@ bool TransactionLogIteratorImpl::RestrictedRead(
|
||||
Slice* record,
|
||||
std::string* scratch) {
|
||||
// Don't read if no more complete entries to read from logs
|
||||
if (currentBatchSeq_ >= *lastFlushedSequence_) {
|
||||
if (currentLastSeq_ >= dbimpl_->GetLatestSequenceNumber()) {
|
||||
return false;
|
||||
}
|
||||
return currentLogReader_->ReadRecord(record, scratch);
|
||||
@@ -90,11 +90,6 @@ void TransactionLogIteratorImpl::SeekToStartSequence(
|
||||
Slice record;
|
||||
started_ = false;
|
||||
isValid_ = false;
|
||||
if (startingSequenceNumber_ > *lastFlushedSequence_) {
|
||||
currentStatus_ = Status::IOError("Looking for a sequence, "
|
||||
"which is not flushed yet.");
|
||||
return;
|
||||
}
|
||||
if (files_->size() <= startFileIndex) {
|
||||
return;
|
||||
}
|
||||
@@ -110,8 +105,7 @@ void TransactionLogIteratorImpl::SeekToStartSequence(
|
||||
continue;
|
||||
}
|
||||
UpdateCurrentWriteBatch(record);
|
||||
if (currentBatchSeq_ + currentBatchCount_ - 1 >=
|
||||
startingSequenceNumber_) {
|
||||
if (currentLastSeq_ >= startingSequenceNumber_) {
|
||||
if (strict && currentBatchSeq_ != startingSequenceNumber_) {
|
||||
currentStatus_ = Status::Corruption("Gap in sequence number. Could not "
|
||||
"seek to required sequence number");
|
||||
@@ -128,38 +122,57 @@ void TransactionLogIteratorImpl::SeekToStartSequence(
|
||||
isValid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find start sequence in first file. Normally this must be the
|
||||
// only file. Otherwise log the error and let the iterator return next entry
|
||||
if (files_->size() != 1) {
|
||||
// If strict is set, we want to seek exactly till the start sequence and it
|
||||
// should have been present in the file we scanned above
|
||||
if (strict) {
|
||||
currentStatus_ = Status::Corruption("Gap in sequence number. Could not "
|
||||
"seek to required sequence number");
|
||||
reporter_.Info(currentStatus_.ToString().c_str());
|
||||
} else if (files_->size() != 1) {
|
||||
currentStatus_ = Status::Corruption("Start sequence was not found, "
|
||||
"skipping to the next available");
|
||||
reporter_.Corruption(0, currentStatus_);
|
||||
started_ = true; // Let Next find next available entry
|
||||
Next();
|
||||
reporter_.Info(currentStatus_.ToString().c_str());
|
||||
// Let NextImpl find the next available entry. started_ remains false
|
||||
// because we don't want to check for gaps while moving to start sequence
|
||||
NextImpl(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionLogIteratorImpl::Next() {
|
||||
return NextImpl(false);
|
||||
}
|
||||
|
||||
void TransactionLogIteratorImpl::NextImpl(bool internal) {
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
isValid_ = false;
|
||||
if (!started_) { // Runs every time until we can seek to the start sequence
|
||||
if (!internal && !started_) {
|
||||
// Runs every time until we can seek to the start sequence
|
||||
return SeekToStartSequence();
|
||||
}
|
||||
while(true) {
|
||||
assert(currentLogReader_);
|
||||
if (currentBatchSeq_ < *lastFlushedSequence_) {
|
||||
if (currentLogReader_->IsEOF()) {
|
||||
currentLogReader_->UnmarkEOF();
|
||||
}
|
||||
while (currentLogReader_->ReadRecord(&record, &scratch)) {
|
||||
if (record.size() < 12) {
|
||||
reporter_.Corruption(
|
||||
record.size(), Status::Corruption("very small log record"));
|
||||
continue;
|
||||
} else {
|
||||
return UpdateCurrentWriteBatch(record);
|
||||
if (currentLogReader_->IsEOF()) {
|
||||
currentLogReader_->UnmarkEOF();
|
||||
}
|
||||
while (RestrictedRead(&record, &scratch)) {
|
||||
if (record.size() < 12) {
|
||||
reporter_.Corruption(
|
||||
record.size(), Status::Corruption("very small log record"));
|
||||
continue;
|
||||
} else {
|
||||
// started_ should be true if called by application
|
||||
assert(internal || started_);
|
||||
// started_ should be false if called internally
|
||||
assert(!internal || !started_);
|
||||
UpdateCurrentWriteBatch(record);
|
||||
if (internal && !started_) {
|
||||
started_ = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,27 +187,27 @@ void TransactionLogIteratorImpl::Next() {
|
||||
}
|
||||
} else {
|
||||
isValid_ = false;
|
||||
if (currentBatchSeq_ == *lastFlushedSequence_) {
|
||||
if (currentLastSeq_ == dbimpl_->GetLatestSequenceNumber()) {
|
||||
currentStatus_ = Status::OK();
|
||||
} else {
|
||||
currentStatus_ = Status::IOError(" NO MORE DATA LEFT");
|
||||
currentStatus_ = Status::IOError("NO MORE DATA LEFT");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TransactionLogIteratorImpl::IsBatchContinuous(
|
||||
bool TransactionLogIteratorImpl::IsBatchExpected(
|
||||
const WriteBatch* batch,
|
||||
const SequenceNumber expectedSeq) {
|
||||
assert(batch);
|
||||
SequenceNumber batchSeq = WriteBatchInternal::Sequence(batch);
|
||||
if (started_ && batchSeq != expectedSeq) {
|
||||
if (batchSeq != expectedSeq) {
|
||||
char buf[200];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Discontinuity in log records. Got seq=%lu, Expected seq=%lu, "
|
||||
"Last flushed seq=%lu. Log iterator will seek the correct batch.",
|
||||
batchSeq, expectedSeq, *lastFlushedSequence_);
|
||||
"Last flushed seq=%lu.Log iterator will reseek the correct batch.",
|
||||
batchSeq, expectedSeq, dbimpl_->GetLatestSequenceNumber());
|
||||
reporter_.Info(buf);
|
||||
return false;
|
||||
}
|
||||
@@ -202,11 +215,12 @@ bool TransactionLogIteratorImpl::IsBatchContinuous(
|
||||
}
|
||||
|
||||
void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
WriteBatch* batch = new WriteBatch();
|
||||
WriteBatchInternal::SetContents(batch, record);
|
||||
std::unique_ptr<WriteBatch> batch(new WriteBatch());
|
||||
WriteBatchInternal::SetContents(batch.get(), record);
|
||||
|
||||
SequenceNumber expectedSeq = currentBatchSeq_ + currentBatchCount_;
|
||||
if (!IsBatchContinuous(batch, expectedSeq)) {
|
||||
SequenceNumber expectedSeq = currentLastSeq_ + 1;
|
||||
// If the iterator has started, then confirm that we get continuous batches
|
||||
if (started_ && !IsBatchExpected(batch.get(), expectedSeq)) {
|
||||
// Seek to the batch having expected sequence number
|
||||
if (expectedSeq < files_->at(currentFileIndex_)->StartSequence()) {
|
||||
// Expected batch must lie in the previous log file
|
||||
@@ -214,15 +228,18 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
currentFileIndex_ = (currentFileIndex_ >= 0) ? currentFileIndex_ : 0;
|
||||
}
|
||||
startingSequenceNumber_ = expectedSeq;
|
||||
// currentStatus_ will be set to Ok if reseek succeeds
|
||||
currentStatus_ = Status::NotFound("Gap in sequence numbers");
|
||||
return SeekToStartSequence(currentFileIndex_, true);
|
||||
}
|
||||
|
||||
currentBatchSeq_ = WriteBatchInternal::Sequence(batch);
|
||||
currentBatchCount_ = WriteBatchInternal::Count(batch);
|
||||
currentBatchSeq_ = WriteBatchInternal::Sequence(batch.get());
|
||||
currentLastSeq_ = currentBatchSeq_ +
|
||||
WriteBatchInternal::Count(batch.get()) - 1;
|
||||
// currentBatchSeq_ can only change here
|
||||
assert(currentBatchSeq_ <= *lastFlushedSequence_);
|
||||
assert(currentLastSeq_ <= dbimpl_->GetLatestSequenceNumber());
|
||||
|
||||
currentBatch_.reset(batch);
|
||||
currentBatch_ = move(batch);
|
||||
isValid_ = true;
|
||||
currentStatus_ = Status::OK();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/filename.h"
|
||||
|
||||
@@ -70,7 +71,7 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
const EnvOptions& soptions,
|
||||
const SequenceNumber seqNum,
|
||||
std::unique_ptr<VectorLogPtr> files,
|
||||
SequenceNumber const * const lastFlushedSequence);
|
||||
DBImpl const * const dbimpl);
|
||||
|
||||
virtual bool Valid();
|
||||
|
||||
@@ -95,16 +96,21 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
Status OpenLogFile(const LogFile* logFile, unique_ptr<SequentialFile>* file);
|
||||
LogReporter reporter_;
|
||||
SequenceNumber currentBatchSeq_; // sequence number at start of current batch
|
||||
uint64_t currentBatchCount_; // count in current batch
|
||||
SequenceNumber const * const lastFlushedSequence_;
|
||||
SequenceNumber currentLastSeq_; // last sequence in the current batch
|
||||
DBImpl const * const dbimpl_; // The db on whose log files this iterates
|
||||
|
||||
// Reads from transaction log only if the writebatch record has been written
|
||||
bool RestrictedRead(Slice* record, std::string* scratch);
|
||||
// Seeks to startingSequenceNumber reading from startFileIndex in files_.
|
||||
// If strict is set,then must get a batch starting with startingSequenceNumber
|
||||
void SeekToStartSequence(uint64_t startFileIndex = 0, bool strict = false);
|
||||
// Check if batch is continuous starting from expectedSeq, else return false
|
||||
bool IsBatchContinuous(const WriteBatch* batch, SequenceNumber expectedSeq);
|
||||
// Implementation of Next. SeekToStartSequence calls it internally with
|
||||
// internal=true to let it find next entry even if it has to jump gaps because
|
||||
// the iterator may start off from the first available entry but promises to
|
||||
// be continuous after that
|
||||
void NextImpl(bool internal = false);
|
||||
// Check if batch is expected, else return false
|
||||
bool IsBatchExpected(const WriteBatch* batch, SequenceNumber expectedSeq);
|
||||
// Update current batch if a continuous batch is found, else return false
|
||||
void UpdateCurrentWriteBatch(const Slice& record);
|
||||
Status OpenLogReader(const LogFile* file);
|
||||
|
||||
+3
-3
@@ -34,9 +34,9 @@ struct FileMetaData {
|
||||
|
||||
class VersionEdit {
|
||||
public:
|
||||
/* implicit */ VersionEdit(int number_levels) :
|
||||
number_levels_(number_levels) {
|
||||
Clear();
|
||||
explicit VersionEdit(int number_levels) :
|
||||
number_levels_(number_levels) {
|
||||
Clear();
|
||||
}
|
||||
~VersionEdit() { }
|
||||
|
||||
|
||||
+28
-20
@@ -19,7 +19,7 @@
|
||||
#include "db/table_cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "util/coding.h"
|
||||
@@ -294,11 +294,11 @@ struct Saver {
|
||||
};
|
||||
}
|
||||
|
||||
// Called from TableCache::Get and InternalGet when file/block in which key may
|
||||
// exist are not there in TableCache/BlockCache respectively. In this case we
|
||||
// can't guarantee that key does not exist and are not permitted to do IO to be
|
||||
// certain.Set the status=kFound and value_found=false to let the caller know
|
||||
// that key may exist but is not there in memory
|
||||
// Called from TableCache::Get and Table::Get when file/block in which
|
||||
// key may exist are not there in TableCache/BlockCache respectively. In this
|
||||
// case we can't guarantee that key does not exist and are not permitted to do
|
||||
// IO to be certain.Set the status=kFound and value_found=false to let the
|
||||
// caller know that key may exist but is not there in memory
|
||||
static void MarkKeyMayExist(void* arg) {
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
s->state = kFound;
|
||||
@@ -1248,18 +1248,8 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu,
|
||||
}
|
||||
|
||||
if (!descriptor_log_ || new_descriptor_log) {
|
||||
// No reason to unlock *mu here since we only hit this path in the
|
||||
// first call to LogAndApply (when opening the database).
|
||||
assert(!descriptor_log_ || new_descriptor_log);
|
||||
new_manifest_file = DescriptorFileName(dbname_, manifest_file_number_);
|
||||
edit->SetNextFile(next_file_number_);
|
||||
unique_ptr<WritableFile> descriptor_file;
|
||||
s = env_->NewWritableFile(new_manifest_file, &descriptor_file,
|
||||
storage_options_);
|
||||
if (s.ok()) {
|
||||
descriptor_log_.reset(new log::Writer(std::move(descriptor_file)));
|
||||
s = WriteSnapshot(descriptor_log_.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock during expensive MANIFEST log write. New writes cannot get here
|
||||
@@ -1271,6 +1261,18 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu,
|
||||
|
||||
mu->Unlock();
|
||||
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
if (!new_manifest_file.empty()) {
|
||||
unique_ptr<WritableFile> descriptor_file;
|
||||
s = env_->NewWritableFile(new_manifest_file, &descriptor_file,
|
||||
storage_options_);
|
||||
if (s.ok()) {
|
||||
descriptor_log_.reset(new log::Writer(std::move(descriptor_file)));
|
||||
s = WriteSnapshot(descriptor_log_.get());
|
||||
}
|
||||
}
|
||||
|
||||
// The calls to Finalize and UpdateFilesBySize are cpu-heavy
|
||||
// and is best called outside the mutex.
|
||||
Finalize(v, size_being_compacted);
|
||||
@@ -1316,6 +1318,7 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu,
|
||||
// find offset in manifest file where this version is stored.
|
||||
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
|
||||
|
||||
LogFlush(options_->info_log);
|
||||
mu->Lock();
|
||||
// cache the manifest_file_size so that it can be used to rollover in the
|
||||
// next call to LogAndApply
|
||||
@@ -1920,12 +1923,12 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
|
||||
} else {
|
||||
// "ikey" falls in the range for this table. Add the
|
||||
// approximate offset of "ikey" within the table.
|
||||
Table* tableptr;
|
||||
TableReader* table_reader_ptr;
|
||||
Iterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), storage_options_, files[i]->number,
|
||||
files[i]->file_size, &tableptr);
|
||||
if (tableptr != nullptr) {
|
||||
result += tableptr->ApproximateOffsetOf(ikey.Encode());
|
||||
files[i]->file_size, &table_reader_ptr);
|
||||
if (table_reader_ptr != nullptr) {
|
||||
result += table_reader_ptr->ApproximateOffsetOf(ikey.Encode());
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
@@ -2467,6 +2470,10 @@ Compaction* VersionSet::PickCompactionUniversal(int level, double score) {
|
||||
// remember this currently undergoing compaction
|
||||
compactions_in_progress_[level].insert(c);
|
||||
|
||||
// Record whether this compaction includes all sst files.
|
||||
// For now, it is only relevant in universal compaction mode.
|
||||
c->is_full_compaction_ = (c->inputs_[0].size() == current_->files_[0].size());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -2921,6 +2928,7 @@ Compaction::Compaction(int level, int out_level, uint64_t target_file_size,
|
||||
parent_index_(-1),
|
||||
score_(0),
|
||||
bottommost_level_(false),
|
||||
is_full_compaction_(false),
|
||||
level_ptrs_(std::vector<size_t>(number_levels)) {
|
||||
edit_ = new VersionEdit(number_levels_);
|
||||
for (int i = 0; i < number_levels_; i++) {
|
||||
|
||||
@@ -589,6 +589,9 @@ class Compaction {
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool BottomMostLevel() { return bottommost_level_; }
|
||||
|
||||
// Does this compaction include all sst files?
|
||||
bool IsFullCompaction() { return is_full_compaction_; }
|
||||
|
||||
private:
|
||||
friend class Version;
|
||||
friend class VersionSet;
|
||||
@@ -624,6 +627,8 @@ class Compaction {
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
// Does this compaction include all sst files?
|
||||
bool is_full_compaction_;
|
||||
|
||||
// level_ptrs_ holds indices into input_version_->levels_: our state
|
||||
// is that we are positioned at one of the file ranges for each
|
||||
|
||||
+6
-1
@@ -188,7 +188,12 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
virtual void Put(const Slice& key, const Slice& value) {
|
||||
mem_->Add(sequence_, kTypeValue, key, value);
|
||||
if (options_->inplace_update_support
|
||||
&& mem_->Update(sequence_, kTypeValue, key, value)) {
|
||||
RecordTick(options_->statistics, NUMBER_KEYS_UPDATED);
|
||||
} else {
|
||||
mem_->Add(sequence_, kTypeValue, key, value);
|
||||
}
|
||||
sequence_++;
|
||||
}
|
||||
virtual void Merge(const Slice& key, const Slice& value) {
|
||||
|
||||
@@ -48,7 +48,7 @@ class WriteBatchInternal {
|
||||
// Drops deletes in batch if filter_del is set to true and
|
||||
// db->KeyMayExist returns false
|
||||
static Status InsertInto(const WriteBatch* batch, MemTable* memtable,
|
||||
const Options* opts = nullptr, DB* db = nullptr,
|
||||
const Options* opts, DB* db = nullptr,
|
||||
const bool filter_del = false);
|
||||
|
||||
static void Append(WriteBatch* dst, const WriteBatch* src);
|
||||
|
||||
@@ -25,7 +25,8 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
MemTable* mem = new MemTable(cmp, factory);
|
||||
mem->Ref();
|
||||
std::string state;
|
||||
Status s = WriteBatchInternal::InsertInto(b, mem);
|
||||
Options options;
|
||||
Status s = WriteBatchInternal::InsertInto(b, mem, &options);
|
||||
int count = 0;
|
||||
Iterator* iter = mem->NewIterator();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
|
||||
@@ -16,8 +16,14 @@ class Slice;
|
||||
|
||||
class CompactionFilter {
|
||||
public:
|
||||
virtual ~CompactionFilter() {}
|
||||
|
||||
// Context information of a compaction run
|
||||
struct Context {
|
||||
// Does this compaction run include all data files
|
||||
bool is_full_compaction;
|
||||
};
|
||||
|
||||
virtual ~CompactionFilter() {}
|
||||
|
||||
// The compaction process invokes this
|
||||
// method for kv that is being compacted. A return value
|
||||
@@ -44,26 +50,28 @@ class CompactionFilter {
|
||||
// Each compaction will create a new CompactionFilter allowing the
|
||||
// application to know about different campactions
|
||||
class CompactionFilterFactory {
|
||||
public:
|
||||
virtual ~CompactionFilterFactory() { };
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter() = 0;
|
||||
public:
|
||||
virtual ~CompactionFilterFactory() { };
|
||||
|
||||
// Returns a name that identifies this compaction filter factory.
|
||||
virtual const char* Name() const = 0;
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) = 0;
|
||||
|
||||
// Returns a name that identifies this compaction filter factory.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
// Default implementaion of CompactionFilterFactory which does not
|
||||
// return any filter
|
||||
class DefaultCompactionFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter() override {
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
public:
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter(const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "DefaultCompactionFilterFactory";
|
||||
}
|
||||
virtual const char* Name() const override {
|
||||
return "DefaultCompactionFilterFactory";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+13
-21
@@ -81,8 +81,8 @@ class DB {
|
||||
DB() { }
|
||||
virtual ~DB();
|
||||
|
||||
// Set the database entry for "key" to "value". Returns OK on success,
|
||||
// and a non-OK status on error.
|
||||
// Set the database entry for "key" to "value".
|
||||
// Returns OK on success, and a non-OK status on error.
|
||||
// Note: consider setting options.sync = true.
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
const Slice& key,
|
||||
@@ -257,32 +257,24 @@ class DB {
|
||||
// Retrieve the sorted list of all wal files with earliest file first
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files) = 0;
|
||||
|
||||
// Delete wal files in files. These can be either live or archived.
|
||||
// Returns Status::OK if all files could be deleted, otherwise Status::IOError
|
||||
virtual Status DeleteWalFiles(const VectorLogPtr& files) = 0;
|
||||
|
||||
// The sequence number of the most recent transaction.
|
||||
virtual SequenceNumber GetLatestSequenceNumber() = 0;
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const = 0;
|
||||
|
||||
// Return's an iterator for all writes since the sequence number
|
||||
// Status::ok if iterator is valid.
|
||||
// The iterator internally holds references to the available log files.
|
||||
// It automatically takes care of closing a file with no-updates left, and
|
||||
// opening the next one.
|
||||
// If the sequence number is non existent. it returns an iterator at a seq_no
|
||||
// just greater than the requested seq_no.
|
||||
// Must set WAL_ttl_seconds to a large value to use this api.
|
||||
// else the WAL files will get
|
||||
// Sets iter to an iterator that is positioned at a write-batch containing
|
||||
// seq_number. If the sequence number is non existent, it returns an iterator
|
||||
// at the first available seq_no after the requested seq_no
|
||||
// Returns Status::Ok if iterator is valid
|
||||
// Must set WAL_ttl_seconds or WAL_size_limit_MB to large values to
|
||||
// use this api, else the WAL files will get
|
||||
// cleared aggressively and the iterator might keep getting invalid before
|
||||
// an update is read.
|
||||
virtual Status GetUpdatesSince(SequenceNumber seq_number,
|
||||
unique_ptr<TransactionLogIterator>* iter) = 0;
|
||||
|
||||
// Delete the file name from the db directory and update the internal
|
||||
// state to reflect that.
|
||||
virtual Status DeleteFile(std::string name) {
|
||||
return Status::OK();
|
||||
}
|
||||
// Delete the file name from the db directory and update the internal state to
|
||||
// reflect that. Supports deletion of sst and log files only. 'name' must be
|
||||
// path relative to the db directory. eg. 000001.sst, /archive/000003.log
|
||||
virtual Status DeleteFile(std::string name) = 0;
|
||||
|
||||
// Returns a list of all table files with their level, start key
|
||||
// and end key
|
||||
|
||||
@@ -373,6 +373,11 @@ class WritableFile {
|
||||
*block_size = preallocation_block_size_;
|
||||
}
|
||||
|
||||
// For documentation, refer to RandomAccessFile::GetUniqueId()
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const {
|
||||
return 0; // Default implementation to prevent issues with backwards
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
@@ -492,6 +497,8 @@ class Logger {
|
||||
virtual size_t GetLogFileSize() const {
|
||||
return DO_NOT_SUPPORT_GET_LOG_FILE_SIZE;
|
||||
}
|
||||
// Flush to the OS buffers
|
||||
virtual void Flush() {}
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
@@ -511,6 +518,9 @@ class FileLock {
|
||||
void operator=(const FileLock&);
|
||||
};
|
||||
|
||||
|
||||
extern void LogFlush(const shared_ptr<Logger>& info_log);
|
||||
|
||||
// Log the specified data to *info_log if info_log is non-nullptr.
|
||||
extern void Log(const shared_ptr<Logger>& info_log, const char* format, ...)
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
@@ -518,6 +528,8 @@ extern void Log(const shared_ptr<Logger>& info_log, const char* format, ...)
|
||||
# endif
|
||||
;
|
||||
|
||||
extern void LogFlush(Logger *info_log);
|
||||
|
||||
extern void Log(Logger* info_log, const char* format, ...)
|
||||
# if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((__format__ (__printf__, 2, 3)))
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
// Users can implement their own memtable representations. We include four
|
||||
// types built in:
|
||||
// - SkipListRep: This is the default; it is backed by a skip list.
|
||||
// - TransformRep: This is backed by an std::unordered_map<Slice,
|
||||
// std::set>. On construction, they are given a SliceTransform object. This
|
||||
// - TransformRep: This is backed by an custom hash map.
|
||||
// On construction, they are given a SliceTransform object. This
|
||||
// object is applied to the user key of stored items which indexes into the
|
||||
// unordered map to yield a set containing all records that share the same user
|
||||
// key under the transform function.
|
||||
// hash map to yield a skiplist containing all records that share the same
|
||||
// user key under the transform function.
|
||||
// - UnsortedRep: A subclass of TransformRep where the transform function is
|
||||
// the identity function. Optimized for point lookups.
|
||||
// - PrefixHashRep: A subclass of TransformRep where the transform function is
|
||||
@@ -130,6 +130,12 @@ class MemTableRep {
|
||||
return GetIterator();
|
||||
}
|
||||
|
||||
// Return an iterator that has a special Seek semantics. The result of
|
||||
// a Seek might only include keys with the same prefix as the target key.
|
||||
virtual std::shared_ptr<Iterator> GetDynamicPrefixIterator() {
|
||||
return GetIterator();
|
||||
}
|
||||
|
||||
protected:
|
||||
// When *key is an internal key concatenated with the value, returns the
|
||||
// user key.
|
||||
@@ -254,6 +260,68 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// NO LOCKS VERSION
|
||||
|
||||
// The same as TransformRepFactory except it doesn't use locks.
|
||||
// Experimental, will replace TransformRepFactory once we are sure
|
||||
// it performs better
|
||||
class TransformRepNoLockFactory : public MemTableRepFactory {
|
||||
public:
|
||||
explicit TransformRepNoLockFactory(const SliceTransform* transform,
|
||||
size_t bucket_count)
|
||||
: transform_(transform),
|
||||
bucket_count_(bucket_count) { }
|
||||
|
||||
virtual ~TransformRepNoLockFactory() { delete transform_; }
|
||||
|
||||
virtual std::shared_ptr<MemTableRep> CreateMemTableRep(
|
||||
MemTableRep::KeyComparator&, Arena*) override;
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "TransformRepNoLockFactory";
|
||||
}
|
||||
|
||||
const SliceTransform* GetTransform() { return transform_; }
|
||||
|
||||
protected:
|
||||
const SliceTransform* transform_;
|
||||
const size_t bucket_count_;
|
||||
};
|
||||
|
||||
// UnsortedReps bin user keys based on an identity function transform -- that
|
||||
// is, transform(key) = key. This optimizes for point look-ups.
|
||||
//
|
||||
// Parameters: See TransformRepNoLockFactory.
|
||||
class UnsortedRepNoLockFactory : public TransformRepNoLockFactory {
|
||||
public:
|
||||
explicit UnsortedRepNoLockFactory(size_t bucket_count = 1000000)
|
||||
: TransformRepNoLockFactory(NewNoopTransform(),
|
||||
bucket_count) { }
|
||||
virtual const char* Name() const override {
|
||||
return "UnsortedRepNoLockFactory";
|
||||
}
|
||||
};
|
||||
|
||||
// PrefixHashReps bin user keys based on a fixed-size prefix. This optimizes for
|
||||
// short ranged scans over a given prefix.
|
||||
//
|
||||
// Parameters: See TransformRepNoLockFactory.
|
||||
class PrefixHashRepNoLockFactory : public TransformRepNoLockFactory {
|
||||
public:
|
||||
explicit PrefixHashRepNoLockFactory(const SliceTransform* prefix_extractor,
|
||||
size_t bucket_count = 1000000)
|
||||
: TransformRepNoLockFactory(prefix_extractor, bucket_count)
|
||||
{ }
|
||||
|
||||
virtual std::shared_ptr<MemTableRep> CreateMemTableRep(
|
||||
MemTableRep::KeyComparator&, Arena*) override;
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "PrefixHashRepNoLockFactory";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // STORAGE_ROCKSDB_DB_MEMTABLEREP_H_
|
||||
+56
-11
@@ -10,8 +10,12 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table_stats.h"
|
||||
#include "rocksdb/universal_compaction.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
@@ -27,6 +31,7 @@ class MergeOperator;
|
||||
class Snapshot;
|
||||
class CompactionFilter;
|
||||
class CompactionFilterFactory;
|
||||
class TableFactory;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
@@ -34,7 +39,7 @@ using std::shared_ptr;
|
||||
// sequence of key,value pairs. Each block may be compressed before
|
||||
// being stored in a file. The following enum describes which
|
||||
// compression method (if any) is used to compress a block.
|
||||
enum CompressionType {
|
||||
enum CompressionType : char {
|
||||
// NOTE: do not change the values of existing entries, as these are
|
||||
// part of the persistent format on disk.
|
||||
kNoCompression = 0x0,
|
||||
@@ -43,7 +48,7 @@ enum CompressionType {
|
||||
kBZip2Compression = 0x3
|
||||
};
|
||||
|
||||
enum CompactionStyle {
|
||||
enum CompactionStyle : char {
|
||||
kCompactionStyleLevel = 0x0, // level based compaction style
|
||||
kCompactionStyleUniversal = 0x1 // Universal compaction style
|
||||
};
|
||||
@@ -109,6 +114,9 @@ struct Options {
|
||||
// errors. This may have unforeseen ramifications: for example, a
|
||||
// corruption of one DB entry may cause a large number of entries to
|
||||
// become unreadable or for the entire DB to become unopenable.
|
||||
// If any of the writes to the database fails (Put, Delete, Merge, Write),
|
||||
// the database will switch to read-only mode and fail all other
|
||||
// Write operations.
|
||||
// Default: false
|
||||
bool paranoid_checks;
|
||||
|
||||
@@ -169,6 +177,11 @@ struct Options {
|
||||
// Default: nullptr
|
||||
shared_ptr<Cache> block_cache;
|
||||
|
||||
// If non-NULL use the specified cache for compressed blocks.
|
||||
// If NULL, rocksdb will not use a compressed block cache.
|
||||
// Default: nullptr
|
||||
shared_ptr<Cache> block_cache_compressed;
|
||||
|
||||
// Approximate size of user data packed per block. Note that the
|
||||
// block size specified here corresponds to uncompressed data. The
|
||||
// actual size of the unit read from disk may be smaller if
|
||||
@@ -476,15 +489,20 @@ struct Options {
|
||||
// be issued on this database.
|
||||
bool disable_auto_compactions;
|
||||
|
||||
// The number of seconds a WAL(write ahead log) should be kept after it has
|
||||
// been marked as Not Live. If the value is set. The WAL files are moved to
|
||||
// the archive directory and deleted after the given TTL.
|
||||
// If set to 0, WAL files are deleted as soon as they are not required by
|
||||
// the database.
|
||||
// If set to std::numeric_limits<uint64_t>::max() the WAL files will never be
|
||||
// deleted.
|
||||
// Default : 0
|
||||
// The following two fields affect how archived logs will be deleted.
|
||||
// 1. If both set to 0, logs will be deleted asap and will not get into
|
||||
// the archive.
|
||||
// 2. If WAL_ttl_seconds is 0 and WAL_size_limit_MB is not 0,
|
||||
// WAL files will be checked every 10 min and if total size is greater
|
||||
// then WAL_size_limit_MB, they will be deleted starting with the
|
||||
// earliest until size_limit is met. All empty files will be deleted.
|
||||
// 3. If WAL_ttl_seconds is not 0 and WAL_size_limit_MB is 0, then
|
||||
// WAL files will be checked every WAL_ttl_secondsi / 2 and those which
|
||||
// are older than WAL_ttl_seconds will be deleted.
|
||||
// 4. If both are not 0, WAL files will be checked every 10 min and both
|
||||
// checks will be performed with ttl being first.
|
||||
uint64_t WAL_ttl_seconds;
|
||||
uint64_t WAL_size_limit_MB;
|
||||
|
||||
// Number of bytes to preallocate (via fallocate) the manifest
|
||||
// files. Default is 4mb, which is reasonable to reduce random IO
|
||||
@@ -574,6 +592,11 @@ struct Options {
|
||||
// MemTableRep.
|
||||
std::shared_ptr<MemTableRepFactory> memtable_factory;
|
||||
|
||||
// This is a factory that provides TableFactory objects.
|
||||
// Default: a factory that provides a default implementation of
|
||||
// Table and TableBuilder.
|
||||
std::shared_ptr<TableFactory> table_factory;
|
||||
|
||||
// This is a factory that provides compaction filter objects which allow
|
||||
// an application to modify/delete a key-value during background compaction.
|
||||
// Default: a factory that doesn't provide any object
|
||||
@@ -583,6 +606,23 @@ struct Options {
|
||||
// to data file.
|
||||
// Default: true
|
||||
bool purge_log_after_memtable_flush;
|
||||
|
||||
// This option allows user to to collect their own interested statistics of
|
||||
// the tables.
|
||||
// Default: emtpy vector -- no user-defined statistics collection will be
|
||||
// performed.
|
||||
std::vector<std::shared_ptr<TableStatsCollector>> table_stats_collectors;
|
||||
|
||||
// Allows thread-safe inplace updates. Requires Updates iff
|
||||
// * key exists in current memtable
|
||||
// * new sizeof(new_value) <= sizeof(old_value)
|
||||
// * old_value for that key is a put i.e. kTypeValue
|
||||
// Default: false.
|
||||
bool inplace_update_support;
|
||||
|
||||
// Number of locks used for inplace update
|
||||
// Default: 10000, if inplace_update_support = true, else 0.
|
||||
size_t inplace_update_num_locks;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -609,6 +649,10 @@ struct ReadOptions {
|
||||
// Default: true
|
||||
bool fill_cache;
|
||||
|
||||
// If this option is set and memtable implementation allows, Seek
|
||||
// might only return keys with the same prefix as the seek-key
|
||||
bool prefix_seek;
|
||||
|
||||
// If "snapshot" is non-nullptr, read as of the supplied snapshot
|
||||
// (which must belong to the DB that is being read and which must
|
||||
// not have been released). If "snapshot" is nullptr, use an impliicit
|
||||
@@ -638,13 +682,14 @@ struct ReadOptions {
|
||||
ReadOptions()
|
||||
: verify_checksums(false),
|
||||
fill_cache(true),
|
||||
prefix_seek(false),
|
||||
snapshot(nullptr),
|
||||
prefix(nullptr),
|
||||
read_tier(kReadAllTier) {
|
||||
}
|
||||
ReadOptions(bool cksum, bool cache) :
|
||||
verify_checksums(cksum), fill_cache(cache),
|
||||
snapshot(nullptr), prefix(nullptr),
|
||||
prefix_seek(false), snapshot(nullptr), prefix(nullptr),
|
||||
read_tier(kReadAllTier) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,14 +22,18 @@ struct PerfContext {
|
||||
void Reset(); // reset all performance counters to zero
|
||||
|
||||
uint64_t user_key_comparison_count; // total number of user key comparisons
|
||||
uint64_t block_cache_hit_count;
|
||||
uint64_t block_read_count;
|
||||
uint64_t block_read_byte;
|
||||
uint64_t block_read_time;
|
||||
uint64_t block_checksum_time;
|
||||
uint64_t block_decompress_time;
|
||||
uint64_t block_cache_hit_count; // total number of block cache hits
|
||||
uint64_t block_read_count; // total number of block reads (with IO)
|
||||
uint64_t block_read_byte; // total number of bytes from block reads
|
||||
uint64_t block_read_time; // total time spent on block reads
|
||||
uint64_t block_checksum_time; // total time spent on block checksum
|
||||
uint64_t block_decompress_time; // total time spent on block decompression
|
||||
// total number of internal keys skipped over during iteration (overwritten or
|
||||
// deleted, to be more specific, hidden by a put or delete of the same key)
|
||||
uint64_t internal_key_skipped_count;
|
||||
// total number of deletes skipped over during iteration
|
||||
uint64_t internal_delete_skipped_count;
|
||||
uint64_t wal_write_time; // total time spent on writing to WAL
|
||||
};
|
||||
|
||||
extern __thread PerfContext perf_context;
|
||||
|
||||
@@ -39,6 +39,8 @@ enum Tickers {
|
||||
NUMBER_KEYS_WRITTEN,
|
||||
// Number of Keys read,
|
||||
NUMBER_KEYS_READ,
|
||||
// Number keys updated, if inplace update is enabled
|
||||
NUMBER_KEYS_UPDATED,
|
||||
// Bytes written / read
|
||||
BYTES_WRITTEN,
|
||||
BYTES_READ,
|
||||
@@ -80,6 +82,9 @@ enum Tickers {
|
||||
// transaction log iterator refreshes
|
||||
GET_UPDATES_SINCE_CALLS,
|
||||
|
||||
BLOCK_CACHE_COMPRESSED_MISS, // miss in the compressed block cache
|
||||
BLOCK_CACHE_COMPRESSED_HIT, // hit in the compressed block cache
|
||||
|
||||
TICKER_ENUM_MAX
|
||||
};
|
||||
|
||||
@@ -94,6 +99,7 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{ COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user" },
|
||||
{ NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written" },
|
||||
{ NUMBER_KEYS_READ, "rocksdb.number.keys.read" },
|
||||
{ NUMBER_KEYS_UPDATED, "rocksdb.number.keys.updated" },
|
||||
{ BYTES_WRITTEN, "rocksdb.bytes.written" },
|
||||
{ BYTES_READ, "rocksdb.bytes.read" },
|
||||
{ NO_FILE_CLOSES, "rocksdb.no.file.closes" },
|
||||
@@ -113,7 +119,9 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{ BLOOM_FILTER_PREFIX_CHECKED, "rocksdb.bloom.filter.prefix.checked" },
|
||||
{ BLOOM_FILTER_PREFIX_USEFUL, "rocksdb.bloom.filter.prefix.useful" },
|
||||
{ NUMBER_OF_RESEEKS_IN_ITERATION, "rocksdb.number.reseeks.iteration" },
|
||||
{ GET_UPDATES_SINCE_CALLS, "rocksdb.getupdatessince.calls" }
|
||||
{ GET_UPDATES_SINCE_CALLS, "rocksdb.getupdatessince.calls" },
|
||||
{ BLOCK_CACHE_COMPRESSED_MISS, "rocksdb.block.cachecompressed.miss" },
|
||||
{ BLOCK_CACHE_COMPRESSED_HIT, "rocksdb.block.cachecompressed.hit" }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -144,7 +152,7 @@ enum Histograms {
|
||||
HARD_RATE_LIMIT_DELAY_COUNT,
|
||||
SOFT_RATE_LIMIT_DELAY_COUNT,
|
||||
NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
HISTOGRAM_ENUM_MAX
|
||||
HISTOGRAM_ENUM_MAX,
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
@@ -165,7 +173,7 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{ STALL_L0_NUM_FILES_COUNT, "rocksdb.num.files.stall.count"},
|
||||
{ HARD_RATE_LIMIT_DELAY_COUNT, "rocksdb.hard.rate.limit.delay.count"},
|
||||
{ SOFT_RATE_LIMIT_DELAY_COUNT, "rocksdb.soft.rate.limit.delay.count"},
|
||||
{ NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction" }
|
||||
{ NUM_FILES_IN_SINGLE_COMPACTION, "rocksdb.numfiles.in.singlecompaction" },
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/table_stats.h"
|
||||
#include "rocksdb/options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct Options;
|
||||
class RandomAccessFile;
|
||||
struct ReadOptions;
|
||||
class TableCache;
|
||||
class WritableFile;
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
// TableBuilder provides the interface used to build a Table
|
||||
// (an immutable and sorted map from keys to values).
|
||||
//
|
||||
// Multiple threads can invoke const methods on a TableBuilder without
|
||||
// external synchronization, but if any of the threads may call a
|
||||
// non-const method, all threads accessing the same TableBuilder must use
|
||||
// external synchronization.
|
||||
class TableBuilder {
|
||||
public:
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
virtual ~TableBuilder() {}
|
||||
|
||||
// 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
|
||||
virtual void Add(const Slice& key, const Slice& value) = 0;
|
||||
|
||||
// Return non-ok iff some error has been detected.
|
||||
virtual Status status() const = 0;
|
||||
|
||||
// Finish building the table.
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
virtual Status Finish() = 0;
|
||||
|
||||
// Indicate that the contents of this builder should be abandoned.
|
||||
// If the caller is not going to call Finish(), it must call Abandon()
|
||||
// before destroying this builder.
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
virtual void Abandon() = 0;
|
||||
|
||||
// Number of calls to Add() so far.
|
||||
virtual uint64_t NumEntries() const = 0;
|
||||
|
||||
// Size of the file generated so far. If invoked after a successful
|
||||
// Finish() call, returns the size of the final generated file.
|
||||
virtual uint64_t FileSize() const = 0;
|
||||
};
|
||||
|
||||
// A Table is a sorted map from strings to strings. Tables are
|
||||
// immutable and persistent. A Table may be safely accessed from
|
||||
// multiple threads without external synchronization.
|
||||
class TableReader {
|
||||
public:
|
||||
virtual ~TableReader() {}
|
||||
|
||||
// Determine whether there is a chance that the current table file
|
||||
// contains the key a key starting with iternal_prefix. The specific
|
||||
// table implementation can use bloom filter and/or other heuristic
|
||||
// to filter out this table as a whole.
|
||||
virtual bool PrefixMayMatch(const Slice& internal_prefix) = 0;
|
||||
|
||||
// Returns a new iterator over the table contents.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
virtual Iterator* NewIterator(const ReadOptions&) = 0;
|
||||
|
||||
// Given a key, return an approximate byte offset in the file where
|
||||
// the data for that key begins (or would begin if the key were
|
||||
// present in the file). The returned value is in terms of file
|
||||
// bytes, and so includes effects like compression of the underlying data.
|
||||
// E.g., the approximate offset of the last key in the table will
|
||||
// be close to the file length.
|
||||
virtual uint64_t ApproximateOffsetOf(const Slice& key) = 0;
|
||||
|
||||
// Returns true if the block for the specified key is in cache.
|
||||
// REQUIRES: key is in this table.
|
||||
virtual bool TEST_KeyInCache(const ReadOptions& options,
|
||||
const Slice& key) = 0;
|
||||
|
||||
// Set up the table for Compaction. Might change some parameters with
|
||||
// posix_fadvise
|
||||
virtual void SetupForCompaction() = 0;
|
||||
|
||||
virtual TableStats& GetTableStats() = 0;
|
||||
|
||||
// Calls (*result_handler)(handle_context, ...) repeatedly, starting with
|
||||
// the entry found after a call to Seek(key), until result_handler returns
|
||||
// false, where k is the actual internal key for a row found and v as the
|
||||
// value of the key. didIO is true if I/O is involved in the operation. May
|
||||
// not make such a call if filter policy says that key is not present.
|
||||
//
|
||||
// mark_key_may_exist_handler needs to be called when it is configured to be
|
||||
// memory only and the key is not found in the block cache, with
|
||||
// the parameter to be handle_context.
|
||||
//
|
||||
// readOptions is the options for the read
|
||||
// key is the key to search for
|
||||
virtual Status Get(
|
||||
const ReadOptions& readOptions,
|
||||
const Slice& key,
|
||||
void* handle_context,
|
||||
bool (*result_handler)(void* handle_context, const Slice& k,
|
||||
const Slice& v, bool didIO),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context) = nullptr) = 0;
|
||||
};
|
||||
|
||||
// A base class for table factories
|
||||
class TableFactory {
|
||||
public:
|
||||
virtual ~TableFactory() {}
|
||||
|
||||
// The type of the table.
|
||||
//
|
||||
// The client of this package should switch to a new name whenever
|
||||
// the table format implementation changes.
|
||||
//
|
||||
// Names starting with "rocksdb." are reserved and should not be used
|
||||
// by any clients of this package.
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
// Returns a Table object table that can fetch data from file specified
|
||||
// in parameter file. It's the caller's responsibility to make sure
|
||||
// file is in the correct format.
|
||||
//
|
||||
// GetTableReader() is called in two places:
|
||||
// (1) TableCache::FindTable() calls the function when table cache miss
|
||||
// and cache the table object returned.
|
||||
// (1) SstFileReader (for SST Dump) opens the table and dump the table
|
||||
// contents using the interator of the table.
|
||||
// options and soptions are options. options is the general options.
|
||||
// Multiple configured can be accessed from there, including and not
|
||||
// limited to block cache and key comparators.
|
||||
// file is a file handler to handle the file for the table
|
||||
// file_size is the physical file size of the file
|
||||
// table_reader is the output table reader
|
||||
virtual Status GetTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader) const = 0;
|
||||
|
||||
// Return a table builder to write to a file for this table type.
|
||||
//
|
||||
// It is called in several places:
|
||||
// (1) When flushing memtable to a level-0 output file, it creates a table
|
||||
// builder (In DBImpl::WriteLevel0Table(), by calling BuildTable())
|
||||
// (2) During compaction, it gets the builder for writing compaction output
|
||||
// files in DBImpl::OpenCompactionOutputFile().
|
||||
// (3) When recovering from transaction logs, it creates a table builder to
|
||||
// write to a level-0 output file (In DBImpl::WriteLevel0TableForRecovery,
|
||||
// by calling BuildTable())
|
||||
// (4) When running Repairer, it creates a table builder to convert logs to
|
||||
// SST files (In Repairer::ConvertLogToTable() by calling BuildTable())
|
||||
//
|
||||
// options is the general options. Multiple configured can be acceseed from
|
||||
// there, including and not limited to compression options.
|
||||
// file is a handle of a writable file. It is the caller's responsibility to
|
||||
// keep the file open and close the file after closing the table builder.
|
||||
// compression_type is the compression type to use in this table.
|
||||
virtual TableBuilder* GetTableBuilder(
|
||||
const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) const = 0;
|
||||
};
|
||||
} // namespace rocksdb
|
||||
@@ -6,14 +6,14 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// TableStats contains a bunch of read-only stats of its associated
|
||||
// table.
|
||||
struct TableStats {
|
||||
public:
|
||||
// TODO(kailiu) we do not support user collected stats yet.
|
||||
//
|
||||
// Other than basic table stats, each table may also have the user
|
||||
// collected stats.
|
||||
// The value of the user-collected stats are encoded as raw bytes --
|
||||
@@ -43,4 +43,24 @@ struct TableStats {
|
||||
UserCollectedStats user_collected_stats;
|
||||
};
|
||||
|
||||
// `TableStatsCollector` provides the mechanism for users to collect their own
|
||||
// interested stats. This class is essentially a collection of callback
|
||||
// functions that will be invoked during table building.
|
||||
class TableStatsCollector {
|
||||
public:
|
||||
virtual ~TableStatsCollector() { }
|
||||
// Add() will be called when a new key/value pair is inserted into the table.
|
||||
// @params key the original key that is inserted into the table.
|
||||
// @params value the original value that is inserted into the table.
|
||||
virtual Status Add(const Slice& key, const Slice& value) = 0;
|
||||
|
||||
// Finish() will be called when a table has already been built and is ready
|
||||
// for writing the stats block.
|
||||
// @params stats User will add their collected statistics to `stats`.
|
||||
virtual Status Finish(TableStats::UserCollectedStats* stats) = 0;
|
||||
|
||||
// The name of the stats collector can be used for debugging purpose.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -14,7 +16,9 @@ typedef std::vector<std::unique_ptr<LogFile>> VectorLogPtr;
|
||||
enum WalFileType {
|
||||
/* Indicates that WAL file is in archive directory. WAL files are moved from
|
||||
* the main db directory to archive directory once they are not live and stay
|
||||
* there for a duration of WAL_ttl_seconds which can be set in Options
|
||||
* there until cleaned up. Files are cleaned depending on archive size
|
||||
* (Options::WAL_size_limit_MB) and time since last cleaning
|
||||
* (Options::WAL_ttl_seconds).
|
||||
*/
|
||||
kArchivedLogFile = 0,
|
||||
|
||||
@@ -52,7 +56,9 @@ struct BatchResult {
|
||||
std::unique_ptr<WriteBatch> writeBatchPtr;
|
||||
};
|
||||
|
||||
// A TransactionLogIterator is used to iterate over the Transaction's in a db.
|
||||
// A TransactionLogIterator is used to iterate over the transactions in a db.
|
||||
// One run of the iterator is continuous, i.e. the iterator will stop at the
|
||||
// beginning of any gap in sequences
|
||||
class TransactionLogIterator {
|
||||
public:
|
||||
TransactionLogIterator() {}
|
||||
@@ -72,7 +78,7 @@ class TransactionLogIterator {
|
||||
virtual Status status() = 0;
|
||||
|
||||
// If valid return's the current write_batch and the sequence number of the
|
||||
// latest transaction contained in the batch.
|
||||
// earliest transaction contained in the batch.
|
||||
// ONLY use if Valid() is true and status() is OK.
|
||||
virtual BatchResult GetBatch() = 0;
|
||||
};
|
||||
|
||||
@@ -136,7 +136,7 @@ class StackableDB : public DB {
|
||||
return sdb_->GetLiveFiles(vec, mfs, flush_memtable);
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() override {
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override {
|
||||
return sdb_->GetLatestSequenceNumber();
|
||||
}
|
||||
|
||||
@@ -144,8 +144,8 @@ class StackableDB : public DB {
|
||||
return sdb_->GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
virtual Status DeleteWalFiles(const VectorLogPtr& files) override {
|
||||
return sdb_->DeleteWalFiles(files);
|
||||
virtual Status DeleteFile(std::string name) override {
|
||||
return sdb_->DeleteFile(name);
|
||||
}
|
||||
|
||||
virtual Status GetUpdatesSince(SequenceNumber seq_number,
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ Block::Block(const BlockContents& contents)
|
||||
: data_(contents.data.data()),
|
||||
size_(contents.data.size()),
|
||||
owned_(contents.heap_allocated),
|
||||
cachable_(contents.cachable) {
|
||||
cachable_(contents.cachable),
|
||||
compression_type_(contents.compression_type) {
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
size_ = 0; // Error marker
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -26,7 +27,9 @@ class Block {
|
||||
|
||||
size_t size() const { return size_; }
|
||||
bool isCachable() const { return cachable_; }
|
||||
CompressionType compressionType() const { return compression_type_; }
|
||||
Iterator* NewIterator(const Comparator* comparator);
|
||||
const char* data() { return data_; }
|
||||
|
||||
private:
|
||||
uint32_t NumRestarts() const;
|
||||
@@ -36,6 +39,7 @@ class Block {
|
||||
uint32_t restart_offset_; // Offset in data_ of restart array
|
||||
bool owned_; // Block owns data_[]
|
||||
bool cachable_;
|
||||
CompressionType compression_type_;
|
||||
|
||||
// No copying allowed
|
||||
Block(const Block&);
|
||||
|
||||
@@ -7,19 +7,22 @@
|
||||
// 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 "rocksdb/table_builder.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -57,9 +60,21 @@ static bool GoodCompressionRatio(size_t compressed_size, size_t raw_size) {
|
||||
return compressed_size < raw_size - (raw_size / 8u);
|
||||
}
|
||||
|
||||
// Were we encounter any error occurs during user-defined statistics collection,
|
||||
// we'll write the warning message to info log.
|
||||
void LogStatsCollectionError(
|
||||
Logger* info_log, const std::string& method, const std::string& name) {
|
||||
assert(method == "Add" || method == "Finish");
|
||||
|
||||
std::string msg =
|
||||
"[Warning] encountered error when calling TableStatsCollector::" +
|
||||
method + "() with collector name: " + name;
|
||||
Log(info_log, msg.c_str());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
struct TableBuilder::Rep {
|
||||
struct BlockBasedTableBuilder::Rep {
|
||||
Options options;
|
||||
Options index_block_options;
|
||||
WritableFile* file;
|
||||
@@ -68,8 +83,7 @@ struct TableBuilder::Rep {
|
||||
BlockBuilder data_block;
|
||||
BlockBuilder index_block;
|
||||
std::string last_key;
|
||||
// Whether enable compression in this table.
|
||||
bool enable_compression;
|
||||
CompressionType compression_type;
|
||||
|
||||
uint64_t num_entries = 0;
|
||||
uint64_t num_data_blocks = 0;
|
||||
@@ -79,6 +93,8 @@ struct TableBuilder::Rep {
|
||||
|
||||
bool closed = false; // Either Finish() or Abandon() has been called.
|
||||
FilterBlockBuilder* filter_block;
|
||||
char compressed_cache_key_prefix[BlockBasedTable::kMaxCacheKeyPrefixSize];
|
||||
size_t compressed_cache_key_prefix_size;
|
||||
|
||||
// We do not emit the index entry for a block until we have seen the
|
||||
// first key for the next data block. This allows us to use shorter
|
||||
@@ -94,51 +110,40 @@ struct TableBuilder::Rep {
|
||||
|
||||
std::string compressed_output;
|
||||
|
||||
Rep(const Options& opt, WritableFile* f, bool enable_compression)
|
||||
Rep(const Options& opt, WritableFile* f, CompressionType compression_type)
|
||||
: options(opt),
|
||||
index_block_options(opt),
|
||||
file(f),
|
||||
data_block(&options),
|
||||
index_block(&index_block_options),
|
||||
enable_compression(enable_compression),
|
||||
index_block(1, index_block_options.comparator),
|
||||
compression_type(compression_type),
|
||||
filter_block(opt.filter_policy == nullptr ? nullptr
|
||||
: new FilterBlockBuilder(opt)),
|
||||
pending_index_entry(false) {
|
||||
index_block_options.block_restart_interval = 1;
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::TableBuilder(const Options& options, WritableFile* file,
|
||||
int level, const bool enable_compression)
|
||||
: rep_(new Rep(options, file, enable_compression)), level_(level) {
|
||||
BlockBasedTableBuilder::BlockBasedTableBuilder(const Options& options,
|
||||
WritableFile* file,
|
||||
CompressionType compression_type)
|
||||
: rep_(new Rep(options, file, compression_type)) {
|
||||
if (rep_->filter_block != nullptr) {
|
||||
rep_->filter_block->StartBlock(0);
|
||||
}
|
||||
if (options.block_cache_compressed.get() != nullptr) {
|
||||
BlockBasedTable::GenerateCachePrefix(options.block_cache_compressed, file,
|
||||
&rep_->compressed_cache_key_prefix[0],
|
||||
&rep_->compressed_cache_key_prefix_size);
|
||||
}
|
||||
}
|
||||
|
||||
TableBuilder::~TableBuilder() {
|
||||
BlockBasedTableBuilder::~BlockBasedTableBuilder() {
|
||||
assert(rep_->closed); // Catch errors where caller forgot to call Finish()
|
||||
delete rep_->filter_block;
|
||||
delete rep_;
|
||||
}
|
||||
|
||||
Status TableBuilder::ChangeOptions(const Options& options) {
|
||||
// Note: if more fields are added to Options, update
|
||||
// this function to catch changes that should not be allowed to
|
||||
// change in the middle of building a Table.
|
||||
if (options.comparator != rep_->options.comparator) {
|
||||
return Status::InvalidArgument("changing comparator while building table");
|
||||
}
|
||||
|
||||
// Note that any live BlockBuilders point to rep_->options and therefore
|
||||
// will automatically pick up the updated options.
|
||||
rep_->options = options;
|
||||
rep_->index_block_options = options;
|
||||
rep_->index_block_options.block_restart_interval = 1;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void TableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
Rep* r = rep_;
|
||||
assert(!r->closed);
|
||||
if (!ok()) return;
|
||||
@@ -180,9 +185,20 @@ void TableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
r->num_entries++;
|
||||
r->raw_key_size += key.size();
|
||||
r->raw_value_size += value.size();
|
||||
|
||||
for (auto collector : r->options.table_stats_collectors) {
|
||||
Status s = collector->Add(key, value);
|
||||
if (!s.ok()) {
|
||||
LogStatsCollectionError(
|
||||
r->options.info_log.get(),
|
||||
"Add", /* method */
|
||||
collector->Name()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TableBuilder::Flush() {
|
||||
void BlockBasedTableBuilder::Flush() {
|
||||
Rep* r = rep_;
|
||||
assert(!r->closed);
|
||||
if (!ok()) return;
|
||||
@@ -200,7 +216,8 @@ void TableBuilder::Flush() {
|
||||
++r->num_data_blocks;
|
||||
}
|
||||
|
||||
void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {
|
||||
void BlockBasedTableBuilder::WriteBlock(BlockBuilder* block,
|
||||
BlockHandle* handle) {
|
||||
// File format contains a sequence of blocks where each block has:
|
||||
// block_data: uint8[n]
|
||||
// type: uint8
|
||||
@@ -211,26 +228,7 @@ void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {
|
||||
|
||||
Slice block_contents;
|
||||
std::string* compressed = &r->compressed_output;
|
||||
CompressionType type;
|
||||
if (!r->enable_compression) {
|
||||
// disable compression
|
||||
type = kNoCompression;
|
||||
} else {
|
||||
// If the use has specified a different compression level for each level,
|
||||
// then pick the compresison for that level.
|
||||
if (!r->options.compression_per_level.empty()) {
|
||||
const int n = r->options.compression_per_level.size();
|
||||
// It is possible for level_ to be -1; in that case, we use level
|
||||
// 0's compression. This occurs mostly in backwards compatibility
|
||||
// situations when the builder doesn't know what level the file
|
||||
// belongs to. Likewise, if level_ is beyond the end of the
|
||||
// specified compression levels, use the last value.
|
||||
type = r->options.compression_per_level[std::max(0,
|
||||
std::min(level_, n))];
|
||||
} else {
|
||||
type = r->options.compression;
|
||||
}
|
||||
}
|
||||
CompressionType type = r->compression_type;
|
||||
switch (type) {
|
||||
case kNoCompression:
|
||||
block_contents = raw;
|
||||
@@ -280,9 +278,9 @@ void TableBuilder::WriteBlock(BlockBuilder* block, BlockHandle* handle) {
|
||||
block->Reset();
|
||||
}
|
||||
|
||||
void TableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
CompressionType type,
|
||||
BlockHandle* handle) {
|
||||
void BlockBasedTableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
CompressionType type,
|
||||
BlockHandle* handle) {
|
||||
Rep* r = rep_;
|
||||
StopWatch sw(r->options.env, r->options.statistics, WRITE_RAW_BLOCK_MICROS);
|
||||
handle->set_offset(r->offset);
|
||||
@@ -295,17 +293,70 @@ void TableBuilder::WriteRawBlock(const Slice& block_contents,
|
||||
crc = crc32c::Extend(crc, trailer, 1); // Extend crc to cover block type
|
||||
EncodeFixed32(trailer+1, crc32c::Mask(crc));
|
||||
r->status = r->file->Append(Slice(trailer, kBlockTrailerSize));
|
||||
if (r->status.ok()) {
|
||||
r->status = InsertBlockInCache(block_contents, type, handle);
|
||||
}
|
||||
if (r->status.ok()) {
|
||||
r->offset += block_contents.size() + kBlockTrailerSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status TableBuilder::status() const {
|
||||
Status BlockBasedTableBuilder::status() const {
|
||||
return rep_->status;
|
||||
}
|
||||
|
||||
Status TableBuilder::Finish() {
|
||||
static void DeleteCachedBlock(const Slice& key, void* value) {
|
||||
Block* block = reinterpret_cast<Block*>(value);
|
||||
delete block;
|
||||
}
|
||||
|
||||
//
|
||||
// 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) {
|
||||
Rep* r = rep_;
|
||||
Cache* block_cache_compressed = r->options.block_cache_compressed.get();
|
||||
|
||||
if (type != kNoCompression && block_cache_compressed != nullptr) {
|
||||
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
size_t size = block_contents.size();
|
||||
|
||||
char* ubuf = new char[size]; // make a new copy
|
||||
memcpy(ubuf, block_contents.data(), size);
|
||||
|
||||
BlockContents results;
|
||||
Slice sl(ubuf, size);
|
||||
results.data = sl;
|
||||
results.cachable = true; // XXX
|
||||
results.heap_allocated = true;
|
||||
results.compression_type = type;
|
||||
|
||||
Block* block = new Block(results);
|
||||
|
||||
// make cache key by appending the file offset to the cache prefix id
|
||||
char* end = EncodeVarint64(
|
||||
r->compressed_cache_key_prefix +
|
||||
r->compressed_cache_key_prefix_size,
|
||||
handle->offset());
|
||||
Slice key(r->compressed_cache_key_prefix, static_cast<size_t>
|
||||
(end - r->compressed_cache_key_prefix));
|
||||
|
||||
// Insert into compressed block cache.
|
||||
cache_handle = block_cache_compressed->Insert(key, block, block->size(),
|
||||
&DeleteCachedBlock);
|
||||
block_cache_compressed->Release(cache_handle);
|
||||
|
||||
// Invalidate OS cache.
|
||||
r->file->InvalidateCache(r->offset, size);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlockBasedTableBuilder::Finish() {
|
||||
Rep* r = rep_;
|
||||
Flush();
|
||||
assert(!r->closed);
|
||||
@@ -348,7 +399,7 @@ Status TableBuilder::Finish() {
|
||||
if (r->filter_block != nullptr) {
|
||||
// Add mapping from "<filter_block_prefix>.Name" to location
|
||||
// of filter data.
|
||||
std::string key = Table::kFilterBlockPrefix;
|
||||
std::string key = BlockBasedTable::kFilterBlockPrefix;
|
||||
key.append(r->options.filter_policy->Name());
|
||||
std::string handle_encoding;
|
||||
filter_block_handle.EncodeTo(&handle_encoding);
|
||||
@@ -367,23 +418,44 @@ Status TableBuilder::Finish() {
|
||||
BytewiseSortedMap stats;
|
||||
|
||||
// Add basic stats
|
||||
AddStats(stats, TableStatsNames::kRawKeySize, r->raw_key_size);
|
||||
AddStats(stats, TableStatsNames::kRawValueSize, r->raw_value_size);
|
||||
AddStats(stats, TableStatsNames::kDataSize, r->data_size);
|
||||
AddStats(stats, BlockBasedTableStatsNames::kRawKeySize, r->raw_key_size);
|
||||
AddStats(stats, BlockBasedTableStatsNames::kRawValueSize,
|
||||
r->raw_value_size);
|
||||
AddStats(stats, BlockBasedTableStatsNames::kDataSize, r->data_size);
|
||||
AddStats(
|
||||
stats,
|
||||
TableStatsNames::kIndexSize,
|
||||
BlockBasedTableStatsNames::kIndexSize,
|
||||
r->index_block.CurrentSizeEstimate() + kBlockTrailerSize
|
||||
);
|
||||
AddStats(stats, TableStatsNames::kNumEntries, r->num_entries);
|
||||
AddStats(stats, TableStatsNames::kNumDataBlocks, r->num_data_blocks);
|
||||
AddStats(stats, BlockBasedTableStatsNames::kNumEntries, r->num_entries);
|
||||
AddStats(stats, BlockBasedTableStatsNames::kNumDataBlocks,
|
||||
r->num_data_blocks);
|
||||
if (r->filter_block != nullptr) {
|
||||
stats.insert(std::make_pair(
|
||||
TableStatsNames::kFilterPolicy,
|
||||
BlockBasedTableStatsNames::kFilterPolicy,
|
||||
r->options.filter_policy->Name()
|
||||
));
|
||||
}
|
||||
|
||||
for (auto collector : r->options.table_stats_collectors) {
|
||||
TableStats::UserCollectedStats user_collected_stats;
|
||||
Status s =
|
||||
collector->Finish(&user_collected_stats);
|
||||
|
||||
if (!s.ok()) {
|
||||
LogStatsCollectionError(
|
||||
r->options.info_log.get(),
|
||||
"Finish", /* method */
|
||||
collector->Name()
|
||||
);
|
||||
} else {
|
||||
stats.insert(
|
||||
user_collected_stats.begin(),
|
||||
user_collected_stats.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& stat : stats) {
|
||||
stats_block.Add(stat.first, stat.second);
|
||||
}
|
||||
@@ -394,7 +466,7 @@ Status TableBuilder::Finish() {
|
||||
std::string handle_encoding;
|
||||
stats_block_handle.EncodeTo(&handle_encoding);
|
||||
meta_block_handles.insert(
|
||||
std::make_pair(Table::kStatsBlock, handle_encoding)
|
||||
std::make_pair(BlockBasedTable::kStatsBlock, handle_encoding)
|
||||
);
|
||||
} // end of stats block writing
|
||||
|
||||
@@ -425,17 +497,17 @@ Status TableBuilder::Finish() {
|
||||
return r->status;
|
||||
}
|
||||
|
||||
void TableBuilder::Abandon() {
|
||||
void BlockBasedTableBuilder::Abandon() {
|
||||
Rep* r = rep_;
|
||||
assert(!r->closed);
|
||||
r->closed = true;
|
||||
}
|
||||
|
||||
uint64_t TableBuilder::NumEntries() const {
|
||||
uint64_t BlockBasedTableBuilder::NumEntries() const {
|
||||
return rep_->num_entries;
|
||||
}
|
||||
|
||||
uint64_t TableBuilder::FileSize() const {
|
||||
uint64_t BlockBasedTableBuilder::FileSize() const {
|
||||
return rep_->offset;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
// 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.
|
||||
//
|
||||
// TableBuilder provides the interface used to build a Table
|
||||
// (an immutable and sorted map from keys to values).
|
||||
//
|
||||
// Multiple threads can invoke const methods on a TableBuilder without
|
||||
// external synchronization, but if any of the threads may call a
|
||||
// non-const method, all threads accessing the same TableBuilder must use
|
||||
// external synchronization.
|
||||
|
||||
#ifndef STORAGE_ROCKSDB_INCLUDE_TABLE_BUILDER_H_
|
||||
#define STORAGE_ROCKSDB_INCLUDE_TABLE_BUILDER_H_
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -23,35 +19,53 @@ class BlockBuilder;
|
||||
class BlockHandle;
|
||||
class WritableFile;
|
||||
|
||||
class TableBuilder {
|
||||
|
||||
class BlockBasedTableBuilder : public TableBuilder {
|
||||
public:
|
||||
// Create a builder that will store the contents of the table it is
|
||||
// building in *file. Does not close the file. It is up to the
|
||||
// caller to close the file after calling Finish(). The output file
|
||||
// will be part of level specified by 'level'. A value of -1 means
|
||||
// that the caller does not know which level the output file will reside.
|
||||
//
|
||||
// If enable_compression=true, this table will follow the compression
|
||||
// setting given in parameter options. If enable_compression=false, the
|
||||
// table will not be compressed.
|
||||
TableBuilder(const Options& options, WritableFile* file, int level=-1,
|
||||
const bool enable_compression=true);
|
||||
// caller to close the file after calling Finish().
|
||||
BlockBasedTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type);
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~TableBuilder();
|
||||
|
||||
// Change the options used by this builder. Note: only some of the
|
||||
// option fields can be changed after construction. If a field is
|
||||
// not allowed to change dynamically and its value in the structure
|
||||
// passed to the constructor is different from its value in the
|
||||
// structure passed to this method, this method will return an error
|
||||
// without changing any fields.
|
||||
Status ChangeOptions(const Options& options);
|
||||
~BlockBasedTableBuilder();
|
||||
|
||||
// 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);
|
||||
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:
|
||||
bool ok() const { return status().ok(); }
|
||||
void WriteBlock(BlockBuilder* block, BlockHandle* handle);
|
||||
void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle);
|
||||
Status InsertBlockInCache(const Slice& block_contents,
|
||||
const CompressionType type, const BlockHandle* handle);
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
|
||||
// Advanced operation: flush any buffered key/value pairs to file.
|
||||
// Can be used to ensure that two adjacent entries never live in
|
||||
@@ -59,42 +73,10 @@ class TableBuilder {
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
void Flush();
|
||||
|
||||
// Return non-ok iff some error has been detected.
|
||||
Status status() const;
|
||||
|
||||
// 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();
|
||||
|
||||
// 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();
|
||||
|
||||
// Number of calls to Add() so far.
|
||||
uint64_t NumEntries() const;
|
||||
|
||||
// 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;
|
||||
|
||||
private:
|
||||
bool ok() const { return status().ok(); }
|
||||
void WriteBlock(BlockBuilder* block, BlockHandle* handle);
|
||||
void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle);
|
||||
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
int level_;
|
||||
|
||||
// No copying allowed
|
||||
TableBuilder(const TableBuilder&);
|
||||
void operator=(const TableBuilder&);
|
||||
BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete;
|
||||
void operator=(const BlockBasedTableBuilder&) = delete;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // STORAGE_ROCKSDB_INCLUDE_TABLE_BUILDER_H_
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 "table/block_based_table_factory.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status BlockBasedTableFactory::GetTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader) const {
|
||||
return BlockBasedTable::Open(options, soptions, std::move(file), file_size,
|
||||
table_reader);
|
||||
}
|
||||
|
||||
TableBuilder* BlockBasedTableFactory::GetTableBuilder(
|
||||
const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) const {
|
||||
return new BlockBasedTableBuilder(options, file, compression_type);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct Options;
|
||||
struct EnvOptions;
|
||||
|
||||
using std::unique_ptr;
|
||||
class Status;
|
||||
class RandomAccessFile;
|
||||
class WritableFile;
|
||||
class Table;
|
||||
class TableBuilder;
|
||||
class BlockBasedTable;
|
||||
class BlockBasedTableBuilder;
|
||||
|
||||
class BlockBasedTableFactory: public TableFactory {
|
||||
public:
|
||||
~BlockBasedTableFactory() {
|
||||
}
|
||||
BlockBasedTableFactory() {
|
||||
}
|
||||
const char* Name() const override {
|
||||
return "BlockBasedTable";
|
||||
}
|
||||
Status GetTableReader(const Options& options, const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file,
|
||||
uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader) const override;
|
||||
|
||||
TableBuilder* GetTableBuilder(const Options& options, WritableFile* file,
|
||||
CompressionType compression_type) const
|
||||
override;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -7,21 +7,20 @@
|
||||
// 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 "table/table.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
|
||||
#include "db/dbformat.h"
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
#include "table/block.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
|
||||
#include "util/coding.h"
|
||||
@@ -35,7 +34,7 @@ namespace rocksdb {
|
||||
// varints.
|
||||
const size_t kMaxCacheKeyPrefixSize = kMaxVarint64Length*3+1;
|
||||
|
||||
struct Table::Rep {
|
||||
struct BlockBasedTable::Rep {
|
||||
~Rep() {
|
||||
delete filter;
|
||||
delete [] filter_data;
|
||||
@@ -51,6 +50,8 @@ struct Table::Rep {
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
char cache_key_prefix[kMaxCacheKeyPrefixSize];
|
||||
size_t cache_key_prefix_size;
|
||||
char compressed_cache_key_prefix[kMaxCacheKeyPrefixSize];
|
||||
size_t compressed_cache_key_prefix_size;
|
||||
FilterBlockReader* filter;
|
||||
const char* filter_data;
|
||||
|
||||
@@ -59,22 +60,52 @@ struct Table::Rep {
|
||||
TableStats table_stats;
|
||||
};
|
||||
|
||||
BlockBasedTable::~BlockBasedTable() {
|
||||
delete rep_;
|
||||
}
|
||||
|
||||
// Helper function to setup the cache key's prefix for the Table.
|
||||
void Table::SetupCacheKeyPrefix(Rep* rep) {
|
||||
void BlockBasedTable::SetupCacheKeyPrefix(Rep* rep) {
|
||||
assert(kMaxCacheKeyPrefixSize >= 10);
|
||||
rep->cache_key_prefix_size = 0;
|
||||
if (rep->options.block_cache) {
|
||||
rep->cache_key_prefix_size = rep->file->GetUniqueId(rep->cache_key_prefix,
|
||||
kMaxCacheKeyPrefixSize);
|
||||
rep->compressed_cache_key_prefix_size = 0;
|
||||
if (rep->options.block_cache != nullptr) {
|
||||
GenerateCachePrefix(rep->options.block_cache, rep->file.get(),
|
||||
&rep->cache_key_prefix[0],
|
||||
&rep->cache_key_prefix_size);
|
||||
}
|
||||
if (rep->options.block_cache_compressed != nullptr) {
|
||||
GenerateCachePrefix(rep->options.block_cache_compressed, rep->file.get(),
|
||||
&rep->compressed_cache_key_prefix[0],
|
||||
&rep->compressed_cache_key_prefix_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (rep->cache_key_prefix_size == 0) {
|
||||
// If the prefix wasn't generated or was too long, we create one from the
|
||||
// cache.
|
||||
char* end = EncodeVarint64(rep->cache_key_prefix,
|
||||
rep->options.block_cache->NewId());
|
||||
rep->cache_key_prefix_size =
|
||||
static_cast<size_t>(end - rep->cache_key_prefix);
|
||||
}
|
||||
void BlockBasedTable::GenerateCachePrefix(shared_ptr<Cache> cc,
|
||||
RandomAccessFile* file, char* buffer, size_t* size) {
|
||||
|
||||
// generate an id from the file
|
||||
*size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
|
||||
|
||||
// If the prefix wasn't generated or was too long,
|
||||
// create one from the cache.
|
||||
if (*size == 0) {
|
||||
char* end = EncodeVarint64(buffer, cc->NewId());
|
||||
*size = static_cast<size_t>(end - buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTable::GenerateCachePrefix(shared_ptr<Cache> cc,
|
||||
WritableFile* file, char* buffer, size_t* size) {
|
||||
|
||||
// generate an id from the file
|
||||
*size = file->GetUniqueId(buffer, kMaxCacheKeyPrefixSize);
|
||||
|
||||
// If the prefix wasn't generated or was too long,
|
||||
// create one from the cache.
|
||||
if (*size == 0) {
|
||||
char* end = EncodeVarint64(buffer, cc->NewId());
|
||||
*size = static_cast<size_t>(end - buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +121,11 @@ Status ReadBlock(RandomAccessFile* file,
|
||||
const BlockHandle& handle,
|
||||
Block** result,
|
||||
Env* env,
|
||||
bool* didIO = nullptr) {
|
||||
bool* didIO = nullptr,
|
||||
bool do_uncompress = true) {
|
||||
BlockContents contents;
|
||||
Status s = ReadBlockContents(file, options, handle, &contents, env);
|
||||
Status s = ReadBlockContents(file, options, handle, &contents,
|
||||
env, do_uncompress);
|
||||
if (s.ok()) {
|
||||
*result = new Block(contents);
|
||||
}
|
||||
@@ -105,12 +138,12 @@ Status ReadBlock(RandomAccessFile* file,
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
Status Table::Open(const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t size,
|
||||
unique_ptr<Table>* table) {
|
||||
table->reset();
|
||||
Status BlockBasedTable::Open(const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile> && file,
|
||||
uint64_t size,
|
||||
unique_ptr<TableReader>* table_reader) {
|
||||
table_reader->reset();
|
||||
if (size < Footer::kEncodedLength) {
|
||||
return Status::InvalidArgument("file is too short to be an sstable");
|
||||
}
|
||||
@@ -139,7 +172,8 @@ Status Table::Open(const Options& options,
|
||||
if (s.ok()) {
|
||||
// We've successfully read the footer and the index block: we're
|
||||
// ready to serve requests.
|
||||
Rep* rep = new Table::Rep(soptions);
|
||||
assert(index_block->compressionType() == kNoCompression);
|
||||
BlockBasedTable::Rep* rep = new BlockBasedTable::Rep(soptions);
|
||||
rep->options = options;
|
||||
rep->file = std::move(file);
|
||||
rep->metaindex_handle = footer.metaindex_handle();
|
||||
@@ -147,8 +181,8 @@ Status Table::Open(const Options& options,
|
||||
SetupCacheKeyPrefix(rep);
|
||||
rep->filter_data = nullptr;
|
||||
rep->filter = nullptr;
|
||||
table->reset(new Table(rep));
|
||||
(*table)->ReadMeta(footer);
|
||||
table_reader->reset(new BlockBasedTable(rep));
|
||||
((BlockBasedTable*) (table_reader->get()))->ReadMeta(footer);
|
||||
} else {
|
||||
if (index_block) delete index_block;
|
||||
}
|
||||
@@ -156,7 +190,7 @@ Status Table::Open(const Options& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
void Table::SetupForCompaction() {
|
||||
void BlockBasedTable::SetupForCompaction() {
|
||||
switch (rep_->options.access_hint_on_compaction_start) {
|
||||
case Options::NONE:
|
||||
break;
|
||||
@@ -175,11 +209,11 @@ void Table::SetupForCompaction() {
|
||||
compaction_optimized_ = true;
|
||||
}
|
||||
|
||||
const TableStats& Table::GetTableStats() const {
|
||||
TableStats& BlockBasedTable::GetTableStats() {
|
||||
return rep_->table_stats;
|
||||
}
|
||||
|
||||
void Table::ReadMeta(const Footer& footer) {
|
||||
void BlockBasedTable::ReadMeta(const Footer& footer) {
|
||||
// TODO(sanjay): Skip this if footer.metaindex_handle() size indicates
|
||||
// it is an empty block.
|
||||
// TODO: we never really verify check sum for meta index block
|
||||
@@ -189,6 +223,7 @@ void Table::ReadMeta(const Footer& footer) {
|
||||
// Do not propagate errors since meta info is not needed for operation
|
||||
return;
|
||||
}
|
||||
assert(meta->compressionType() == kNoCompression);
|
||||
|
||||
Iterator* iter = meta->NewIterator(BytewiseComparator());
|
||||
// read filter
|
||||
@@ -222,7 +257,7 @@ void Table::ReadMeta(const Footer& footer) {
|
||||
delete meta;
|
||||
}
|
||||
|
||||
void Table::ReadFilter(const Slice& filter_handle_value) {
|
||||
void BlockBasedTable::ReadFilter(const Slice& filter_handle_value) {
|
||||
Slice v = filter_handle_value;
|
||||
BlockHandle filter_handle;
|
||||
if (!filter_handle.DecodeFrom(&v).ok()) {
|
||||
@@ -230,11 +265,11 @@ void Table::ReadFilter(const Slice& filter_handle_value) {
|
||||
}
|
||||
|
||||
// TODO: We might want to unify with ReadBlock() if we start
|
||||
// requiring checksum verification in Table::Open.
|
||||
// requiring checksum verification in BlockBasedTable::Open.
|
||||
ReadOptions opt;
|
||||
BlockContents block;
|
||||
if (!ReadBlockContents(rep_->file.get(), opt, filter_handle, &block,
|
||||
rep_->options.env).ok()) {
|
||||
rep_->options.env, false).ok()) {
|
||||
return;
|
||||
}
|
||||
if (block.heap_allocated) {
|
||||
@@ -243,7 +278,7 @@ void Table::ReadFilter(const Slice& filter_handle_value) {
|
||||
rep_->filter = new FilterBlockReader(rep_->options, block.data);
|
||||
}
|
||||
|
||||
Status Table::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
Status BlockBasedTable::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
Slice v = handle_value;
|
||||
BlockHandle handle;
|
||||
if (!handle.DecodeFrom(&v).ok()) {
|
||||
@@ -256,7 +291,8 @@ Status Table::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
ReadOptions(),
|
||||
handle,
|
||||
&block_contents,
|
||||
rep->options.env
|
||||
rep->options.env,
|
||||
false
|
||||
);
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -271,12 +307,12 @@ Status Table::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
auto& table_stats = rep->table_stats;
|
||||
// All pre-defined stats of type uint64_t
|
||||
std::unordered_map<std::string, uint64_t*> predefined_uint64_stats = {
|
||||
{ TableStatsNames::kDataSize, &table_stats.data_size },
|
||||
{ TableStatsNames::kIndexSize, &table_stats.index_size },
|
||||
{ TableStatsNames::kRawKeySize, &table_stats.raw_key_size },
|
||||
{ TableStatsNames::kRawValueSize, &table_stats.raw_value_size },
|
||||
{ TableStatsNames::kNumDataBlocks, &table_stats.num_data_blocks },
|
||||
{ TableStatsNames::kNumEntries, &table_stats.num_entries },
|
||||
{ BlockBasedTableStatsNames::kDataSize, &table_stats.data_size },
|
||||
{ BlockBasedTableStatsNames::kIndexSize, &table_stats.index_size },
|
||||
{ BlockBasedTableStatsNames::kRawKeySize, &table_stats.raw_key_size },
|
||||
{ BlockBasedTableStatsNames::kRawValueSize, &table_stats.raw_value_size },
|
||||
{ BlockBasedTableStatsNames::kNumDataBlocks, &table_stats.num_data_blocks},
|
||||
{ BlockBasedTableStatsNames::kNumEntries, &table_stats.num_entries },
|
||||
};
|
||||
|
||||
std::string last_key;
|
||||
@@ -309,7 +345,7 @@ Status Table::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
continue;
|
||||
}
|
||||
*(pos->second) = val;
|
||||
} else if (key == TableStatsNames::kFilterPolicy) {
|
||||
} else if (key == BlockBasedTableStatsNames::kFilterPolicy) {
|
||||
table_stats.filter_policy_name = raw_val.ToString();
|
||||
} else {
|
||||
// handle user-collected
|
||||
@@ -322,10 +358,6 @@ Status Table::ReadStats(const Slice& handle_value, Rep* rep) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Table::~Table() {
|
||||
delete rep_;
|
||||
}
|
||||
|
||||
static void DeleteBlock(void* arg, void* ignored) {
|
||||
delete reinterpret_cast<Block*>(arg);
|
||||
}
|
||||
@@ -343,17 +375,21 @@ static void ReleaseBlock(void* arg, void* h) {
|
||||
|
||||
// Convert an index iterator value (i.e., an encoded BlockHandle)
|
||||
// into an iterator over the contents of the corresponding block.
|
||||
Iterator* Table::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value,
|
||||
bool* didIO,
|
||||
bool for_compaction) {
|
||||
Iterator* BlockBasedTable::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value,
|
||||
bool* didIO,
|
||||
bool for_compaction) {
|
||||
const bool no_io = (options.read_tier == kBlockCacheTier);
|
||||
Table* table = reinterpret_cast<Table*>(arg);
|
||||
BlockBasedTable* table = reinterpret_cast<BlockBasedTable*>(arg);
|
||||
Cache* block_cache = table->rep_->options.block_cache.get();
|
||||
Cache* block_cache_compressed = table->rep_->options.
|
||||
block_cache_compressed.get();
|
||||
std::shared_ptr<Statistics> statistics = table->rep_->options.statistics;
|
||||
Block* block = nullptr;
|
||||
Block* cblock = nullptr;
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
Cache::Handle* compressed_cache_handle = nullptr;
|
||||
|
||||
BlockHandle handle;
|
||||
Slice input = index_value;
|
||||
@@ -362,26 +398,88 @@ Iterator* Table::BlockReader(void* arg,
|
||||
// can add more features in the future.
|
||||
|
||||
if (s.ok()) {
|
||||
if (block_cache != nullptr) {
|
||||
if (block_cache != nullptr || block_cache_compressed != nullptr) {
|
||||
char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
const size_t cache_key_prefix_size = table->rep_->cache_key_prefix_size;
|
||||
assert(cache_key_prefix_size != 0);
|
||||
assert(cache_key_prefix_size <= kMaxCacheKeyPrefixSize);
|
||||
memcpy(cache_key, table->rep_->cache_key_prefix,
|
||||
cache_key_prefix_size);
|
||||
char* end = EncodeVarint64(cache_key + cache_key_prefix_size,
|
||||
handle.offset());
|
||||
Slice key(cache_key, static_cast<size_t>(end-cache_key));
|
||||
cache_handle = block_cache->Lookup(key);
|
||||
if (cache_handle != nullptr) {
|
||||
block = reinterpret_cast<Block*>(block_cache->Value(cache_handle));
|
||||
char compressed_cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
char* end = cache_key;
|
||||
|
||||
// create key for block cache
|
||||
if (block_cache != nullptr) {
|
||||
assert(table->rep_->cache_key_prefix_size != 0);
|
||||
assert(table->rep_->cache_key_prefix_size <= kMaxCacheKeyPrefixSize);
|
||||
memcpy(cache_key, table->rep_->cache_key_prefix,
|
||||
table->rep_->cache_key_prefix_size);
|
||||
end = EncodeVarint64(cache_key + table->rep_->cache_key_prefix_size,
|
||||
handle.offset());
|
||||
}
|
||||
Slice key(cache_key, static_cast<size_t>(end - cache_key));
|
||||
|
||||
// create key for compressed block cache
|
||||
end = compressed_cache_key;
|
||||
if (block_cache_compressed != nullptr) {
|
||||
assert(table->rep_->compressed_cache_key_prefix_size != 0);
|
||||
assert(table->rep_->compressed_cache_key_prefix_size <=
|
||||
kMaxCacheKeyPrefixSize);
|
||||
memcpy(compressed_cache_key, table->rep_->compressed_cache_key_prefix,
|
||||
table->rep_->compressed_cache_key_prefix_size);
|
||||
end = EncodeVarint64(compressed_cache_key +
|
||||
table->rep_->compressed_cache_key_prefix_size,
|
||||
handle.offset());
|
||||
}
|
||||
Slice ckey(compressed_cache_key, static_cast<size_t>
|
||||
(end - compressed_cache_key));
|
||||
|
||||
// Lookup uncompressed cache first
|
||||
if (block_cache != nullptr) {
|
||||
cache_handle = block_cache->Lookup(key);
|
||||
if (cache_handle != nullptr) {
|
||||
block = reinterpret_cast<Block*>(block_cache->Value(cache_handle));
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in uncompressed cache, lookup compressed cache
|
||||
if (block == nullptr && block_cache_compressed != nullptr) {
|
||||
compressed_cache_handle = block_cache_compressed->Lookup(ckey);
|
||||
|
||||
// if we found in the compressed cache, then uncompress and
|
||||
// insert into uncompressed cache
|
||||
if (compressed_cache_handle != nullptr) {
|
||||
// found compressed block
|
||||
cblock = reinterpret_cast<Block*>(block_cache_compressed->
|
||||
Value(compressed_cache_handle));
|
||||
assert(cblock->compressionType() != kNoCompression);
|
||||
|
||||
// Retrieve the uncompressed contents into a new buffer
|
||||
BlockContents contents;
|
||||
s = UncompressBlockContents(cblock->data(), cblock->size(),
|
||||
&contents);
|
||||
|
||||
// Insert uncompressed block into block cache
|
||||
if (s.ok()) {
|
||||
block = new Block(contents); // uncompressed block
|
||||
assert(block->compressionType() == kNoCompression);
|
||||
if (block_cache != nullptr && block->isCachable() &&
|
||||
options.fill_cache) {
|
||||
cache_handle = block_cache->Insert(key, block, block->size(),
|
||||
&DeleteCachedBlock);
|
||||
assert(reinterpret_cast<Block*>(block_cache->Value(cache_handle))
|
||||
== block);
|
||||
}
|
||||
}
|
||||
// Release hold on compressed cache entry
|
||||
block_cache_compressed->Release(compressed_cache_handle);
|
||||
RecordTick(statistics, BLOCK_CACHE_COMPRESSED_HIT);
|
||||
}
|
||||
}
|
||||
|
||||
if (block != nullptr) {
|
||||
BumpPerfCount(&perf_context.block_cache_hit_count);
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
} else if (no_io) {
|
||||
// Did not find in block_cache and can't do IO
|
||||
return NewErrorIterator(Status::Incomplete("no blocking io"));
|
||||
} else {
|
||||
|
||||
Histograms histogram = for_compaction ?
|
||||
READ_BLOCK_COMPACTION_MICROS : READ_BLOCK_GET_MICROS;
|
||||
{ // block for stop watch
|
||||
@@ -390,19 +488,54 @@ Iterator* Table::BlockReader(void* arg,
|
||||
table->rep_->file.get(),
|
||||
options,
|
||||
handle,
|
||||
&block,
|
||||
&cblock,
|
||||
table->rep_->options.env,
|
||||
didIO
|
||||
didIO,
|
||||
block_cache_compressed == nullptr
|
||||
);
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (block->isCachable() && options.fill_cache) {
|
||||
cache_handle = block_cache->Insert(
|
||||
key, block, block->size(), &DeleteCachedBlock);
|
||||
assert(cblock->compressionType() == kNoCompression ||
|
||||
block_cache_compressed != nullptr);
|
||||
|
||||
// Retrieve the uncompressed contents into a new buffer
|
||||
BlockContents contents;
|
||||
if (cblock->compressionType() != kNoCompression) {
|
||||
s = UncompressBlockContents(cblock->data(), cblock->size(),
|
||||
&contents);
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (cblock->compressionType() != kNoCompression) {
|
||||
block = new Block(contents); // uncompressed block
|
||||
} else {
|
||||
block = cblock;
|
||||
cblock = nullptr;
|
||||
}
|
||||
if (block->isCachable() && options.fill_cache) {
|
||||
// Insert compressed block into compressed block cache.
|
||||
// Release the hold on the compressed cache entry immediately.
|
||||
if (block_cache_compressed != nullptr && cblock != nullptr) {
|
||||
compressed_cache_handle = block_cache_compressed->Insert(
|
||||
ckey, cblock, cblock->size(), &DeleteCachedBlock);
|
||||
block_cache_compressed->Release(compressed_cache_handle);
|
||||
RecordTick(statistics, BLOCK_CACHE_COMPRESSED_MISS);
|
||||
cblock = nullptr;
|
||||
}
|
||||
// insert into uncompressed block cache
|
||||
assert((block->compressionType() == kNoCompression));
|
||||
if (block_cache != nullptr) {
|
||||
cache_handle = block_cache->Insert(
|
||||
key, block, block->size(), &DeleteCachedBlock);
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
assert(reinterpret_cast<Block*>(block_cache->Value(
|
||||
cache_handle))== block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
if (cblock != nullptr) {
|
||||
delete cblock;
|
||||
}
|
||||
}
|
||||
} else if (no_io) {
|
||||
// Could not read from block_cache and can't do IO
|
||||
@@ -416,10 +549,10 @@ Iterator* Table::BlockReader(void* arg,
|
||||
Iterator* iter;
|
||||
if (block != nullptr) {
|
||||
iter = block->NewIterator(table->rep_->options.comparator);
|
||||
if (cache_handle == nullptr) {
|
||||
iter->RegisterCleanup(&DeleteBlock, block, nullptr);
|
||||
} else {
|
||||
if (cache_handle != nullptr) {
|
||||
iter->RegisterCleanup(&ReleaseBlock, block_cache, cache_handle);
|
||||
} else {
|
||||
iter->RegisterCleanup(&DeleteBlock, block, nullptr);
|
||||
}
|
||||
} else {
|
||||
iter = NewErrorIterator(s);
|
||||
@@ -427,11 +560,11 @@ Iterator* Table::BlockReader(void* arg,
|
||||
return iter;
|
||||
}
|
||||
|
||||
Iterator* Table::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
const Slice& index_value,
|
||||
bool for_compaction) {
|
||||
Iterator* BlockBasedTable::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
const Slice& index_value,
|
||||
bool for_compaction) {
|
||||
return BlockReader(arg, options, index_value, nullptr, for_compaction);
|
||||
}
|
||||
|
||||
@@ -448,7 +581,7 @@ Iterator* Table::BlockReader(void* arg,
|
||||
// in memory. When blooms may need to be paged in, we should refactor so that
|
||||
// this is only ever called lazily. In particular, this shouldn't be called
|
||||
// while the DB lock is held like it is now.
|
||||
bool Table::PrefixMayMatch(const Slice& internal_prefix) const {
|
||||
bool BlockBasedTable::PrefixMayMatch(const Slice& internal_prefix) {
|
||||
FilterBlockReader* filter = rep_->filter;
|
||||
bool may_match = true;
|
||||
Status s;
|
||||
@@ -464,7 +597,7 @@ bool Table::PrefixMayMatch(const Slice& internal_prefix) const {
|
||||
// we're past end of file
|
||||
may_match = false;
|
||||
} else if (ExtractUserKey(iiter->key()).starts_with(
|
||||
ExtractUserKey(internal_prefix))) {
|
||||
ExtractUserKey(internal_prefix))) {
|
||||
// we need to check for this subtle case because our only
|
||||
// guarantee is that "the key is a string >= last key in that data
|
||||
// block" according to the doc/table_format.txt spec.
|
||||
@@ -497,7 +630,7 @@ bool Table::PrefixMayMatch(const Slice& internal_prefix) const {
|
||||
return may_match;
|
||||
}
|
||||
|
||||
Iterator* Table::NewIterator(const ReadOptions& options) const {
|
||||
Iterator* BlockBasedTable::NewIterator(const ReadOptions& options) {
|
||||
if (options.prefix) {
|
||||
InternalKey internal_prefix(*options.prefix, 0, kTypeValue);
|
||||
if (!PrefixMayMatch(internal_prefix.Encode())) {
|
||||
@@ -509,24 +642,27 @@ Iterator* Table::NewIterator(const ReadOptions& options) const {
|
||||
|
||||
return NewTwoLevelIterator(
|
||||
rep_->index_block->NewIterator(rep_->options.comparator),
|
||||
&Table::BlockReader, const_cast<Table*>(this), options, rep_->soptions);
|
||||
&BlockBasedTable::BlockReader, const_cast<BlockBasedTable*>(this),
|
||||
options, rep_->soptions);
|
||||
}
|
||||
|
||||
Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
void* arg,
|
||||
bool (*saver)(void*, const Slice&, const Slice&,
|
||||
bool),
|
||||
void (*mark_key_may_exist)(void*)) {
|
||||
Status BlockBasedTable::Get(
|
||||
const ReadOptions& readOptions,
|
||||
const Slice& key,
|
||||
void* handle_context,
|
||||
bool (*result_handler)(void* handle_context, const Slice& k,
|
||||
const Slice& v, bool didIO),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context)) {
|
||||
Status s;
|
||||
Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
|
||||
bool done = false;
|
||||
for (iiter->Seek(k); iiter->Valid() && !done; iiter->Next()) {
|
||||
for (iiter->Seek(key); iiter->Valid() && !done; iiter->Next()) {
|
||||
Slice handle_value = iiter->value();
|
||||
FilterBlockReader* filter = rep_->filter;
|
||||
BlockHandle handle;
|
||||
if (filter != nullptr &&
|
||||
handle.DecodeFrom(&handle_value).ok() &&
|
||||
!filter->KeyMayMatch(handle.offset(), k)) {
|
||||
!filter->KeyMayMatch(handle.offset(), key)) {
|
||||
// Not found
|
||||
// TODO: think about interaction with Merge. If a user key cannot
|
||||
// cross one data block, we should be fine.
|
||||
@@ -535,19 +671,20 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
} else {
|
||||
bool didIO = false;
|
||||
std::unique_ptr<Iterator> block_iter(
|
||||
BlockReader(this, options, iiter->value(), &didIO));
|
||||
BlockReader(this, readOptions, iiter->value(), &didIO));
|
||||
|
||||
if (options.read_tier && block_iter->status().IsIncomplete()) {
|
||||
if (readOptions.read_tier && block_iter->status().IsIncomplete()) {
|
||||
// couldn't get block from block_cache
|
||||
// Update Saver.state to Found because we are only looking for whether
|
||||
// we can guarantee the key is not there when "no_io" is set
|
||||
(*mark_key_may_exist)(arg);
|
||||
(*mark_key_may_exist_handler)(handle_context);
|
||||
break;
|
||||
}
|
||||
|
||||
// Call the *saver function on each entry/block until it returns false
|
||||
for (block_iter->Seek(k); block_iter->Valid(); block_iter->Next()) {
|
||||
if (!(*saver)(arg, block_iter->key(), block_iter->value(), didIO)) {
|
||||
for (block_iter->Seek(key); block_iter->Valid(); block_iter->Next()) {
|
||||
if (!(*result_handler)(handle_context, block_iter->key(),
|
||||
block_iter->value(), didIO)) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
@@ -566,16 +703,17 @@ bool SaveDidIO(void* arg, const Slice& key, const Slice& value, bool didIO) {
|
||||
*reinterpret_cast<bool*>(arg) = didIO;
|
||||
return false;
|
||||
}
|
||||
bool Table::TEST_KeyInCache(const ReadOptions& options, const Slice& key) {
|
||||
// We use InternalGet() as it has logic that checks whether we read the
|
||||
bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
|
||||
const Slice& key) {
|
||||
// We use Get() as it has logic that checks whether we read the
|
||||
// block from the disk or not.
|
||||
bool didIO = false;
|
||||
Status s = InternalGet(options, key, &didIO, SaveDidIO);
|
||||
Status s = Get(options, key, &didIO, SaveDidIO);
|
||||
assert(s.ok());
|
||||
return !didIO;
|
||||
}
|
||||
|
||||
uint64_t Table::ApproximateOffsetOf(const Slice& key) const {
|
||||
uint64_t BlockBasedTable::ApproximateOffsetOf(const Slice& key) {
|
||||
Iterator* index_iter =
|
||||
rep_->index_block->NewIterator(rep_->options.comparator);
|
||||
index_iter->Seek(key);
|
||||
@@ -602,15 +740,20 @@ uint64_t Table::ApproximateOffsetOf(const Slice& key) const {
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::string Table::kFilterBlockPrefix = "filter.";
|
||||
const std::string Table::kStatsBlock = "rocksdb.stats";
|
||||
const std::string BlockBasedTable::kFilterBlockPrefix = "filter.";
|
||||
const std::string BlockBasedTable::kStatsBlock = "rocksdb.stats";
|
||||
|
||||
const std::string TableStatsNames::kDataSize = "rocksdb.data.size";
|
||||
const std::string TableStatsNames::kIndexSize = "rocksdb.index.size";
|
||||
const std::string TableStatsNames::kRawKeySize = "rocksdb.raw.key.size";
|
||||
const std::string TableStatsNames::kRawValueSize = "rocksdb.raw.value.size";
|
||||
const std::string TableStatsNames::kNumDataBlocks = "rocksdb.num.data.blocks";
|
||||
const std::string TableStatsNames::kNumEntries = "rocksdb.num.entries";
|
||||
const std::string TableStatsNames::kFilterPolicy = "rocksdb.filter.policy";
|
||||
const std::string BlockBasedTableStatsNames::kDataSize = "rocksdb.data.size";
|
||||
const std::string BlockBasedTableStatsNames::kIndexSize = "rocksdb.index.size";
|
||||
const std::string BlockBasedTableStatsNames::kRawKeySize =
|
||||
"rocksdb.raw.key.size";
|
||||
const std::string BlockBasedTableStatsNames::kRawValueSize =
|
||||
"rocksdb.raw.value.size";
|
||||
const std::string BlockBasedTableStatsNames::kNumDataBlocks =
|
||||
"rocksdb.num.data.blocks";
|
||||
const std::string BlockBasedTableStatsNames::kNumEntries =
|
||||
"rocksdb.num.entries";
|
||||
const std::string BlockBasedTableStatsNames::kFilterPolicy =
|
||||
"rocksdb.filter.policy";
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -10,9 +10,12 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/table_stats.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -23,13 +26,14 @@ struct Options;
|
||||
class RandomAccessFile;
|
||||
struct ReadOptions;
|
||||
class TableCache;
|
||||
class TableReader;
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
// A Table is a sorted map from strings to strings. Tables are
|
||||
// immutable and persistent. A Table may be safely accessed from
|
||||
// multiple threads without external synchronization.
|
||||
class Table {
|
||||
class BlockBasedTable : public TableReader {
|
||||
public:
|
||||
static const std::string kFilterBlockPrefix;
|
||||
static const std::string kStatsBlock;
|
||||
@@ -38,28 +42,33 @@ class Table {
|
||||
// of "file", and read the metadata entries necessary to allow
|
||||
// retrieving data from the table.
|
||||
//
|
||||
// If successful, returns ok and sets "*table" to the newly opened
|
||||
// table. The client should delete "*table" when no longer needed.
|
||||
// If there was an error while initializing the table, sets "*table"
|
||||
// to nullptr and returns a non-ok status. Does not take ownership of
|
||||
// "*source", but the client must ensure that "source" remains live
|
||||
// for the duration of the returned table's lifetime.
|
||||
// If successful, returns ok and sets "*table_reader" to the newly opened
|
||||
// table. The client should delete "*table_reader" when no longer needed.
|
||||
// If there was an error while initializing the table, sets "*table_reader"
|
||||
// to nullptr and returns a non-ok status.
|
||||
//
|
||||
// *file must remain live while this Table is in use.
|
||||
static Status Open(const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size,
|
||||
unique_ptr<Table>* table);
|
||||
unique_ptr<TableReader>* table_reader);
|
||||
|
||||
~Table();
|
||||
|
||||
bool PrefixMayMatch(const Slice& internal_prefix) const;
|
||||
bool PrefixMayMatch(const Slice& internal_prefix) override;
|
||||
|
||||
// Returns a new iterator over the table contents.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
Iterator* NewIterator(const ReadOptions&) const;
|
||||
Iterator* NewIterator(const ReadOptions&) override;
|
||||
|
||||
Status Get(
|
||||
const ReadOptions& readOptions,
|
||||
const Slice& key,
|
||||
void* handle_context,
|
||||
bool (*result_handler)(void* handle_context, const Slice& k,
|
||||
const Slice& v, bool didIO),
|
||||
void (*mark_key_may_exist_handler)(void* handle_context) = nullptr)
|
||||
override;
|
||||
|
||||
// Given a key, return an approximate byte offset in the file where
|
||||
// the data for that key begins (or would begin if the key were
|
||||
@@ -67,24 +76,25 @@ class Table {
|
||||
// bytes, and so includes effects like compression of the underlying data.
|
||||
// E.g., the approximate offset of the last key in the table will
|
||||
// be close to the file length.
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) const;
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) override;
|
||||
|
||||
// Returns true if the block for the specified key is in cache.
|
||||
// REQUIRES: key is in this table.
|
||||
bool TEST_KeyInCache(const ReadOptions& options, const Slice& key);
|
||||
bool TEST_KeyInCache(const ReadOptions& options, const Slice& key) override;
|
||||
|
||||
// Set up the table for Compaction. Might change some parameters with
|
||||
// posix_fadvise
|
||||
void SetupForCompaction();
|
||||
void SetupForCompaction() override;
|
||||
|
||||
const TableStats& GetTableStats() const;
|
||||
TableStats& GetTableStats() override;
|
||||
|
||||
~BlockBasedTable();
|
||||
|
||||
private:
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
bool compaction_optimized_;
|
||||
|
||||
explicit Table(Rep* rep) : compaction_optimized_(false) { rep_ = rep; }
|
||||
static Iterator* BlockReader(void*, const ReadOptions&,
|
||||
const EnvOptions& soptions, const Slice&,
|
||||
bool for_compaction);
|
||||
@@ -95,12 +105,7 @@ class Table {
|
||||
// after a call to Seek(key), until handle_result returns false.
|
||||
// May not make such a call if filter policy says that key is not present.
|
||||
friend class TableCache;
|
||||
Status InternalGet(
|
||||
const ReadOptions&, const Slice& key,
|
||||
void* arg,
|
||||
bool (*handle_result)(void* arg, const Slice& k, const Slice& v, bool),
|
||||
void (*mark_key_may_exist)(void*) = nullptr);
|
||||
|
||||
friend class BlockBasedTableBuilder;
|
||||
|
||||
void ReadMeta(const Footer& footer);
|
||||
void ReadFilter(const Slice& filter_handle_value);
|
||||
@@ -108,12 +113,26 @@ class Table {
|
||||
|
||||
static void SetupCacheKeyPrefix(Rep* rep);
|
||||
|
||||
explicit BlockBasedTable(Rep* rep) :
|
||||
compaction_optimized_(false) {
|
||||
rep_ = rep;
|
||||
}
|
||||
// Generate a cache key prefix from the file
|
||||
static void GenerateCachePrefix(shared_ptr<Cache> cc,
|
||||
RandomAccessFile* file, char* buffer, size_t* size);
|
||||
static void GenerateCachePrefix(shared_ptr<Cache> cc,
|
||||
WritableFile* file, char* buffer, size_t* size);
|
||||
|
||||
// The longest prefix of the cache key used to identify blocks.
|
||||
// For Posix files the unique ID is three varints.
|
||||
static const size_t kMaxCacheKeyPrefixSize = kMaxVarint64Length*3+1;
|
||||
|
||||
// No copying allowed
|
||||
Table(const Table&);
|
||||
void operator=(const Table&);
|
||||
explicit BlockBasedTable(const TableReader&) = delete;
|
||||
void operator=(const TableReader&) = delete;
|
||||
};
|
||||
|
||||
struct TableStatsNames {
|
||||
struct BlockBasedTableStatsNames {
|
||||
static const std::string kDataSize;
|
||||
static const std::string kIndexSize;
|
||||
static const std::string kRawKeySize;
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -20,7 +20,7 @@ class Comparator;
|
||||
|
||||
class BlockBuilder {
|
||||
public:
|
||||
BlockBuilder(int block_builder, const Comparator* comparator);
|
||||
BlockBuilder(int block_restart_interval, const Comparator* comparator);
|
||||
explicit BlockBuilder(const Options* options);
|
||||
|
||||
// Reset the contents as if the BlockBuilder was just constructed.
|
||||
|
||||
+1
-2
@@ -10,10 +10,9 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/table.h"
|
||||
#include "table/format.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
+37
-29
@@ -76,7 +76,8 @@ Status ReadBlockContents(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result,
|
||||
Env* env) {
|
||||
Env* env,
|
||||
bool do_uncompress) {
|
||||
result->data = Slice();
|
||||
result->cachable = false;
|
||||
result->heap_allocated = false;
|
||||
@@ -116,41 +117,55 @@ Status ReadBlockContents(RandomAccessFile* file,
|
||||
BumpPerfTime(&perf_context.block_checksum_time, &timer);
|
||||
}
|
||||
|
||||
// If the caller has requested that the block not be uncompressed
|
||||
if (!do_uncompress || data[n] == kNoCompression) {
|
||||
if (data != buf) {
|
||||
// File implementation gave us pointer to some other data.
|
||||
// Use it directly under the assumption that it will be live
|
||||
// while the file is open.
|
||||
delete[] buf;
|
||||
result->data = Slice(data, n);
|
||||
result->heap_allocated = false;
|
||||
result->cachable = false; // Do not double-cache
|
||||
} else {
|
||||
result->data = Slice(buf, n);
|
||||
result->heap_allocated = true;
|
||||
result->cachable = true;
|
||||
}
|
||||
result->compression_type = (rocksdb::CompressionType)data[n];
|
||||
s = Status::OK();
|
||||
} else {
|
||||
s = UncompressBlockContents(data, n, result);
|
||||
delete[] buf;
|
||||
}
|
||||
BumpPerfTime(&perf_context.block_decompress_time, &timer);
|
||||
return s;
|
||||
}
|
||||
|
||||
//
|
||||
// The 'data' points to the raw block contents that was read in from file.
|
||||
// This method allocates a new heap buffer and the raw block
|
||||
// contents are uncompresed into this buffer. This
|
||||
// buffer is returned via 'result' and it is upto the caller to
|
||||
// free this buffer.
|
||||
Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents* result) {
|
||||
char* ubuf = nullptr;
|
||||
int decompress_size = 0;
|
||||
assert(data[n] != kNoCompression);
|
||||
switch (data[n]) {
|
||||
case kNoCompression:
|
||||
if (data != buf) {
|
||||
// File implementation gave us pointer to some other data.
|
||||
// Use it directly under the assumption that it will be live
|
||||
// while the file is open.
|
||||
delete[] buf;
|
||||
result->data = Slice(data, n);
|
||||
result->heap_allocated = false;
|
||||
result->cachable = false; // Do not double-cache
|
||||
} else {
|
||||
result->data = Slice(buf, n);
|
||||
result->heap_allocated = true;
|
||||
result->cachable = true;
|
||||
}
|
||||
|
||||
// Ok
|
||||
break;
|
||||
case kSnappyCompression: {
|
||||
size_t ulength = 0;
|
||||
static char snappy_corrupt_msg[] =
|
||||
"Snappy not supported or corrupted Snappy compressed block contents";
|
||||
if (!port::Snappy_GetUncompressedLength(data, n, &ulength)) {
|
||||
delete[] buf;
|
||||
return Status::Corruption(snappy_corrupt_msg);
|
||||
}
|
||||
ubuf = new char[ulength];
|
||||
if (!port::Snappy_Uncompress(data, n, ubuf)) {
|
||||
delete[] buf;
|
||||
delete[] ubuf;
|
||||
return Status::Corruption(snappy_corrupt_msg);
|
||||
}
|
||||
delete[] buf;
|
||||
result->data = Slice(ubuf, ulength);
|
||||
result->heap_allocated = true;
|
||||
result->cachable = true;
|
||||
@@ -161,10 +176,8 @@ Status ReadBlockContents(RandomAccessFile* file,
|
||||
static char zlib_corrupt_msg[] =
|
||||
"Zlib not supported or corrupted Zlib compressed block contents";
|
||||
if (!ubuf) {
|
||||
delete[] buf;
|
||||
return Status::Corruption(zlib_corrupt_msg);
|
||||
}
|
||||
delete[] buf;
|
||||
result->data = Slice(ubuf, decompress_size);
|
||||
result->heap_allocated = true;
|
||||
result->cachable = true;
|
||||
@@ -174,21 +187,16 @@ Status ReadBlockContents(RandomAccessFile* file,
|
||||
static char bzip2_corrupt_msg[] =
|
||||
"Bzip2 not supported or corrupted Bzip2 compressed block contents";
|
||||
if (!ubuf) {
|
||||
delete[] buf;
|
||||
return Status::Corruption(bzip2_corrupt_msg);
|
||||
}
|
||||
delete[] buf;
|
||||
result->data = Slice(ubuf, decompress_size);
|
||||
result->heap_allocated = true;
|
||||
result->cachable = true;
|
||||
break;
|
||||
default:
|
||||
delete[] buf;
|
||||
return Status::Corruption("bad block type");
|
||||
}
|
||||
|
||||
BumpPerfTime(&perf_context.block_decompress_time, &timer);
|
||||
|
||||
result->compression_type = kNoCompression; // not compressed any more
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -12,7 +12,8 @@
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -90,6 +91,7 @@ struct BlockContents {
|
||||
Slice data; // Actual contents of data
|
||||
bool cachable; // True iff data can be cached
|
||||
bool heap_allocated; // True iff caller should delete[] data.data()
|
||||
CompressionType compression_type;
|
||||
};
|
||||
|
||||
// Read the block identified by "handle" from "file". On failure
|
||||
@@ -98,7 +100,17 @@ extern Status ReadBlockContents(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result,
|
||||
Env* env);
|
||||
Env* env,
|
||||
bool do_uncompress);
|
||||
|
||||
// The 'data' points to the raw block contents read in from file.
|
||||
// This method allocates a new heap buffer and the raw block
|
||||
// contents are uncompresed into this buffer. This buffer is
|
||||
// returned via 'result' and it is upto the caller to
|
||||
// free this buffer.
|
||||
extern Status UncompressBlockContents(const char* data,
|
||||
size_t n,
|
||||
BlockContents* result);
|
||||
|
||||
// Implementation details follow. Clients should ignore,
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "util/histogram.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
// Make a key that i determines the first 4 characters and j determines the
|
||||
// last 4 characters.
|
||||
static std::string MakeKey(int i, int j) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%04d__key___%04d ", i, j);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static bool DummySaveValue(void* arg, const Slice& ikey, const Slice& v,
|
||||
bool didIO) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A very simple benchmark that.
|
||||
// Create a table with roughly numKey1 * numKey2 keys,
|
||||
// where there are numKey1 prefixes of the key, each has numKey2 number of
|
||||
// distinguished key, differing in the suffix part.
|
||||
// If if_query_empty_keys = false, query the existing keys numKey1 * numKey2
|
||||
// times randomly.
|
||||
// If if_query_empty_keys = true, query numKey1 * numKey2 random empty keys.
|
||||
// Print out the total time.
|
||||
//
|
||||
// If for_terator=true, instead of just query one key each time, it queries
|
||||
// a range sharing the same prefix.
|
||||
void TableReaderBenchmark(Options& opts, EnvOptions& env_options,
|
||||
ReadOptions& read_options, TableFactory* tf,
|
||||
int num_keys1, int num_keys2, int num_iter,
|
||||
bool if_query_empty_keys, bool for_iterator) {
|
||||
std::string file_name = test::TmpDir()
|
||||
+ "/rocksdb_table_reader_benchmark";
|
||||
ReadOptions ro;
|
||||
unique_ptr<WritableFile> file;
|
||||
Env* env = Env::Default();
|
||||
env->NewWritableFile(file_name, &file, env_options);
|
||||
TableBuilder* tb = tf->GetTableBuilder(opts, file.get(),
|
||||
CompressionType::kNoCompression);
|
||||
|
||||
// Populate slightly more than 1M keys
|
||||
for (int i = 0; i < num_keys1; i++) {
|
||||
for (int j = 0; j < num_keys2; j++) {
|
||||
std::string key = MakeKey(i * 2, j);
|
||||
tb->Add(key, key);
|
||||
}
|
||||
}
|
||||
tb->Finish();
|
||||
file->Close();
|
||||
|
||||
unique_ptr<TableReader> table_reader;
|
||||
unique_ptr<RandomAccessFile> raf;
|
||||
Status s = env->NewRandomAccessFile(file_name, &raf, env_options);
|
||||
uint64_t file_size;
|
||||
env->GetFileSize(file_name, &file_size);
|
||||
s = tf->GetTableReader(opts, env_options, std::move(raf), file_size,
|
||||
&table_reader);
|
||||
|
||||
Random rnd(301);
|
||||
HistogramImpl hist;
|
||||
|
||||
void* arg = nullptr;
|
||||
for (int it = 0; it < num_iter; it++) {
|
||||
for (int i = 0; i < num_keys1; i++) {
|
||||
for (int j = 0; j < num_keys2; j++) {
|
||||
int r1 = rnd.Uniform(num_keys1) * 2;
|
||||
int r2 = rnd.Uniform(num_keys2);
|
||||
if (!for_iterator) {
|
||||
if (if_query_empty_keys) {
|
||||
r1++;
|
||||
r2 = num_keys2 * 2 - r2;
|
||||
}
|
||||
// Query one existing key;
|
||||
std::string key = MakeKey(r1, r2);
|
||||
uint64_t start_micros = env->NowMicros();
|
||||
s = table_reader->Get(ro, key, arg, DummySaveValue, nullptr);
|
||||
hist.Add(env->NowMicros() - start_micros);
|
||||
} else {
|
||||
int r2_len = rnd.Uniform(num_keys2) + 1;
|
||||
if (r2_len + r2 > num_keys2) {
|
||||
r2_len = num_keys2 - r2;
|
||||
}
|
||||
std::string start_key = MakeKey(r1, r2);
|
||||
std::string end_key = MakeKey(r1, r2 + r2_len);
|
||||
uint64_t total_time = 0;
|
||||
uint64_t start_micros = env->NowMicros();
|
||||
Iterator* iter = table_reader->NewIterator(read_options);
|
||||
int count = 0;
|
||||
for(iter->Seek(start_key); iter->Valid(); iter->Next()) {
|
||||
// verify key;
|
||||
total_time += env->NowMicros() - start_micros;
|
||||
assert(Slice(MakeKey(r1, r2 + count)) == iter->key());
|
||||
start_micros = env->NowMicros();
|
||||
if (++count >= r2_len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count != r2_len) {
|
||||
fprintf(
|
||||
stderr, "Iterator cannot iterate expected number of entries. "
|
||||
"Expected %d but got %d\n", r2_len, count);
|
||||
assert(false);
|
||||
}
|
||||
delete iter;
|
||||
total_time += env->NowMicros() - start_micros;
|
||||
hist.Add(total_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"==================================================="
|
||||
"====================================================\n"
|
||||
"InMemoryTableSimpleBenchmark: %20s num_key1: %5d "
|
||||
"num_key2: %5d %10s\n"
|
||||
"==================================================="
|
||||
"===================================================="
|
||||
"\nHistogram (unit: microseconds): \n%s",
|
||||
tf->Name(), num_keys1, num_keys2,
|
||||
for_iterator? "iterator" : (if_query_empty_keys ? "empty" : "non_empty"),
|
||||
hist.ToString().c_str());
|
||||
env->DeleteFile(file_name);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
DEFINE_bool(query_empty, false, "query non-existing keys instead of existing "
|
||||
"ones.");
|
||||
DEFINE_int32(num_keys1, 4096, "number of distinguish prefix of keys");
|
||||
DEFINE_int32(num_keys2, 512, "number of distinguish keys for each prefix");
|
||||
DEFINE_int32(iter, 3, "query non-existing keys instead of existing ones");
|
||||
DEFINE_bool(iterator, false, "For test iterator");
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
google::SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
|
||||
" [OPTIONS]...");
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
rocksdb::TableFactory* tf;
|
||||
rocksdb::Options options;
|
||||
rocksdb::ReadOptions ro;
|
||||
rocksdb::EnvOptions env_options;
|
||||
tf = new rocksdb::BlockBasedTableFactory();
|
||||
TableReaderBenchmark(options, env_options, ro, tf, FLAGS_num_keys1,
|
||||
FLAGS_num_keys2, FLAGS_iter, FLAGS_query_empty,
|
||||
FLAGS_iterator);
|
||||
delete tf;
|
||||
return 0;
|
||||
}
|
||||
+39
-32
@@ -17,12 +17,13 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "table/block_based_table_reader.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -237,19 +238,19 @@ class BlockConstructor: public Constructor {
|
||||
BlockConstructor();
|
||||
};
|
||||
|
||||
class TableConstructor: public Constructor {
|
||||
class BlockBasedTableConstructor: public Constructor {
|
||||
public:
|
||||
explicit TableConstructor(
|
||||
explicit BlockBasedTableConstructor(
|
||||
const Comparator* cmp)
|
||||
: Constructor(cmp) {
|
||||
}
|
||||
~TableConstructor() {
|
||||
~BlockBasedTableConstructor() {
|
||||
Reset();
|
||||
}
|
||||
virtual Status FinishImpl(const Options& options, const KVMap& data) {
|
||||
Reset();
|
||||
sink_.reset(new StringSink());
|
||||
TableBuilder builder(options, sink_.get());
|
||||
BlockBasedTableBuilder builder(options, sink_.get(), options.compression);
|
||||
|
||||
for (KVMap::const_iterator it = data.begin();
|
||||
it != data.end();
|
||||
@@ -265,32 +266,37 @@ class TableConstructor: public Constructor {
|
||||
// Open the table
|
||||
uniq_id_ = cur_uniq_id_++;
|
||||
source_.reset(new StringSource(sink_->contents(), uniq_id_));
|
||||
return Table::Open(options, soptions, std::move(source_),
|
||||
sink_->contents().size(), &table_);
|
||||
unique_ptr<TableFactory> table_factory;
|
||||
return options.table_factory->GetTableReader(options, soptions,
|
||||
std::move(source_),
|
||||
sink_->contents().size(),
|
||||
&table_reader_);
|
||||
}
|
||||
|
||||
virtual Iterator* NewIterator() const {
|
||||
return table_->NewIterator(ReadOptions());
|
||||
return table_reader_->NewIterator(ReadOptions());
|
||||
}
|
||||
|
||||
uint64_t ApproximateOffsetOf(const Slice& key) const {
|
||||
return table_->ApproximateOffsetOf(key);
|
||||
return table_reader_->ApproximateOffsetOf(key);
|
||||
}
|
||||
|
||||
virtual Status Reopen(const Options& options) {
|
||||
source_.reset(new StringSource(sink_->contents(), uniq_id_));
|
||||
return Table::Open(options, soptions, std::move(source_),
|
||||
sink_->contents().size(), &table_);
|
||||
return options.table_factory->GetTableReader(options, soptions,
|
||||
std::move(source_),
|
||||
sink_->contents().size(),
|
||||
&table_reader_);
|
||||
}
|
||||
|
||||
virtual Table* table() {
|
||||
return table_.get();
|
||||
virtual TableReader* table_reader() {
|
||||
return table_reader_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
void Reset() {
|
||||
uniq_id_ = 0;
|
||||
table_.reset();
|
||||
table_reader_.reset();
|
||||
sink_.reset();
|
||||
source_.reset();
|
||||
}
|
||||
@@ -298,14 +304,14 @@ class TableConstructor: public Constructor {
|
||||
uint64_t uniq_id_;
|
||||
unique_ptr<StringSink> sink_;
|
||||
unique_ptr<StringSource> source_;
|
||||
unique_ptr<Table> table_;
|
||||
unique_ptr<TableReader> table_reader_;
|
||||
|
||||
TableConstructor();
|
||||
BlockBasedTableConstructor();
|
||||
|
||||
static uint64_t cur_uniq_id_;
|
||||
const EnvOptions soptions;
|
||||
};
|
||||
uint64_t TableConstructor::cur_uniq_id_ = 1;
|
||||
uint64_t BlockBasedTableConstructor::cur_uniq_id_ = 1;
|
||||
|
||||
// A helper class that converts internal format keys into user keys
|
||||
class KeyConvertingIterator: public Iterator {
|
||||
@@ -533,7 +539,7 @@ class Harness {
|
||||
}
|
||||
switch (args.type) {
|
||||
case TABLE_TEST:
|
||||
constructor_ = new TableConstructor(options_.comparator);
|
||||
constructor_ = new BlockBasedTableConstructor(options_.comparator);
|
||||
break;
|
||||
case BLOCK_TEST:
|
||||
constructor_ = new BlockConstructor(options_.comparator);
|
||||
@@ -821,12 +827,13 @@ TEST(MemTableTest, Simple) {
|
||||
MemTable* memtable = new MemTable(cmp, table_factory);
|
||||
memtable->Ref();
|
||||
WriteBatch batch;
|
||||
Options options;
|
||||
WriteBatchInternal::SetSequence(&batch, 100);
|
||||
batch.Put(std::string("k1"), std::string("v1"));
|
||||
batch.Put(std::string("k2"), std::string("v2"));
|
||||
batch.Put(std::string("k3"), std::string("v3"));
|
||||
batch.Put(std::string("largekey"), std::string("vlarge"));
|
||||
ASSERT_TRUE(WriteBatchInternal::InsertInto(&batch, memtable).ok());
|
||||
ASSERT_TRUE(WriteBatchInternal::InsertInto(&batch, memtable, &options).ok());
|
||||
|
||||
Iterator* iter = memtable->NewIterator();
|
||||
iter->SeekToFirst();
|
||||
@@ -857,7 +864,7 @@ class TableTest { };
|
||||
// This test include all the basic checks except those for index size and block
|
||||
// size, which will be conducted in separated unit tests.
|
||||
TEST(TableTest, BasicTableStats) {
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
|
||||
c.Add("a1", "val1");
|
||||
c.Add("b2", "val2");
|
||||
@@ -877,7 +884,7 @@ TEST(TableTest, BasicTableStats) {
|
||||
|
||||
c.Finish(options, &keys, &kvmap);
|
||||
|
||||
auto& stats = c.table()->GetTableStats();
|
||||
auto& stats = c.table_reader()->GetTableStats();
|
||||
ASSERT_EQ(kvmap.size(), stats.num_entries);
|
||||
|
||||
auto raw_key_size = kvmap.size() * 2ul;
|
||||
@@ -901,7 +908,7 @@ TEST(TableTest, BasicTableStats) {
|
||||
}
|
||||
|
||||
TEST(TableTest, FilterPolicyNameStats) {
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
c.Add("a1", "val1");
|
||||
std::vector<std::string> keys;
|
||||
KVMap kvmap;
|
||||
@@ -912,7 +919,7 @@ TEST(TableTest, FilterPolicyNameStats) {
|
||||
options.filter_policy = filter_policy.get();
|
||||
|
||||
c.Finish(options, &keys, &kvmap);
|
||||
auto& stats = c.table()->GetTableStats();
|
||||
auto& stats = c.table_reader()->GetTableStats();
|
||||
ASSERT_EQ("rocksdb.BuiltinBloomFilter", stats.filter_policy_name);
|
||||
}
|
||||
|
||||
@@ -941,7 +948,7 @@ TEST(TableTest, IndexSizeStat) {
|
||||
// Each time we load one more key to the table. the table index block
|
||||
// size is expected to be larger than last time's.
|
||||
for (size_t i = 1; i < keys.size(); ++i) {
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
c.Add(keys[j], "val");
|
||||
}
|
||||
@@ -954,7 +961,7 @@ TEST(TableTest, IndexSizeStat) {
|
||||
|
||||
c.Finish(options, &ks, &kvmap);
|
||||
auto index_size =
|
||||
c.table()->GetTableStats().index_size;
|
||||
c.table_reader()->GetTableStats().index_size;
|
||||
ASSERT_GT(index_size, last_index_size);
|
||||
last_index_size = index_size;
|
||||
}
|
||||
@@ -962,7 +969,7 @@ TEST(TableTest, IndexSizeStat) {
|
||||
|
||||
TEST(TableTest, NumBlockStat) {
|
||||
Random rnd(test::RandomSeed());
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
Options options;
|
||||
options.compression = kNoCompression;
|
||||
options.block_restart_interval = 1;
|
||||
@@ -979,12 +986,12 @@ TEST(TableTest, NumBlockStat) {
|
||||
c.Finish(options, &ks, &kvmap);
|
||||
ASSERT_EQ(
|
||||
kvmap.size(),
|
||||
c.table()->GetTableStats().num_data_blocks
|
||||
c.table_reader()->GetTableStats().num_data_blocks
|
||||
);
|
||||
}
|
||||
|
||||
TEST(TableTest, ApproximateOffsetOfPlain) {
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
c.Add("k01", "hello");
|
||||
c.Add("k02", "hello2");
|
||||
c.Add("k03", std::string(10000, 'x'));
|
||||
@@ -1015,7 +1022,7 @@ TEST(TableTest, ApproximateOffsetOfPlain) {
|
||||
|
||||
static void Do_Compression_Test(CompressionType comp) {
|
||||
Random rnd(301);
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
std::string tmp;
|
||||
c.Add("k01", "hello");
|
||||
c.Add("k02", test::CompressibleString(&rnd, 0.25, 10000, &tmp));
|
||||
@@ -1071,7 +1078,7 @@ TEST(TableTest, BlockCacheLeak) {
|
||||
opt.block_cache = NewLRUCache(16*1024*1024); // big enough so we don't ever
|
||||
// lose cached values.
|
||||
|
||||
TableConstructor c(BytewiseComparator());
|
||||
BlockBasedTableConstructor c(BytewiseComparator());
|
||||
c.Add("k01", "hello");
|
||||
c.Add("k02", "hello2");
|
||||
c.Add("k03", std::string(10000, 'x'));
|
||||
@@ -1094,7 +1101,7 @@ TEST(TableTest, BlockCacheLeak) {
|
||||
|
||||
ASSERT_OK(c.Reopen(opt));
|
||||
for (const std::string& key: keys) {
|
||||
ASSERT_TRUE(c.table()->TEST_KeyInCache(ReadOptions(), key));
|
||||
ASSERT_TRUE(c.table_reader()->TEST_KeyInCache(ReadOptions(), key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
|
||||
#include "table/two_level_iterator.h"
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -138,7 +139,8 @@ void TwoLevelIterator::Prev() {
|
||||
|
||||
|
||||
void TwoLevelIterator::SkipEmptyDataBlocksForward() {
|
||||
while (data_iter_.iter() == nullptr || !data_iter_.Valid()) {
|
||||
while (data_iter_.iter() == nullptr || (!data_iter_.Valid() &&
|
||||
!data_iter_.status().IsIncomplete())) {
|
||||
// Move to next block
|
||||
if (!index_iter_.Valid()) {
|
||||
SetDataIterator(nullptr);
|
||||
@@ -151,7 +153,8 @@ void TwoLevelIterator::SkipEmptyDataBlocksForward() {
|
||||
}
|
||||
|
||||
void TwoLevelIterator::SkipEmptyDataBlocksBackward() {
|
||||
while (data_iter_.iter() == nullptr || !data_iter_.Valid()) {
|
||||
while (data_iter_.iter() == nullptr || (!data_iter_.Valid() &&
|
||||
!data_iter_.status().IsIncomplete())) {
|
||||
// Move to next block
|
||||
if (!index_iter_.Valid()) {
|
||||
SetDataIterator(nullptr);
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/blob_store.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
#define KB 1024LL
|
||||
#define MB 1024*1024LL
|
||||
// BlobStore does costly asserts to make sure it's running correctly, which
|
||||
// significantly impacts benchmark runtime.
|
||||
// NDEBUG will compile out those asserts.
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
using namespace rocksdb;
|
||||
using namespace std;
|
||||
|
||||
// used by all threads
|
||||
uint64_t timeout_sec;
|
||||
Env *env;
|
||||
BlobStore* bs;
|
||||
|
||||
static std::string RandomString(Random* rnd, uint64_t len) {
|
||||
std::string r;
|
||||
test::RandomString(rnd, len, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct Result {
|
||||
uint32_t writes;
|
||||
uint32_t reads;
|
||||
uint32_t deletes;
|
||||
uint64_t data_written;
|
||||
uint64_t data_read;
|
||||
|
||||
void print() {
|
||||
printf("Total writes = %u\n", writes);
|
||||
printf("Total reads = %u\n", reads);
|
||||
printf("Total deletes = %u\n", deletes);
|
||||
printf("Write throughput = %lf MB/s\n",
|
||||
(double)data_written / (1024*1024.0) / timeout_sec);
|
||||
printf("Read throughput = %lf MB/s\n",
|
||||
(double)data_read / (1024*1024.0) / timeout_sec);
|
||||
printf("Total throughput = %lf MB/s\n",
|
||||
(double)(data_read + data_written) / (1024*1024.0) / timeout_sec);
|
||||
}
|
||||
|
||||
Result() {
|
||||
writes = reads = deletes = data_read = data_written = 0;
|
||||
}
|
||||
|
||||
Result (uint32_t writes, uint32_t reads, uint32_t deletes,
|
||||
uint64_t data_written, uint64_t data_read) :
|
||||
writes(writes), reads(reads), deletes(deletes),
|
||||
data_written(data_written), data_read(data_read) {}
|
||||
|
||||
};
|
||||
|
||||
Result operator + (const Result &a, const Result &b) {
|
||||
return Result(a.writes + b.writes, a.reads + b.reads,
|
||||
a.deletes + b.deletes, a.data_written + b.data_written,
|
||||
a.data_read + b.data_read);
|
||||
}
|
||||
|
||||
struct WorkerThread {
|
||||
uint64_t data_size_from, data_size_to;
|
||||
double read_ratio;
|
||||
uint64_t working_set_size; // start deleting once you reach this
|
||||
Result result;
|
||||
atomic<bool> stopped;
|
||||
|
||||
WorkerThread(uint64_t data_size_from, uint64_t data_size_to,
|
||||
double read_ratio, uint64_t working_set_size) :
|
||||
data_size_from(data_size_from), data_size_to(data_size_to),
|
||||
read_ratio(read_ratio), working_set_size(working_set_size),
|
||||
stopped(false) {}
|
||||
|
||||
WorkerThread(const WorkerThread& wt) :
|
||||
data_size_from(wt.data_size_from), data_size_to(wt.data_size_to),
|
||||
read_ratio(wt.read_ratio), working_set_size(wt.working_set_size),
|
||||
stopped(false) {}
|
||||
};
|
||||
|
||||
static void WorkerThreadBody(void* arg) {
|
||||
WorkerThread* t = reinterpret_cast<WorkerThread*>(arg);
|
||||
Random rnd(5);
|
||||
string buf;
|
||||
vector<pair<Blob, uint64_t>> blobs;
|
||||
vector<string> random_strings;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
random_strings.push_back(RandomString(&rnd, t->data_size_to));
|
||||
}
|
||||
|
||||
uint64_t total_size = 0;
|
||||
|
||||
uint64_t start_micros = env->NowMicros();
|
||||
while (env->NowMicros() - start_micros < timeout_sec * 1000 * 1000) {
|
||||
if (blobs.size() && rand() < RAND_MAX * t->read_ratio) {
|
||||
// read
|
||||
int bi = rand() % blobs.size();
|
||||
Status s = bs->Get(blobs[bi].first, &buf);
|
||||
assert(s.ok());
|
||||
t->result.data_read += buf.size();
|
||||
t->result.reads++;
|
||||
} else {
|
||||
// write
|
||||
uint64_t size = rand() % (t->data_size_to - t->data_size_from) +
|
||||
t->data_size_from;
|
||||
total_size += size;
|
||||
string put_str = random_strings[rand() % random_strings.size()];
|
||||
blobs.push_back(make_pair(Blob(), size));
|
||||
Status s = bs->Put(Slice(put_str.data(), size), &blobs.back().first);
|
||||
assert(s.ok());
|
||||
t->result.data_written += size;
|
||||
t->result.writes++;
|
||||
}
|
||||
|
||||
while (total_size >= t->working_set_size) {
|
||||
// delete random
|
||||
int bi = rand() % blobs.size();
|
||||
total_size -= blobs[bi].second;
|
||||
bs->Delete(blobs[bi].first);
|
||||
blobs.erase(blobs.begin() + bi);
|
||||
t->result.deletes++;
|
||||
}
|
||||
}
|
||||
t->stopped.store(true);
|
||||
}
|
||||
|
||||
Result StartBenchmark(vector<WorkerThread*>& config) {
|
||||
for (auto w : config) {
|
||||
env->StartThread(WorkerThreadBody, w);
|
||||
}
|
||||
|
||||
Result result;
|
||||
|
||||
for (auto w : config) {
|
||||
while (!w->stopped.load());
|
||||
result = result + w->result;
|
||||
}
|
||||
|
||||
for (auto w : config) {
|
||||
delete w;
|
||||
}
|
||||
|
||||
delete bs;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<WorkerThread*> SetupBenchmarkBalanced() {
|
||||
string test_path;
|
||||
env->GetTestDirectory(&test_path);
|
||||
test_path.append("/blob_store");
|
||||
|
||||
// config start
|
||||
uint32_t block_size = 16*KB;
|
||||
uint32_t file_size = 1*MB;
|
||||
double read_write_ratio = 0.5;
|
||||
uint64_t data_read_from = 16*KB;
|
||||
uint64_t data_read_to = 32*KB;
|
||||
int number_of_threads = 10;
|
||||
uint64_t working_set_size = 5*MB;
|
||||
timeout_sec = 5;
|
||||
// config end
|
||||
|
||||
bs = new BlobStore(test_path, block_size, file_size / block_size, 10000, env);
|
||||
|
||||
vector <WorkerThread*> config;
|
||||
|
||||
for (int i = 0; i < number_of_threads; ++i) {
|
||||
config.push_back(new WorkerThread(data_read_from,
|
||||
data_read_to,
|
||||
read_write_ratio,
|
||||
working_set_size));
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
vector<WorkerThread*> SetupBenchmarkWriteHeavy() {
|
||||
string test_path;
|
||||
env->GetTestDirectory(&test_path);
|
||||
test_path.append("/blob_store");
|
||||
|
||||
// config start
|
||||
uint32_t block_size = 16*KB;
|
||||
uint32_t file_size = 1*MB;
|
||||
double read_write_ratio = 0.1;
|
||||
uint64_t data_read_from = 16*KB;
|
||||
uint64_t data_read_to = 32*KB;
|
||||
int number_of_threads = 10;
|
||||
uint64_t working_set_size = 5*MB;
|
||||
timeout_sec = 5;
|
||||
// config end
|
||||
|
||||
bs = new BlobStore(test_path, block_size, file_size / block_size, 10000, env);
|
||||
|
||||
vector <WorkerThread*> config;
|
||||
|
||||
for (int i = 0; i < number_of_threads; ++i) {
|
||||
config.push_back(new WorkerThread(data_read_from,
|
||||
data_read_to,
|
||||
read_write_ratio,
|
||||
working_set_size));
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
vector<WorkerThread*> SetupBenchmarkReadHeavy() {
|
||||
string test_path;
|
||||
env->GetTestDirectory(&test_path);
|
||||
test_path.append("/blob_store");
|
||||
|
||||
// config start
|
||||
uint32_t block_size = 16*KB;
|
||||
uint32_t file_size = 1*MB;
|
||||
double read_write_ratio = 0.9;
|
||||
uint64_t data_read_from = 16*KB;
|
||||
uint64_t data_read_to = 32*KB;
|
||||
int number_of_threads = 10;
|
||||
uint64_t working_set_size = 5*MB;
|
||||
timeout_sec = 5;
|
||||
// config end
|
||||
|
||||
bs = new BlobStore(test_path, block_size, file_size / block_size, 10000, env);
|
||||
|
||||
vector <WorkerThread*> config;
|
||||
|
||||
for (int i = 0; i < number_of_threads; ++i) {
|
||||
config.push_back(new WorkerThread(data_read_from,
|
||||
data_read_to,
|
||||
read_write_ratio,
|
||||
working_set_size));
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
srand(33);
|
||||
env = Env::Default();
|
||||
|
||||
{
|
||||
printf("--- Balanced read/write benchmark ---\n");
|
||||
vector <WorkerThread*> config = SetupBenchmarkBalanced();
|
||||
Result r = StartBenchmark(config);
|
||||
r.print();
|
||||
}
|
||||
{
|
||||
printf("--- Write heavy benchmark ---\n");
|
||||
vector <WorkerThread*> config = SetupBenchmarkWriteHeavy();
|
||||
Result r = StartBenchmark(config);
|
||||
r.print();
|
||||
}
|
||||
{
|
||||
printf("--- Read heavy benchmark ---\n");
|
||||
vector <WorkerThread*> config = SetupBenchmarkReadHeavy();
|
||||
Result r = StartBenchmark(config);
|
||||
r.print();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+14
-17
@@ -5,6 +5,8 @@
|
||||
//
|
||||
#include <cstdio>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/types.h"
|
||||
@@ -76,23 +78,17 @@ static void ReplicationThreadBody(void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_uint64(num_inserts, 1000, "the num of inserts the first thread should"
|
||||
" perform.");
|
||||
DEFINE_uint64(wal_ttl_seconds, 1000, "the wal ttl for the run(in seconds)");
|
||||
DEFINE_uint64(wal_size_limit_MB, 10, "the wal size limit for the run"
|
||||
"(in MB)");
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
|
||||
uint64_t FLAGS_num_inserts = 1000;
|
||||
uint64_t FLAGS_WAL_ttl_seconds = 1000;
|
||||
char junk;
|
||||
long l;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (sscanf(argv[i], "--num_inserts=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_num_inserts = l;
|
||||
} else if (sscanf(argv[i], "--wal_ttl=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_WAL_ttl_seconds = l;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid Flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
google::SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
|
||||
" --num_inserts=<num_inserts> --wal_ttl_seconds=<WAL_ttl_seconds>" +
|
||||
" --wal_size_limit_MB=<WAL_size_limit_MB>");
|
||||
google::ParseCommandLineFlags(&argc, const_cast<char***>(&argv), true);
|
||||
|
||||
Env* env = Env::Default();
|
||||
std::string default_db_path;
|
||||
@@ -100,7 +96,8 @@ int main(int argc, const char** argv) {
|
||||
default_db_path += "db_repl_stress";
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.WAL_ttl_seconds = FLAGS_WAL_ttl_seconds;
|
||||
options.WAL_ttl_seconds = FLAGS_wal_ttl_seconds;
|
||||
options.WAL_size_limit_MB = FLAGS_wal_size_limit_MB;
|
||||
DB* db;
|
||||
DestroyDB(default_db_path, options);
|
||||
|
||||
|
||||
+263
-353
@@ -23,6 +23,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/db_statistics.h"
|
||||
@@ -45,192 +46,259 @@
|
||||
|
||||
static const long KB = 1024;
|
||||
|
||||
// Seed for PRNG
|
||||
static uint32_t FLAGS_seed = 2341234;
|
||||
|
||||
// Max number of key/values to place in database
|
||||
static long FLAGS_max_key = 1 * KB * KB * KB;
|
||||
static bool ValidateUint32Range(const char* flagname, uint64_t value) {
|
||||
if (value > std::numeric_limits<uint32_t>::max()) {
|
||||
fprintf(stderr, "Invalid value for --%s: %lu, overflow\n", flagname, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DEFINE_uint64(seed, 2341234, "Seed for PRNG");
|
||||
static const bool FLAGS_seed_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_seed, &ValidateUint32Range);
|
||||
|
||||
// If set, the test uses MultiGet(), MultiPut() and MultiDelete() which
|
||||
// read/write/delete multiple keys in a batch. In this mode, we do not verify
|
||||
// db content by comparing the content with the pre-allocated array. Instead,
|
||||
// we do partial verification inside MultiGet() by checking various values in
|
||||
// a batch. Benefit of this mode:
|
||||
// (a) No need to acquire mutexes during writes (less cache flushes
|
||||
// in multi-core leading to speed up)
|
||||
// (b) No long validation at the end (more speed up)
|
||||
// (c) Test snapshot and atomicity of batch writes
|
||||
static bool FLAGS_test_batches_snapshots = false;
|
||||
DEFINE_int64(max_key, 1 * KB * KB * KB,
|
||||
"Max number of key/values to place in database");
|
||||
|
||||
// Number of concurrent threads to run.
|
||||
static int FLAGS_threads = 32;
|
||||
DEFINE_bool(test_batches_snapshots, false,
|
||||
"If set, the test uses MultiGet(), MultiPut() and MultiDelete()"
|
||||
" which read/write/delete multiple keys in a batch. In this mode,"
|
||||
" we do not verify db content by comparing the content with the "
|
||||
"pre-allocated array. Instead, we do partial verification inside"
|
||||
" MultiGet() by checking various values in a batch. Benefit of"
|
||||
" this mode:\n"
|
||||
"\t(a) No need to acquire mutexes during writes (less cache "
|
||||
"flushes in multi-core leading to speed up)\n"
|
||||
"\t(b) No long validation at the end (more speed up)\n"
|
||||
"\t(c) Test snapshot and atomicity of batch writes");
|
||||
|
||||
// Opens the db with this ttl value if this is not -1
|
||||
// Carefully specify a large value such that verifications on deleted
|
||||
// values don't fail
|
||||
static int FLAGS_ttl = -1;
|
||||
DEFINE_int32(threads, 32, "Number of concurrent threads to run.");
|
||||
|
||||
// Size of each value will be this number times rand_int(1,3) bytes
|
||||
static int FLAGS_value_size_mult = 8;
|
||||
DEFINE_int32(ttl, -1,
|
||||
"Opens the db with this ttl value if this is not -1. "
|
||||
"Carefully specify a large value such that verifications on "
|
||||
"deleted values don't fail");
|
||||
|
||||
static bool FLAGS_verify_before_write = false;
|
||||
DEFINE_int32(value_size_mult, 8,
|
||||
"Size of value will be this number times rand_int(1,3) bytes");
|
||||
|
||||
// Print histogram of operation timings
|
||||
static bool FLAGS_histogram = false;
|
||||
DEFINE_bool(verify_before_write, false, "Verify before write");
|
||||
|
||||
// Destroys the database dir before start if this is true
|
||||
static bool FLAGS_destroy_db_initially = true;
|
||||
DEFINE_bool(histogram, false, "Print histogram of operation timings");
|
||||
|
||||
static bool FLAGS_verbose = false;
|
||||
DEFINE_bool(destroy_db_initially, true,
|
||||
"Destroys the database dir before start if this is true");
|
||||
|
||||
// Number of bytes to buffer in memtable before compacting
|
||||
// (initialized to default value by "main")
|
||||
static int FLAGS_write_buffer_size = 0;
|
||||
DEFINE_bool (verbose, false, "Verbose");
|
||||
|
||||
// The number of in-memory memtables.
|
||||
// Each memtable is of size FLAGS_write_buffer_size.
|
||||
// This is initialized to default value of 2 in "main" function.
|
||||
static int FLAGS_max_write_buffer_number = 0;
|
||||
DEFINE_int32(write_buffer_size, rocksdb::Options().write_buffer_size,
|
||||
"Number of bytes to buffer in memtable before compacting");
|
||||
|
||||
// The minimum number of write buffers that will be merged together
|
||||
// before writing to storage. This is cheap because it is an
|
||||
// in-memory merge. If this feature is not enabled, then all these
|
||||
// write buffers are flushed to L0 as separate files and this increases
|
||||
// read amplification because a get request has to check in all of these
|
||||
// files. Also, an in-memory merge may result in writing less
|
||||
// data to storage if there are duplicate records in each of these
|
||||
// individual write buffers.
|
||||
static int FLAGS_min_write_buffer_number_to_merge = 0;
|
||||
DEFINE_int32(max_write_buffer_number,
|
||||
rocksdb::Options().max_write_buffer_number,
|
||||
"The number of in-memory memtables. "
|
||||
"Each memtable is of size FLAGS_write_buffer_size.");
|
||||
|
||||
// The maximum number of concurrent background compactions
|
||||
// that can occur in parallel.
|
||||
// This is initialized to default value of 1 in "main" function.
|
||||
static int FLAGS_max_background_compactions = 0;
|
||||
DEFINE_int32(min_write_buffer_number_to_merge,
|
||||
rocksdb::Options().min_write_buffer_number_to_merge,
|
||||
"The minimum number of write buffers that will be merged together "
|
||||
"before writing to storage. This is cheap because it is an "
|
||||
"in-memory merge. If this feature is not enabled, then all these "
|
||||
"write buffers are flushed to L0 as separate files and this "
|
||||
"increases read amplification because a get request has to check "
|
||||
"in all of these files. Also, an in-memory merge may result in "
|
||||
"writing less data to storage if there are duplicate records in"
|
||||
" each of these individual write buffers.");
|
||||
|
||||
// This is initialized to default value of false
|
||||
static rocksdb::CompactionStyle FLAGS_compaction_style = rocksdb::kCompactionStyleLevel;
|
||||
DEFINE_int32(open_files, rocksdb::Options().max_open_files,
|
||||
"Maximum number of files to keep open at the same time "
|
||||
"(use default if == 0)");
|
||||
|
||||
// The ratio of file sizes that trigger compaction in universal style
|
||||
static unsigned int FLAGS_universal_size_ratio = 0;
|
||||
DEFINE_int64(compressed_cache_size, -1,
|
||||
"Number of bytes to use as a cache of compressed data."
|
||||
" Negative means use default settings.");
|
||||
|
||||
// The minimum number of files to compact in universal style compaction
|
||||
static unsigned int FLAGS_universal_min_merge_width = 0;
|
||||
DEFINE_int32(compaction_style, rocksdb::Options().compaction_style, "");
|
||||
|
||||
// The max number of files to compact in universal style compaction
|
||||
static unsigned int FLAGS_universal_max_merge_width = 0;
|
||||
DEFINE_int32(level0_file_num_compaction_trigger,
|
||||
rocksdb::Options().level0_file_num_compaction_trigger,
|
||||
"Level0 compaction start trigger");
|
||||
|
||||
// The max size amplification for universal style compaction
|
||||
static unsigned int FLAGS_universal_max_size_amplification_percent = 0;
|
||||
DEFINE_int32(level0_slowdown_writes_trigger,
|
||||
rocksdb::Options().level0_slowdown_writes_trigger,
|
||||
"Number of files in level-0 that will slow down writes");
|
||||
|
||||
// Number of bytes to use as a cache of uncompressed data.
|
||||
static long FLAGS_cache_size = 2 * KB * KB * KB;
|
||||
DEFINE_int32(level0_stop_writes_trigger,
|
||||
rocksdb::Options().level0_stop_writes_trigger,
|
||||
"Number of files in level-0 that will trigger put stop.");
|
||||
|
||||
// Number of bytes in a block.
|
||||
static int FLAGS_block_size = 4 * KB;
|
||||
DEFINE_int32(block_size, rocksdb::Options().block_size,
|
||||
"Number of bytes in a block.");
|
||||
|
||||
// Number of times database reopens
|
||||
static int FLAGS_reopen = 10;
|
||||
DEFINE_int32(max_background_compactions,
|
||||
rocksdb::Options().max_background_compactions,
|
||||
"The maximum number of concurrent background compactions "
|
||||
"that can occur in parallel.");
|
||||
|
||||
// Maximum number of files to keep open at the same time (use default if == 0)
|
||||
static int FLAGS_open_files = 0;
|
||||
DEFINE_int32(universal_size_ratio, 0, "The ratio of file sizes that trigger"
|
||||
" compaction in universal style");
|
||||
|
||||
// Bloom filter bits per key.
|
||||
// Negative means use default settings.
|
||||
static int FLAGS_bloom_bits = 10;
|
||||
DEFINE_int32(universal_min_merge_width, 0, "The minimum number of files to "
|
||||
"compact in universal style compaction");
|
||||
|
||||
// Use the db with the following name.
|
||||
static const char* FLAGS_db = nullptr;
|
||||
DEFINE_int32(universal_max_merge_width, 0, "The max number of files to compact"
|
||||
" in universal style compaction");
|
||||
|
||||
// Verify checksum for every block read from storage
|
||||
static bool FLAGS_verify_checksum = false;
|
||||
DEFINE_int32(universal_max_size_amplification_percent, 0,
|
||||
"The max size amplification for universal style compaction");
|
||||
|
||||
// Allow reads to occur via mmap-ing files
|
||||
static bool FLAGS_use_mmap_reads = rocksdb::EnvOptions().use_mmap_reads;
|
||||
DEFINE_int64(cache_size, 2 * KB * KB * KB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
|
||||
static bool ValidateInt32Positive(const char* flagname, int32_t value) {
|
||||
if (value < 0) {
|
||||
fprintf(stderr, "Invalid value for --%s: %d, must be >=0\n",
|
||||
flagname, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DEFINE_int32(reopen, 10, "Number of times database reopens");
|
||||
static const bool FLAGS_reopen_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_reopen, &ValidateInt32Positive);
|
||||
|
||||
DEFINE_int32(bloom_bits, 10, "Bloom filter bits per key. "
|
||||
"Negative means use default settings.");
|
||||
|
||||
DEFINE_string(db, "", "Use the db with the following name.");
|
||||
|
||||
DEFINE_bool(verify_checksum, false,
|
||||
"Verify checksum for every block read from storage");
|
||||
|
||||
DEFINE_bool(mmap_read, rocksdb::EnvOptions().use_mmap_reads,
|
||||
"Allow reads to occur via mmap-ing files");
|
||||
|
||||
// Database statistics
|
||||
static std::shared_ptr<rocksdb::Statistics> dbstats;
|
||||
DEFINE_bool(statistics, false, "Create database statistics");
|
||||
|
||||
// Sync all writes to disk
|
||||
static bool FLAGS_sync = false;
|
||||
DEFINE_bool(sync, false, "Sync all writes to disk");
|
||||
|
||||
// If true, do not wait until data is synced to disk.
|
||||
static bool FLAGS_disable_data_sync = false;
|
||||
DEFINE_bool(disable_data_sync, false,
|
||||
"If true, do not wait until data is synced to disk.");
|
||||
|
||||
// If true, issue fsync instead of fdatasync
|
||||
static bool FLAGS_use_fsync = false;
|
||||
DEFINE_bool(use_fsync, false, "If true, issue fsync instead of fdatasync");
|
||||
|
||||
// If non-zero, kill at various points in source code with probability 1/this
|
||||
static int FLAGS_kill_random_test = 0;
|
||||
DEFINE_int32(kill_random_test, 0,
|
||||
"If non-zero, kill at various points in source code with "
|
||||
"probability 1/this");
|
||||
static const bool FLAGS_kill_random_test_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_kill_random_test,
|
||||
&ValidateInt32Positive);
|
||||
extern int rocksdb_kill_odds;
|
||||
|
||||
// If true, do not write WAL for write.
|
||||
static bool FLAGS_disable_wal = false;
|
||||
DEFINE_bool(disable_wal, false, "If true, do not write WAL for write.");
|
||||
|
||||
// Target level-1 file size for compaction
|
||||
static int FLAGS_target_file_size_base = 64 * KB;
|
||||
DEFINE_int32(target_file_size_base, 64 * KB,
|
||||
"Target level-1 file size for compaction");
|
||||
|
||||
// A multiplier to compute targe level-N file size (N >= 2)
|
||||
static int FLAGS_target_file_size_multiplier = 1;
|
||||
DEFINE_int32(target_file_size_multiplier, 1,
|
||||
"A multiplier to compute targe level-N file size (N >= 2)");
|
||||
|
||||
// Max bytes for level-1
|
||||
static uint64_t FLAGS_max_bytes_for_level_base = 256 * KB;
|
||||
DEFINE_uint64(max_bytes_for_level_base, 256 * KB, "Max bytes for level-1");
|
||||
|
||||
// A multiplier to compute max bytes for level-N (N >= 2)
|
||||
static int FLAGS_max_bytes_for_level_multiplier = 2;
|
||||
DEFINE_int32(max_bytes_for_level_multiplier, 2,
|
||||
"A multiplier to compute max bytes for level-N (N >= 2)");
|
||||
|
||||
// Number of files in level-0 that will trigger put stop.
|
||||
static int FLAGS_level0_stop_writes_trigger = 12;
|
||||
static bool ValidateInt32Percent(const char* flagname, int32_t value) {
|
||||
if (value < 0 || value>100) {
|
||||
fprintf(stderr, "Invalid value for --%s: %d, 0<= pct <=100 \n",
|
||||
flagname, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DEFINE_int32(readpercent, 10,
|
||||
"Ratio of reads to total workload (expressed as a percentage)");
|
||||
static const bool FLAGS_readpercent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_readpercent, &ValidateInt32Percent);
|
||||
|
||||
// Number of files in level-0 that will slow down writes.
|
||||
static int FLAGS_level0_slowdown_writes_trigger = 8;
|
||||
DEFINE_int32(prefixpercent, 20,
|
||||
"Ratio of prefix iterators to total workload (expressed as a"
|
||||
" percentage)");
|
||||
static const bool FLAGS_prefixpercent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_prefixpercent, &ValidateInt32Percent);
|
||||
|
||||
// Ratio of reads to total workload (expressed as a percentage)
|
||||
static unsigned int FLAGS_readpercent = 10;
|
||||
DEFINE_int32(writepercent, 45,
|
||||
" Ratio of deletes to total workload (expressed as a percentage)");
|
||||
static const bool FLAGS_writepercent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_writepercent, &ValidateInt32Percent);
|
||||
|
||||
// Ratio of prefix iterators to total workload (expressed as a percentage)
|
||||
static unsigned int FLAGS_prefixpercent = 20;
|
||||
DEFINE_int32(delpercent, 15,
|
||||
"Ratio of deletes to total workload (expressed as a percentage)");
|
||||
static const bool FLAGS_delpercent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_delpercent, &ValidateInt32Percent);
|
||||
|
||||
// Ratio of deletes to total workload (expressed as a percentage)
|
||||
static unsigned int FLAGS_writepercent = 45;
|
||||
DEFINE_int32(iterpercent, 10, "Ratio of iterations to total workload"
|
||||
" (expressed as a percentage)");
|
||||
static const bool FLAGS_iterpercent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_iterpercent, &ValidateInt32Percent);
|
||||
|
||||
// Ratio of deletes to total workload (expressed as a percentage)
|
||||
static unsigned int FLAGS_delpercent = 15;
|
||||
DEFINE_uint64(num_iterations, 10, "Number of iterations per MultiIterate run");
|
||||
static const bool FLAGS_num_iterations_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_num_iterations, &ValidateUint32Range);
|
||||
|
||||
// Ratio of iterations to total workload (expressed as a percentage)
|
||||
static unsigned int FLAGS_iterpercent = 10;
|
||||
DEFINE_bool(disable_seek_compaction, false,
|
||||
"Option to disable compation triggered by read.");
|
||||
|
||||
// Number of iterations per MultiIterate run
|
||||
static uint32_t FLAGS_num_iterations = 10;
|
||||
DEFINE_uint64(delete_obsolete_files_period_micros, 0,
|
||||
"Option to delete obsolete files periodically"
|
||||
"0 means that obsolete files are "
|
||||
" deleted after every compaction run.");
|
||||
|
||||
// Option to disable compation triggered by read.
|
||||
static int FLAGS_disable_seek_compaction = false;
|
||||
enum rocksdb::CompressionType StringToCompressionType(const char* ctype) {
|
||||
assert(ctype);
|
||||
|
||||
// Option to delete obsolete files periodically
|
||||
// Default: 0 which means that obsolete files are
|
||||
// deleted after every compaction run.
|
||||
static uint64_t FLAGS_delete_obsolete_files_period_micros = 0;
|
||||
if (!strcasecmp(ctype, "none"))
|
||||
return rocksdb::kNoCompression;
|
||||
else if (!strcasecmp(ctype, "snappy"))
|
||||
return rocksdb::kSnappyCompression;
|
||||
else if (!strcasecmp(ctype, "zlib"))
|
||||
return rocksdb::kZlibCompression;
|
||||
else if (!strcasecmp(ctype, "bzip2"))
|
||||
return rocksdb::kBZip2Compression;
|
||||
|
||||
// Algorithm to use to compress the database
|
||||
static enum rocksdb::CompressionType FLAGS_compression_type =
|
||||
fprintf(stdout, "Cannot parse compression type '%s'\n", ctype);
|
||||
return rocksdb::kSnappyCompression; //default value
|
||||
}
|
||||
DEFINE_string(compression_type, "snappy",
|
||||
"Algorithm to use to compress the database");
|
||||
static enum rocksdb::CompressionType FLAGS_compression_type_e =
|
||||
rocksdb::kSnappyCompression;
|
||||
|
||||
DEFINE_string(hdfs, "", "Name of hdfs environment");
|
||||
// posix or hdfs environment
|
||||
static rocksdb::Env* FLAGS_env = rocksdb::Env::Default();
|
||||
|
||||
// Number of operations per thread.
|
||||
static uint32_t FLAGS_ops_per_thread = 600000;
|
||||
DEFINE_uint64(ops_per_thread, 600000, "Number of operations per thread.");
|
||||
static const bool FLAGS_ops_per_thread_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_ops_per_thread, &ValidateUint32Range);
|
||||
|
||||
// Log2 of number of keys per lock
|
||||
static uint32_t FLAGS_log2_keys_per_lock = 2; // implies 2^2 keys per lock
|
||||
DEFINE_uint64(log2_keys_per_lock, 2, "Log2 of number of keys per lock");
|
||||
static const bool FLAGS_log2_keys_per_lock_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_log2_keys_per_lock,
|
||||
&ValidateUint32Range);
|
||||
|
||||
// Percentage of times we want to purge redundant keys in memory before flushing
|
||||
static uint32_t FLAGS_purge_redundant_percent = 50;
|
||||
DEFINE_int32(purge_redundant_percent, 50,
|
||||
"Percentage of times we want to purge redundant keys in memory "
|
||||
"before flushing");
|
||||
static const bool FLAGS_purge_redundant_percent_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_purge_redundant_percent,
|
||||
&ValidateInt32Percent);
|
||||
|
||||
// On true, deletes use KeyMayExist to drop the delete if key not present
|
||||
static bool FLAGS_filter_deletes = false;
|
||||
|
||||
// Level0 compaction start trigger
|
||||
static int FLAGS_level0_file_num_compaction_trigger = 0;
|
||||
DEFINE_bool(filter_deletes, false, "On true, deletes use KeyMayExist to drop"
|
||||
" the delete if key not present");
|
||||
|
||||
enum RepFactory {
|
||||
kSkipList,
|
||||
@@ -238,14 +306,39 @@ enum RepFactory {
|
||||
kUnsorted,
|
||||
kVectorRep
|
||||
};
|
||||
enum RepFactory StringToRepFactory(const char* ctype) {
|
||||
assert(ctype);
|
||||
|
||||
if (!strcasecmp(ctype, "skip_list"))
|
||||
return kSkipList;
|
||||
else if (!strcasecmp(ctype, "prefix_hash"))
|
||||
return kPrefixHash;
|
||||
else if (!strcasecmp(ctype, "unsorted"))
|
||||
return kUnsorted;
|
||||
else if (!strcasecmp(ctype, "vector"))
|
||||
return kVectorRep;
|
||||
|
||||
fprintf(stdout, "Cannot parse memreptable %s\n", ctype);
|
||||
return kSkipList;
|
||||
}
|
||||
static enum RepFactory FLAGS_rep_factory;
|
||||
DEFINE_string(memtablerep, "skip_list", "");
|
||||
|
||||
// Control the prefix size for PrefixHashRep
|
||||
static bool FLAGS_prefix_size = 0;
|
||||
static bool ValidatePrefixSize(const char* flagname, int32_t value) {
|
||||
if (value < 0 || value>=2000000000) {
|
||||
fprintf(stderr, "Invalid value for --%s: %d. 0<= PrefixSize <=2000000000\n",
|
||||
flagname, value);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DEFINE_int32(prefix_size, 0, "Control the prefix size for PrefixHashRep");
|
||||
static const bool FLAGS_prefix_size_dummy =
|
||||
google::RegisterFlagValidator(&FLAGS_prefix_size, &ValidatePrefixSize);
|
||||
|
||||
DEFINE_bool(use_merge, false, "On true, replaces all writes with a Merge "
|
||||
"that behaves like a Put");
|
||||
|
||||
// On true, replaces all writes with a Merge that behaves like a Put
|
||||
static bool FLAGS_use_merge_put = false;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -583,6 +676,9 @@ class StressTest {
|
||||
public:
|
||||
StressTest()
|
||||
: cache_(NewLRUCache(FLAGS_cache_size)),
|
||||
compressed_cache_(FLAGS_compressed_cache_size >= 0 ?
|
||||
NewLRUCache(FLAGS_compressed_cache_size) :
|
||||
nullptr),
|
||||
filter_policy_(FLAGS_bloom_bits >= 0
|
||||
? NewBloomFilterPolicy(FLAGS_bloom_bits)
|
||||
: nullptr),
|
||||
@@ -596,7 +692,7 @@ class StressTest {
|
||||
FLAGS_env->GetChildren(FLAGS_db, &files);
|
||||
for (unsigned int i = 0; i < files.size(); i++) {
|
||||
if (Slice(files[i]).starts_with("heap-")) {
|
||||
FLAGS_env->DeleteFile(std::string(FLAGS_db) + "/" + files[i]);
|
||||
FLAGS_env->DeleteFile(FLAGS_db + "/" + files[i]);
|
||||
}
|
||||
}
|
||||
DestroyDB(FLAGS_db, Options());
|
||||
@@ -733,7 +829,7 @@ class StressTest {
|
||||
keys[i] += key.ToString();
|
||||
values[i] += value.ToString();
|
||||
value_slices[i] = values[i];
|
||||
if (FLAGS_use_merge_put) {
|
||||
if (FLAGS_use_merge) {
|
||||
batch.Merge(keys[i], value_slices[i]);
|
||||
} else {
|
||||
batch.Put(keys[i], value_slices[i]);
|
||||
@@ -921,7 +1017,7 @@ class StressTest {
|
||||
unique_ptr<Iterator> iter(db_->NewIterator(readoptionscopy));
|
||||
|
||||
iter->Seek(key);
|
||||
for (long i = 0; i < FLAGS_num_iterations && iter->Valid(); i++) {
|
||||
for (uint64_t i = 0; i < FLAGS_num_iterations && iter->Valid(); i++) {
|
||||
if (thread->rand.OneIn(2)) {
|
||||
iter->Next();
|
||||
} else {
|
||||
@@ -955,7 +1051,7 @@ class StressTest {
|
||||
const int delBound = writeBound + (int)FLAGS_delpercent;
|
||||
|
||||
thread->stats.Start();
|
||||
for (long i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
if(i != 0 && (i % (FLAGS_ops_per_thread / (FLAGS_reopen + 1))) == 0) {
|
||||
{
|
||||
thread->stats.FinishedSingleOp();
|
||||
@@ -1038,7 +1134,7 @@ class StressTest {
|
||||
true);
|
||||
}
|
||||
thread->shared->Put(rand_key, value_base);
|
||||
if (FLAGS_use_merge_put) {
|
||||
if (FLAGS_use_merge) {
|
||||
db_->Merge(write_opts, key, v);
|
||||
} else {
|
||||
db_->Put(write_opts, key, v);
|
||||
@@ -1183,7 +1279,7 @@ class StressTest {
|
||||
fprintf(stdout, "LevelDB version : %d.%d\n",
|
||||
kMajorVersion, kMinorVersion);
|
||||
fprintf(stdout, "Number of threads : %d\n", FLAGS_threads);
|
||||
fprintf(stdout, "Ops per thread : %d\n", FLAGS_ops_per_thread);
|
||||
fprintf(stdout, "Ops per thread : %lu\n", FLAGS_ops_per_thread);
|
||||
std::string ttl_state("unused");
|
||||
if (FLAGS_ttl > 0) {
|
||||
ttl_state = NumberToString(FLAGS_ttl);
|
||||
@@ -1195,7 +1291,7 @@ class StressTest {
|
||||
fprintf(stdout, "Delete percentage : %d\n", FLAGS_delpercent);
|
||||
fprintf(stdout, "Iterate percentage : %d\n", FLAGS_iterpercent);
|
||||
fprintf(stdout, "Write-buffer-size : %d\n", FLAGS_write_buffer_size);
|
||||
fprintf(stdout, "Iterations : %d\n", FLAGS_num_iterations);
|
||||
fprintf(stdout, "Iterations : %lu\n", FLAGS_num_iterations);
|
||||
fprintf(stdout, "Max key : %ld\n", FLAGS_max_key);
|
||||
fprintf(stdout, "Ratio #ops/#keys : %f\n",
|
||||
(1.0 * FLAGS_ops_per_thread * FLAGS_threads)/FLAGS_max_key);
|
||||
@@ -1210,7 +1306,7 @@ class StressTest {
|
||||
1 << FLAGS_log2_keys_per_lock);
|
||||
|
||||
const char* compression = "";
|
||||
switch (FLAGS_compression_type) {
|
||||
switch (FLAGS_compression_type_e) {
|
||||
case rocksdb::kNoCompression:
|
||||
compression = "none";
|
||||
break;
|
||||
@@ -1252,12 +1348,14 @@ class StressTest {
|
||||
assert(db_ == nullptr);
|
||||
Options options;
|
||||
options.block_cache = cache_;
|
||||
options.block_cache_compressed = compressed_cache_;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
options.max_write_buffer_number = FLAGS_max_write_buffer_number;
|
||||
options.min_write_buffer_number_to_merge =
|
||||
FLAGS_min_write_buffer_number_to_merge;
|
||||
options.max_background_compactions = FLAGS_max_background_compactions;
|
||||
options.compaction_style = FLAGS_compaction_style;
|
||||
options.compaction_style =
|
||||
static_cast<rocksdb::CompactionStyle>(FLAGS_compaction_style);
|
||||
options.block_size = FLAGS_block_size;
|
||||
options.filter_policy = filter_policy_;
|
||||
options.prefix_extractor = prefix_extractor_;
|
||||
@@ -1266,7 +1364,7 @@ class StressTest {
|
||||
options.env = FLAGS_env;
|
||||
options.disableDataSync = FLAGS_disable_data_sync;
|
||||
options.use_fsync = FLAGS_use_fsync;
|
||||
options.allow_mmap_reads = FLAGS_use_mmap_reads;
|
||||
options.allow_mmap_reads = FLAGS_mmap_read;
|
||||
rocksdb_kill_odds = FLAGS_kill_random_test;
|
||||
options.target_file_size_base = FLAGS_target_file_size_base;
|
||||
options.target_file_size_multiplier = FLAGS_target_file_size_multiplier;
|
||||
@@ -1278,7 +1376,7 @@ class StressTest {
|
||||
FLAGS_level0_slowdown_writes_trigger;
|
||||
options.level0_file_num_compaction_trigger =
|
||||
FLAGS_level0_file_num_compaction_trigger;
|
||||
options.compression = FLAGS_compression_type;
|
||||
options.compression = FLAGS_compression_type_e;
|
||||
options.create_if_missing = true;
|
||||
options.disable_seek_compaction = FLAGS_disable_seek_compaction;
|
||||
options.delete_obsolete_files_period_micros =
|
||||
@@ -1311,11 +1409,12 @@ class StressTest {
|
||||
break;
|
||||
}
|
||||
static Random purge_percent(1000); // no benefit from non-determinism here
|
||||
if (purge_percent.Uniform(100) < FLAGS_purge_redundant_percent - 1) {
|
||||
if (static_cast<int32_t>(purge_percent.Uniform(100)) <
|
||||
FLAGS_purge_redundant_percent - 1) {
|
||||
options.purge_redundant_kvs_while_flush = false;
|
||||
}
|
||||
|
||||
if (FLAGS_use_merge_put) {
|
||||
if (FLAGS_use_merge) {
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
}
|
||||
|
||||
@@ -1337,7 +1436,7 @@ class StressTest {
|
||||
FLAGS_universal_max_size_amplification_percent;
|
||||
}
|
||||
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db);
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db.c_str());
|
||||
|
||||
Status s;
|
||||
if (FLAGS_ttl == -1) {
|
||||
@@ -1378,6 +1477,7 @@ class StressTest {
|
||||
|
||||
private:
|
||||
shared_ptr<Cache> cache_;
|
||||
shared_ptr<Cache> compressed_cache_;
|
||||
const FilterPolicy* filter_policy_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
DB* db_;
|
||||
@@ -1387,213 +1487,22 @@ class StressTest {
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
FLAGS_write_buffer_size = rocksdb::Options().write_buffer_size;
|
||||
FLAGS_max_write_buffer_number = rocksdb::Options().max_write_buffer_number;
|
||||
FLAGS_min_write_buffer_number_to_merge =
|
||||
rocksdb::Options().min_write_buffer_number_to_merge;
|
||||
FLAGS_open_files = rocksdb::Options().max_open_files;
|
||||
FLAGS_max_background_compactions =
|
||||
rocksdb::Options().max_background_compactions;
|
||||
FLAGS_compaction_style =
|
||||
rocksdb::Options().compaction_style;
|
||||
FLAGS_level0_file_num_compaction_trigger =
|
||||
rocksdb::Options().level0_file_num_compaction_trigger;
|
||||
FLAGS_level0_slowdown_writes_trigger =
|
||||
rocksdb::Options().level0_slowdown_writes_trigger;
|
||||
FLAGS_level0_stop_writes_trigger =
|
||||
rocksdb::Options().level0_stop_writes_trigger;
|
||||
// Compression test code above refers to FLAGS_block_size
|
||||
FLAGS_block_size = rocksdb::Options().block_size;
|
||||
std::string default_db_path;
|
||||
google::SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
|
||||
" [OPTIONS]...");
|
||||
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
int n;
|
||||
uint32_t u;
|
||||
long l;
|
||||
char junk;
|
||||
char hdfsname[2048];
|
||||
|
||||
if (sscanf(argv[i], "--seed=%uf%c", &u, &junk) == 1) {
|
||||
FLAGS_seed = u;
|
||||
} else if (sscanf(argv[i], "--max_key=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_max_key = l;
|
||||
} else if (sscanf(argv[i], "--log2_keys_per_lock=%u%c", &u, &junk) == 1) {
|
||||
FLAGS_log2_keys_per_lock = u;
|
||||
} else if (sscanf(argv[i], "--ops_per_thread=%u%c", &u, &junk) == 1) {
|
||||
FLAGS_ops_per_thread = u;
|
||||
} else if (sscanf(argv[i], "--verbose=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_verbose = n;
|
||||
} else if (sscanf(argv[i], "--histogram=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_histogram = n;
|
||||
} else if (sscanf(argv[i], "--destroy_db_initially=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_destroy_db_initially = n;
|
||||
} else if (sscanf(argv[i], "--verify_before_write=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_verify_before_write = n;
|
||||
} else if (sscanf(argv[i], "--test_batches_snapshots=%d%c", &n, &junk) == 1
|
||||
&& (n == 0 || n == 1)) {
|
||||
FLAGS_test_batches_snapshots = n;
|
||||
} else if (sscanf(argv[i], "--threads=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_threads = n;
|
||||
} else if (sscanf(argv[i], "--ttl=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_ttl = n;
|
||||
} else if (sscanf(argv[i], "--value_size_mult=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_value_size_mult = n;
|
||||
} else if (sscanf(argv[i], "--write_buffer_size=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_write_buffer_size = n;
|
||||
} else if (sscanf(argv[i], "--max_write_buffer_number=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_max_write_buffer_number = n;
|
||||
} else if (sscanf(argv[i], "--min_write_buffer_number_to_merge=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_min_write_buffer_number_to_merge = n;
|
||||
} else if (sscanf(argv[i], "--max_background_compactions=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_max_background_compactions = n;
|
||||
} else if (sscanf(argv[i], "--compaction_style=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_compaction_style = (rocksdb::CompactionStyle)n;
|
||||
} else if (sscanf(argv[i], "--cache_size=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_cache_size = l;
|
||||
} else if (sscanf(argv[i], "--block_size=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_block_size = n;
|
||||
} else if (sscanf(argv[i], "--reopen=%d%c", &n, &junk) == 1 && n >= 0) {
|
||||
FLAGS_reopen = n;
|
||||
} else if (sscanf(argv[i], "--bloom_bits=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_bloom_bits = n;
|
||||
} else if (sscanf(argv[i], "--open_files=%d%c", &n, &junk) == 1) {
|
||||
FLAGS_open_files = n;
|
||||
} else if (strncmp(argv[i], "--db=", 5) == 0) {
|
||||
FLAGS_db = argv[i] + 5;
|
||||
} else if (sscanf(argv[i], "--verify_checksum=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_verify_checksum = n;
|
||||
} else if (sscanf(argv[i], "--mmap_read=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_use_mmap_reads = n;
|
||||
} else if (sscanf(argv[i], "--statistics=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
if (n == 1) {
|
||||
dbstats = rocksdb::CreateDBStatistics();
|
||||
}
|
||||
} else if (sscanf(argv[i], "--sync=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_sync = n;
|
||||
} else if (sscanf(argv[i], "--readpercent=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0 && n <= 100)) {
|
||||
FLAGS_readpercent = n;
|
||||
} else if (sscanf(argv[i], "--prefixpercent=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0 && n <= 100)) {
|
||||
FLAGS_prefixpercent = n;
|
||||
} else if (sscanf(argv[i], "--writepercent=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0 && n <= 100)) {
|
||||
FLAGS_writepercent = n;
|
||||
} else if (sscanf(argv[i], "--iterpercent=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0 && n <= 100)) {
|
||||
FLAGS_iterpercent = n;
|
||||
} else if (sscanf(argv[i], "--delpercent=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0 && n <= 100)) {
|
||||
FLAGS_delpercent = n;
|
||||
} else if (sscanf(argv[i], "--numiterations=%u%c", &u, &junk) == 1) {
|
||||
FLAGS_num_iterations = u;
|
||||
} else if (sscanf(argv[i], "--disable_data_sync=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_disable_data_sync = n;
|
||||
} else if (sscanf(argv[i], "--use_fsync=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_use_fsync = n;
|
||||
} else if (sscanf(argv[i], "--kill_random_test=%d%c", &n, &junk) == 1 &&
|
||||
(n >= 0)) {
|
||||
FLAGS_kill_random_test = n;
|
||||
} else if (sscanf(argv[i], "--disable_wal=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_disable_wal = n;
|
||||
} else if (sscanf(argv[i], "--hdfs=%s", hdfsname) == 1) {
|
||||
FLAGS_env = new rocksdb::HdfsEnv(hdfsname);
|
||||
} else if (sscanf(argv[i], "--target_file_size_base=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_target_file_size_base = n;
|
||||
} else if ( sscanf(argv[i], "--target_file_size_multiplier=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_target_file_size_multiplier = n;
|
||||
} else if (
|
||||
sscanf(argv[i], "--max_bytes_for_level_base=%ld%c", &l, &junk) == 1) {
|
||||
FLAGS_max_bytes_for_level_base = l;
|
||||
} else if (sscanf(argv[i], "--max_bytes_for_level_multiplier=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_max_bytes_for_level_multiplier = n;
|
||||
} else if (sscanf(argv[i],"--level0_stop_writes_trigger=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_level0_stop_writes_trigger = n;
|
||||
} else if (sscanf(argv[i],"--level0_slowdown_writes_trigger=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_level0_slowdown_writes_trigger = n;
|
||||
} else if (sscanf(argv[i],"--level0_file_num_compaction_trigger=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_level0_file_num_compaction_trigger = n;
|
||||
} else if (strncmp(argv[i], "--compression_type=", 19) == 0) {
|
||||
const char* ctype = argv[i] + 19;
|
||||
if (!strcasecmp(ctype, "none"))
|
||||
FLAGS_compression_type = rocksdb::kNoCompression;
|
||||
else if (!strcasecmp(ctype, "snappy"))
|
||||
FLAGS_compression_type = rocksdb::kSnappyCompression;
|
||||
else if (!strcasecmp(ctype, "zlib"))
|
||||
FLAGS_compression_type = rocksdb::kZlibCompression;
|
||||
else if (!strcasecmp(ctype, "bzip2"))
|
||||
FLAGS_compression_type = rocksdb::kBZip2Compression;
|
||||
else {
|
||||
fprintf(stdout, "Cannot parse %s\n", argv[i]);
|
||||
}
|
||||
} else if (strncmp(argv[i], "--memtablerep=", 14) == 0) {
|
||||
const char* ctype = argv[i] + 14;
|
||||
if (!strcasecmp(ctype, "skip_list"))
|
||||
FLAGS_rep_factory = kSkipList;
|
||||
else if (!strcasecmp(ctype, "prefix_hash"))
|
||||
FLAGS_rep_factory = kPrefixHash;
|
||||
else if (!strcasecmp(ctype, "unsorted"))
|
||||
FLAGS_rep_factory = kUnsorted;
|
||||
else if (!strcasecmp(ctype, "vector"))
|
||||
FLAGS_rep_factory = kVectorRep;
|
||||
else {
|
||||
fprintf(stdout, "Cannot parse %s\n", argv[i]);
|
||||
}
|
||||
} else if (sscanf(argv[i], "--disable_seek_compaction=%d%c", &n, &junk) == 1
|
||||
&& (n == 0 || n == 1)) {
|
||||
FLAGS_disable_seek_compaction = n;
|
||||
} else if (sscanf(argv[i], "--delete_obsolete_files_period_micros=%ld%c",
|
||||
&l, &junk) == 1) {
|
||||
FLAGS_delete_obsolete_files_period_micros = n;
|
||||
} else if (sscanf(argv[i], "--purge_redundant_percent=%d%c", &n, &junk) == 1
|
||||
&& (n >= 0 && n <= 100)) {
|
||||
FLAGS_purge_redundant_percent = n;
|
||||
} else if (sscanf(argv[i], "--filter_deletes=%d%c", &n, &junk)
|
||||
== 1 && (n == 0 || n == 1)) {
|
||||
FLAGS_filter_deletes = n;
|
||||
} else if (sscanf(argv[i], "--prefix_size=%d%c", &n, &junk) == 1 &&
|
||||
n >= 0 && n < 2000000000) {
|
||||
FLAGS_prefix_size = n;
|
||||
} else if (sscanf(argv[i], "--use_merge=%d%c", &n, &junk)
|
||||
== 1 && (n == 0 || n == 1)) {
|
||||
FLAGS_use_merge_put = n;
|
||||
} else if (sscanf(argv[i], "--universal_size_ratio=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_universal_size_ratio = n;
|
||||
} else if (sscanf(argv[i], "--universal_min_merge_width=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_universal_min_merge_width = n;
|
||||
} else if (sscanf(argv[i], "--universal_max_merge_width=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_universal_max_merge_width = n;
|
||||
} else if (sscanf(argv[i],
|
||||
"--universal_max_size_amplification_percent=%d%c",
|
||||
&n, &junk) == 1) {
|
||||
FLAGS_universal_max_size_amplification_percent = n;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_statistics) {
|
||||
dbstats = rocksdb::CreateDBStatistics();
|
||||
}
|
||||
FLAGS_compression_type_e =
|
||||
StringToCompressionType(FLAGS_compression_type.c_str());
|
||||
if (!FLAGS_hdfs.empty()) {
|
||||
FLAGS_env = new rocksdb::HdfsEnv(FLAGS_hdfs);
|
||||
}
|
||||
FLAGS_rep_factory = StringToRepFactory(FLAGS_memtablerep.c_str());
|
||||
|
||||
// The number of background threads should be at least as much the
|
||||
// max number of concurrent compactions.
|
||||
@@ -1611,16 +1520,17 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
if ((unsigned)FLAGS_reopen >= FLAGS_ops_per_thread) {
|
||||
fprintf(stderr, "Error: #DB-reopens should be < ops_per_thread\n"
|
||||
"Provided reopens = %d and ops_per_thread = %u\n", FLAGS_reopen,
|
||||
"Provided reopens = %d and ops_per_thread = %lu\n", FLAGS_reopen,
|
||||
FLAGS_ops_per_thread);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db == nullptr) {
|
||||
if (FLAGS_db.empty()) {
|
||||
std::string default_db_path;
|
||||
rocksdb::Env::Default()->GetTestDirectory(&default_db_path);
|
||||
default_db_path += "/dbstress";
|
||||
FLAGS_db = default_db_path.c_str();
|
||||
FLAGS_db = default_db_path;
|
||||
}
|
||||
|
||||
rocksdb::StressTest stress;
|
||||
|
||||
@@ -145,6 +145,24 @@ class LDBTestCase(unittest.TestCase):
|
||||
self.assertRunFAIL("batchput k1")
|
||||
self.assertRunFAIL("batchput k1 v1 k2")
|
||||
|
||||
def testCountDelimDump(self):
|
||||
print "Running testCountDelimDump..."
|
||||
self.assertRunOK("batchput x.1 x1 --create_if_missing", "OK")
|
||||
self.assertRunOK("batchput y.abc abc y.2 2 z.13c pqr", "OK")
|
||||
self.assertRunOK("dump --count_delim", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
|
||||
self.assertRunOK("dump --count_delim=\".\"", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
|
||||
self.assertRunOK("batchput x,2 x2 x,abc xabc", "OK")
|
||||
self.assertRunOK("dump --count_delim=\",\"", "x => count:2\tsize:14\nx.1 => count:1\tsize:5\ny.2 => count:1\tsize:4\ny.abc => count:1\tsize:8\nz.13c => count:1\tsize:8")
|
||||
|
||||
def testCountDelimIDump(self):
|
||||
print "Running testCountDelimIDump..."
|
||||
self.assertRunOK("batchput x.1 x1 --create_if_missing", "OK")
|
||||
self.assertRunOK("batchput y.abc abc y.2 2 z.13c pqr", "OK")
|
||||
self.assertRunOK("dump --count_delim", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
|
||||
self.assertRunOK("dump --count_delim=\".\"", "x => count:1\tsize:5\ny => count:2\tsize:12\nz => count:1\tsize:8")
|
||||
self.assertRunOK("batchput x,2 x2 x,abc xabc", "OK")
|
||||
self.assertRunOK("dump --count_delim=\",\"", "x => count:2\tsize:14\nx.1 => count:1\tsize:5\ny.2 => count:1\tsize:4\ny.abc => count:1\tsize:8\nz.13c => count:1\tsize:8")
|
||||
|
||||
def testInvalidCmdLines(self):
|
||||
print "Running testInvalidCmdLines..."
|
||||
# db not specified
|
||||
|
||||
+8
-5
@@ -3,7 +3,6 @@
|
||||
// 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/table.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -15,7 +14,7 @@
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/table_builder.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/format.h"
|
||||
@@ -64,7 +63,7 @@ Status SstFileReader::ReadSequential(bool print_kv,
|
||||
bool has_to,
|
||||
const std::string& to_key)
|
||||
{
|
||||
unique_ptr<Table> table;
|
||||
unique_ptr<TableReader> table_reader;
|
||||
InternalKeyComparator internal_comparator_(BytewiseComparator());
|
||||
Options table_options;
|
||||
table_options.comparator = &internal_comparator_;
|
||||
@@ -76,12 +75,16 @@ Status SstFileReader::ReadSequential(bool print_kv,
|
||||
}
|
||||
uint64_t file_size;
|
||||
table_options.env->GetFileSize(file_name_, &file_size);
|
||||
s = Table::Open(table_options, soptions_, std::move(file), file_size, &table);
|
||||
unique_ptr<TableFactory> table_factory;
|
||||
s = table_options.table_factory->GetTableReader(table_options, soptions_,
|
||||
std::move(file), file_size,
|
||||
&table_reader);
|
||||
if(!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* iter = table->NewIterator(ReadOptions(verify_checksum_, false));
|
||||
Iterator* iter = table_reader->NewIterator(ReadOptions(verify_checksum_,
|
||||
false));
|
||||
uint64_t i = 0;
|
||||
if (has_from) {
|
||||
InternalKey ikey(from_key, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
|
||||
+71
-105
@@ -47,59 +47,15 @@ Blob::Blob(const std::string& blob) {
|
||||
}
|
||||
|
||||
// FreeList
|
||||
FreeList::FreeList() {
|
||||
// We add (0, 0, 0) blob because it makes our life easier and
|
||||
// code cleaner. (0, 0, 0) is always in the list so we can
|
||||
// guarantee that free_chunks_list_ != nullptr, which avoids
|
||||
// lots of unnecessary ifs
|
||||
free_chunks_list_ = (FreeChunk *)malloc(sizeof(FreeChunk));
|
||||
free_chunks_list_->chunk = BlobChunk(0, 0, 0);
|
||||
free_chunks_list_->next = nullptr;
|
||||
}
|
||||
|
||||
FreeList::~FreeList() {
|
||||
while (free_chunks_list_ != nullptr) {
|
||||
FreeChunk* t = free_chunks_list_;
|
||||
free_chunks_list_ = free_chunks_list_->next;
|
||||
free(t);
|
||||
}
|
||||
}
|
||||
|
||||
Status FreeList::Free(const Blob& blob) {
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// add it back to the free list
|
||||
for (auto chunk : blob.chunks) {
|
||||
FreeChunk* itr = free_chunks_list_;
|
||||
|
||||
// find a node AFTER which we'll add the block
|
||||
for ( ; itr->next != nullptr && itr->next->chunk <= chunk;
|
||||
itr = itr->next) {
|
||||
}
|
||||
|
||||
// try to merge with previous block
|
||||
if (itr->chunk.ImmediatelyBefore(chunk)) {
|
||||
// merge
|
||||
itr->chunk.size += chunk.size;
|
||||
free_blocks_ += chunk.size;
|
||||
if (fifo_free_chunks_.size() &&
|
||||
fifo_free_chunks_.back().ImmediatelyBefore(chunk)) {
|
||||
fifo_free_chunks_.back().size += chunk.size;
|
||||
} else {
|
||||
// Insert the block after itr
|
||||
FreeChunk* t = (FreeChunk*)malloc(sizeof(FreeChunk));
|
||||
if (t == nullptr) {
|
||||
throw runtime_error("Malloc failed");
|
||||
}
|
||||
t->chunk = chunk;
|
||||
t->next = itr->next;
|
||||
itr->next = t;
|
||||
itr = t;
|
||||
}
|
||||
|
||||
// try to merge with the next block
|
||||
if (itr->next != nullptr &&
|
||||
itr->chunk.ImmediatelyBefore(itr->next->chunk)) {
|
||||
FreeChunk *tobedeleted = itr->next;
|
||||
itr->chunk.size += itr->next->chunk.size;
|
||||
itr->next = itr->next->next;
|
||||
free(tobedeleted);
|
||||
fifo_free_chunks_.push_back(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,48 +63,38 @@ Status FreeList::Free(const Blob& blob) {
|
||||
}
|
||||
|
||||
Status FreeList::Allocate(uint32_t blocks, Blob* blob) {
|
||||
MutexLock l(&mutex_);
|
||||
FreeChunk** best_fit_node = nullptr;
|
||||
|
||||
// Find the smallest free chunk whose size is greater or equal to blocks
|
||||
for (FreeChunk** itr = &free_chunks_list_; (*itr) != nullptr;
|
||||
itr = &((*itr)->next)) {
|
||||
if ((*itr)->chunk.size >= blocks &&
|
||||
(best_fit_node == nullptr ||
|
||||
(*best_fit_node)->chunk.size > (*itr)->chunk.size)) {
|
||||
best_fit_node = itr;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_fit_node == nullptr || *best_fit_node == nullptr) {
|
||||
// Not enough memory
|
||||
if (free_blocks_ < blocks) {
|
||||
return Status::Incomplete("");
|
||||
}
|
||||
|
||||
blob->SetOneChunk((*best_fit_node)->chunk.bucket_id,
|
||||
(*best_fit_node)->chunk.offset,
|
||||
blocks);
|
||||
blob->chunks.clear();
|
||||
free_blocks_ -= blocks;
|
||||
|
||||
if ((*best_fit_node)->chunk.size > blocks) {
|
||||
// just shorten best_fit_node
|
||||
(*best_fit_node)->chunk.offset += blocks;
|
||||
(*best_fit_node)->chunk.size -= blocks;
|
||||
} else {
|
||||
assert(blocks == (*best_fit_node)->chunk.size);
|
||||
// delete best_fit_node
|
||||
FreeChunk* t = *best_fit_node;
|
||||
(*best_fit_node) = (*best_fit_node)->next;
|
||||
free(t);
|
||||
while (blocks > 0) {
|
||||
assert(fifo_free_chunks_.size() > 0);
|
||||
auto& front = fifo_free_chunks_.front();
|
||||
if (front.size > blocks) {
|
||||
blob->chunks.push_back(BlobChunk(front.bucket_id, front.offset, blocks));
|
||||
front.offset += blocks;
|
||||
front.size -= blocks;
|
||||
blocks = 0;
|
||||
} else {
|
||||
blob->chunks.push_back(front);
|
||||
blocks -= front.size;
|
||||
fifo_free_chunks_.pop_front();
|
||||
}
|
||||
}
|
||||
assert(blocks == 0);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool FreeList::Overlap(const Blob &blob) const {
|
||||
MutexLock l(&mutex_);
|
||||
for (auto chunk : blob.chunks) {
|
||||
for (auto itr = free_chunks_list_; itr != nullptr; itr = itr->next) {
|
||||
if (itr->chunk.Overlap(chunk)) {
|
||||
for (auto itr = fifo_free_chunks_.begin();
|
||||
itr != fifo_free_chunks_.end();
|
||||
++itr) {
|
||||
if (itr->Overlap(chunk)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -160,43 +106,50 @@ bool FreeList::Overlap(const Blob &blob) const {
|
||||
BlobStore::BlobStore(const string& directory,
|
||||
uint64_t block_size,
|
||||
uint32_t blocks_per_bucket,
|
||||
uint32_t max_buckets,
|
||||
Env* env) :
|
||||
directory_(directory),
|
||||
block_size_(block_size),
|
||||
blocks_per_bucket_(blocks_per_bucket),
|
||||
env_(env) {
|
||||
env_(env),
|
||||
max_buckets_(max_buckets) {
|
||||
env_->CreateDirIfMissing(directory_);
|
||||
|
||||
storage_options_.use_mmap_writes = false;
|
||||
storage_options_.use_mmap_reads = false;
|
||||
|
||||
buckets_size_ = 0;
|
||||
buckets_ = new unique_ptr<RandomRWFile>[max_buckets_];
|
||||
|
||||
CreateNewBucket();
|
||||
}
|
||||
|
||||
BlobStore::~BlobStore() {
|
||||
// TODO we don't care about recovery for now
|
||||
delete [] buckets_;
|
||||
}
|
||||
|
||||
Status BlobStore::Put(const char* value, uint64_t size, Blob* blob) {
|
||||
Status BlobStore::Put(const Slice& value, Blob* blob) {
|
||||
// convert size to number of blocks
|
||||
Status s = Allocate((size + block_size_ - 1) / block_size_, blob);
|
||||
Status s = Allocate((value.size() + block_size_ - 1) / block_size_, blob);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
size_t size_left = value.size();
|
||||
|
||||
uint64_t offset = 0; // in bytes, not blocks
|
||||
for (auto chunk : blob->chunks) {
|
||||
uint64_t write_size = min(chunk.size * block_size_, size);
|
||||
assert(chunk.bucket_id < buckets_.size());
|
||||
uint64_t write_size = min(chunk.size * block_size_, size_left);
|
||||
assert(chunk.bucket_id < buckets_size_);
|
||||
s = buckets_[chunk.bucket_id].get()->Write(chunk.offset * block_size_,
|
||||
Slice(value + offset,
|
||||
Slice(value.data() + offset,
|
||||
write_size));
|
||||
if (!s.ok()) {
|
||||
Delete(*blob);
|
||||
return s;
|
||||
}
|
||||
offset += write_size;
|
||||
size -= write_size;
|
||||
size_left -= write_size;
|
||||
if (write_size < chunk.size * block_size_) {
|
||||
// if we have any space left in the block, fill it up with zeros
|
||||
string zero_string(chunk.size * block_size_ - write_size, 0);
|
||||
@@ -206,7 +159,7 @@ Status BlobStore::Put(const char* value, uint64_t size, Blob* blob) {
|
||||
}
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
if (size_left > 0) {
|
||||
Delete(*blob);
|
||||
return Status::IOError("Tried to write more data than fits in the blob");
|
||||
}
|
||||
@@ -216,21 +169,19 @@ Status BlobStore::Put(const char* value, uint64_t size, Blob* blob) {
|
||||
|
||||
Status BlobStore::Get(const Blob& blob,
|
||||
string* value) const {
|
||||
// assert that it doesn't overlap with free list
|
||||
// it will get compiled out for release
|
||||
assert(!free_list_.Overlap(blob));
|
||||
|
||||
uint32_t total_size = 0; // in blocks
|
||||
for (auto chunk : blob.chunks) {
|
||||
total_size += chunk.size;
|
||||
{
|
||||
// assert that it doesn't overlap with free list
|
||||
// it will get compiled out for release
|
||||
MutexLock l(&free_list_mutex_);
|
||||
assert(!free_list_.Overlap(blob));
|
||||
}
|
||||
assert(total_size > 0);
|
||||
value->resize(total_size * block_size_);
|
||||
|
||||
value->resize(blob.Size() * block_size_);
|
||||
|
||||
uint64_t offset = 0; // in bytes, not blocks
|
||||
for (auto chunk : blob.chunks) {
|
||||
Slice result;
|
||||
assert(chunk.bucket_id < buckets_.size());
|
||||
assert(chunk.bucket_id < buckets_size_);
|
||||
Status s;
|
||||
s = buckets_[chunk.bucket_id].get()->Read(chunk.offset * block_size_,
|
||||
chunk.size * block_size_,
|
||||
@@ -250,13 +201,22 @@ Status BlobStore::Get(const Blob& blob,
|
||||
}
|
||||
|
||||
Status BlobStore::Delete(const Blob& blob) {
|
||||
MutexLock l(&free_list_mutex_);
|
||||
return free_list_.Free(blob);
|
||||
}
|
||||
|
||||
Status BlobStore::Sync() {
|
||||
for (size_t i = 0; i < buckets_size_; ++i) {
|
||||
Status s = buckets_[i].get()->Sync();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobStore::Allocate(uint32_t blocks, Blob* blob) {
|
||||
// TODO we don't currently support fragmented blobs
|
||||
MutexLock l(&allocate_mutex_);
|
||||
assert(blocks <= blocks_per_bucket_);
|
||||
MutexLock l(&free_list_mutex_);
|
||||
Status s;
|
||||
|
||||
s = free_list_.Allocate(blocks, blob);
|
||||
@@ -271,10 +231,15 @@ Status BlobStore::Allocate(uint32_t blocks, Blob* blob) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// called with free_list_mutex_ held
|
||||
Status BlobStore::CreateNewBucket() {
|
||||
int new_bucket_id;
|
||||
new_bucket_id = buckets_.size();
|
||||
buckets_.push_back(unique_ptr<RandomRWFile>());
|
||||
MutexLock l(&buckets_mutex_);
|
||||
|
||||
if (buckets_size_ >= max_buckets_) {
|
||||
return Status::IOError("Max size exceeded\n");
|
||||
}
|
||||
|
||||
int new_bucket_id = buckets_size_;
|
||||
|
||||
char fname[200];
|
||||
sprintf(fname, "%s/%d.bs", directory_.c_str(), new_bucket_id);
|
||||
@@ -283,7 +248,6 @@ Status BlobStore::CreateNewBucket() {
|
||||
&buckets_[new_bucket_id],
|
||||
storage_options_);
|
||||
if (!s.ok()) {
|
||||
buckets_.erase(buckets_.begin() + new_bucket_id);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -292,6 +256,8 @@ Status BlobStore::CreateNewBucket() {
|
||||
// (also, tmpfs does not support allocate)
|
||||
buckets_[new_bucket_id].get()->Allocate(0, block_size_ * blocks_per_bucket_);
|
||||
|
||||
buckets_size_ = new_bucket_id + 1;
|
||||
|
||||
return free_list_.Free(Blob(new_bucket_id, 0, blocks_per_bucket_));
|
||||
}
|
||||
|
||||
|
||||
+35
-25
@@ -10,6 +10,7 @@
|
||||
#include "util/coding.h"
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
@@ -26,16 +27,6 @@ struct BlobChunk {
|
||||
BlobChunk(uint32_t bucket_id, uint32_t offset, uint32_t size) :
|
||||
bucket_id(bucket_id), offset(offset), size(size) {}
|
||||
|
||||
bool operator <= (const BlobChunk& chunk) const {
|
||||
if (bucket_id != chunk.bucket_id) {
|
||||
return bucket_id < chunk.bucket_id;
|
||||
}
|
||||
if (offset != chunk.offset) {
|
||||
return offset < chunk.offset;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns true if it's immediately before chunk
|
||||
bool ImmediatelyBefore(const BlobChunk& chunk) const;
|
||||
// returns true if chunks overlap
|
||||
@@ -63,15 +54,26 @@ struct Blob {
|
||||
chunks.push_back(BlobChunk(bucket_id, offset, size));
|
||||
}
|
||||
|
||||
uint32_t Size() const { // in blocks
|
||||
uint32_t ret = 0;
|
||||
for (auto chunk : chunks) {
|
||||
ret += chunk.size;
|
||||
}
|
||||
assert(ret > 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// bucket_id, offset, size
|
||||
std::vector<BlobChunk> chunks;
|
||||
};
|
||||
|
||||
// Keeps a list of free chunks
|
||||
// NOT thread-safe. Externally synchronized
|
||||
class FreeList {
|
||||
public:
|
||||
FreeList ();
|
||||
~FreeList();
|
||||
FreeList() :
|
||||
free_blocks_(0) {}
|
||||
~FreeList() {}
|
||||
|
||||
// Allocates a a blob. Stores the allocated blob in
|
||||
// 'blob'. Returns non-OK status if it failed to allocate.
|
||||
@@ -85,12 +87,8 @@ class FreeList {
|
||||
bool Overlap(const Blob &blob) const;
|
||||
|
||||
private:
|
||||
struct FreeChunk {
|
||||
BlobChunk chunk;
|
||||
FreeChunk* next;
|
||||
};
|
||||
|
||||
FreeChunk* free_chunks_list_;
|
||||
std::deque<BlobChunk> fifo_free_chunks_;
|
||||
uint32_t free_blocks_;
|
||||
mutable port::Mutex mutex_;
|
||||
};
|
||||
|
||||
@@ -104,24 +102,30 @@ class BlobStore {
|
||||
// Bucket is a device or a file that we use to store the blobs.
|
||||
// If we don't have enough blocks to allocate a new blob, we will
|
||||
// try to create a new file or device.
|
||||
// max_buckets - maximum number of buckets BlobStore will create
|
||||
// BlobStore max size in bytes is
|
||||
// max_buckets * blocks_per_bucket * block_size
|
||||
// env - env for creating new files
|
||||
BlobStore(const std::string& directory,
|
||||
uint64_t block_size,
|
||||
uint32_t blocks_per_bucket,
|
||||
uint32_t max_buckets,
|
||||
Env* env);
|
||||
~BlobStore();
|
||||
|
||||
// Allocates space for size bytes (rounded up to be multiple of
|
||||
// block size) and writes size bytes from value to a backing store.
|
||||
// Allocates space for value.size bytes (rounded up to be multiple of
|
||||
// block size) and writes value.size bytes from value.data to a backing store.
|
||||
// Sets Blob blob that can than be used for addressing the
|
||||
// stored value. Returns non-OK status on error.
|
||||
Status Put(const char* value, uint64_t size, Blob* blob);
|
||||
Status Put(const Slice& value, Blob* blob);
|
||||
// Value needs to have enough space to store all the loaded stuff.
|
||||
// This function is thread safe!
|
||||
Status Get(const Blob& blob, std::string* value) const;
|
||||
// Frees the blob for reuse, but does not delete the data
|
||||
// on the backing store.
|
||||
Status Delete(const Blob& blob);
|
||||
// Sync all opened files that are modified
|
||||
Status Sync();
|
||||
|
||||
private:
|
||||
const std::string directory_;
|
||||
@@ -132,10 +136,17 @@ class BlobStore {
|
||||
const uint32_t blocks_per_bucket_;
|
||||
Env* env_;
|
||||
EnvOptions storage_options_;
|
||||
// protected by free_list_mutex_
|
||||
FreeList free_list_;
|
||||
// protected by buckets mutex
|
||||
std::vector<unique_ptr<RandomRWFile>> buckets_;
|
||||
mutable port::Mutex allocate_mutex_;
|
||||
// free_list_mutex_ is locked BEFORE buckets_mutex_
|
||||
mutable port::Mutex free_list_mutex_;
|
||||
// protected by buckets_mutex_
|
||||
// array of buckets
|
||||
unique_ptr<RandomRWFile>* buckets_;
|
||||
// number of buckets in the array
|
||||
uint32_t buckets_size_;
|
||||
uint32_t max_buckets_;
|
||||
mutable port::Mutex buckets_mutex_;
|
||||
|
||||
// Calls FreeList allocate. If free list can't allocate
|
||||
// new blob, creates new bucket and tries again
|
||||
@@ -144,7 +155,6 @@ class BlobStore {
|
||||
|
||||
// Creates a new backing store and adds all the blocks
|
||||
// from the new backing store to the free list
|
||||
// NOT thread-safe, call with lock held
|
||||
Status CreateNewBucket();
|
||||
};
|
||||
|
||||
|
||||
+73
-6
@@ -37,6 +37,7 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
BlobStore blob_store(test::TmpDir() + "/blob_store_test",
|
||||
block_size,
|
||||
blocks_per_file,
|
||||
1000,
|
||||
Env::Default());
|
||||
|
||||
string buf;
|
||||
@@ -44,7 +45,7 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
// put string of size 170
|
||||
test::RandomString(&random, 170, &buf);
|
||||
Blob r1;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), 170, &r1));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r1));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r1.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r1.chunks[0].bucket_id, 0u);
|
||||
@@ -53,7 +54,7 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
// put string of size 30
|
||||
test::RandomString(&random, 30, &buf);
|
||||
Blob r2;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), 30, &r2));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r2));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r2.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r2.chunks[0].bucket_id, 0u);
|
||||
@@ -65,7 +66,7 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
// put a string of size 100
|
||||
test::RandomString(&random, 100, &buf);
|
||||
Blob r3;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), 100, &r3));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r3));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r3.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r3.chunks[0].bucket_id, 0u);
|
||||
@@ -74,7 +75,7 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
// put a string of size 70
|
||||
test::RandomString(&random, 70, &buf);
|
||||
Blob r4;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), 70, &r4));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r4));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r4.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r4.chunks[0].bucket_id, 0u);
|
||||
@@ -83,12 +84,51 @@ TEST(BlobStoreTest, SanityTest) {
|
||||
// put a string of size 5
|
||||
test::RandomString(&random, 5, &buf);
|
||||
Blob r5;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), 70, &r5));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r5));
|
||||
// now you get to use the second file
|
||||
for (size_t i = 0; i < r5.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r5.chunks[0].bucket_id, 1u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlobStoreTest, FragmentedChunksTest) {
|
||||
const uint64_t block_size = 10;
|
||||
const uint32_t blocks_per_file = 20;
|
||||
Random random(5);
|
||||
|
||||
BlobStore blob_store(test::TmpDir() + "/blob_store_test",
|
||||
block_size,
|
||||
blocks_per_file,
|
||||
1000,
|
||||
Env::Default());
|
||||
|
||||
string buf;
|
||||
|
||||
vector <Blob> r(4);
|
||||
|
||||
// put 4 strings of size 50
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
test::RandomString(&random, 50, &buf);
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r[k]));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r[k].chunks.size(); ++i) {
|
||||
ASSERT_EQ(r[k].chunks[0].bucket_id, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the first and third
|
||||
ASSERT_OK(blob_store.Delete(r[0]));
|
||||
ASSERT_OK(blob_store.Delete(r[2]));
|
||||
|
||||
// put string of size 100. it should reuse space that we deleting
|
||||
// by deleting first and third strings of size 50
|
||||
test::RandomString(&random, 100, &buf);
|
||||
Blob r2;
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r2));
|
||||
// use the first file
|
||||
for (size_t i = 0; i < r2.chunks.size(); ++i) {
|
||||
ASSERT_EQ(r2.chunks[0].bucket_id, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlobStoreTest, CreateAndStoreTest) {
|
||||
@@ -100,6 +140,7 @@ TEST(BlobStoreTest, CreateAndStoreTest) {
|
||||
BlobStore blob_store(test::TmpDir() + "/blob_store_test",
|
||||
block_size,
|
||||
blocks_per_file,
|
||||
10000,
|
||||
Env::Default());
|
||||
vector<pair<Blob, string>> ranges;
|
||||
|
||||
@@ -111,7 +152,7 @@ TEST(BlobStoreTest, CreateAndStoreTest) {
|
||||
int string_size = size_blocks * block_size - (rand() % block_size);
|
||||
test::RandomString(&random, string_size, &buf);
|
||||
Blob r;
|
||||
ASSERT_OK(blob_store.Put(buf.data(), string_size, &r));
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r));
|
||||
ranges.push_back(make_pair(r, buf));
|
||||
} else if (decision == 3) {
|
||||
int ti = rand() % ranges.size();
|
||||
@@ -124,6 +165,32 @@ TEST(BlobStoreTest, CreateAndStoreTest) {
|
||||
ranges.erase(ranges.begin() + ti);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(blob_store.Sync());
|
||||
}
|
||||
|
||||
TEST(BlobStoreTest, MaxSizeTest) {
|
||||
const uint64_t block_size = 10;
|
||||
const uint32_t blocks_per_file = 100;
|
||||
const int max_buckets = 10;
|
||||
Random random(5);
|
||||
|
||||
BlobStore blob_store(test::TmpDir() + "/blob_store_test",
|
||||
block_size,
|
||||
blocks_per_file,
|
||||
max_buckets,
|
||||
Env::Default());
|
||||
string buf;
|
||||
for (int i = 0; i < max_buckets; ++i) {
|
||||
test::RandomString(&random, 1000, &buf);
|
||||
Blob r;
|
||||
ASSERT_OK(blob_store.Put(Slice(buf), &r));
|
||||
}
|
||||
|
||||
test::RandomString(&random, 1000, &buf);
|
||||
Blob r;
|
||||
// should fail because max size
|
||||
Status s = blob_store.Put(Slice(buf), &r);
|
||||
ASSERT_EQ(s.ok(), false);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+12
@@ -30,6 +30,12 @@ Logger::~Logger() {
|
||||
FileLock::~FileLock() {
|
||||
}
|
||||
|
||||
void LogFlush(Logger *info_log) {
|
||||
if (info_log) {
|
||||
info_log->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Log(Logger* info_log, const char* format, ...) {
|
||||
if (info_log) {
|
||||
va_list ap;
|
||||
@@ -39,6 +45,12 @@ void Log(Logger* info_log, const char* format, ...) {
|
||||
}
|
||||
}
|
||||
|
||||
void LogFlush(const shared_ptr<Logger>& info_log) {
|
||||
if (info_log) {
|
||||
info_log->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Log(const shared_ptr<Logger>& info_log, const char* format, ...) {
|
||||
if (info_log) {
|
||||
va_list ap;
|
||||
|
||||
+35
-25
@@ -101,6 +101,36 @@ static void TestKillRandom(int odds, const std::string& srcfile,
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
namespace {
|
||||
static size_t GetUniqueIdFromFile(int fd, char* id, size_t max_size) {
|
||||
if (max_size < kMaxVarint64Length*3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat buf;
|
||||
int result = fstat(fd, &buf);
|
||||
if (result == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long version = 0;
|
||||
result = ioctl(fd, FS_IOC_GETVERSION, &version);
|
||||
if (result == -1) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t uversion = (uint64_t)version;
|
||||
|
||||
char* rid = id;
|
||||
rid = EncodeVarint64(rid, buf.st_dev);
|
||||
rid = EncodeVarint64(rid, buf.st_ino);
|
||||
rid = EncodeVarint64(rid, uversion);
|
||||
assert(rid >= id);
|
||||
return static_cast<size_t>(rid-id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
class PosixSequentialFile: public SequentialFile {
|
||||
private:
|
||||
std::string filename_;
|
||||
@@ -187,30 +217,7 @@ class PosixRandomAccessFile: public RandomAccessFile {
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const {
|
||||
// TODO: possibly allow this function to handle tighter bounds.
|
||||
if (max_size < kMaxVarint64Length*3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat buf;
|
||||
int result = fstat(fd_, &buf);
|
||||
if (result == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long version = 0;
|
||||
result = ioctl(fd_, FS_IOC_GETVERSION, &version);
|
||||
if (result == -1) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t uversion = (uint64_t)version;
|
||||
|
||||
char* rid = id;
|
||||
rid = EncodeVarint64(rid, buf.st_dev);
|
||||
rid = EncodeVarint64(rid, buf.st_ino);
|
||||
rid = EncodeVarint64(rid, uversion);
|
||||
assert(rid >= id);
|
||||
return static_cast<size_t>(rid-id);
|
||||
return GetUniqueIdFromFile(fd_, id, max_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -711,6 +718,9 @@ class PosixWritableFile : public WritableFile {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
virtual size_t GetUniqueId(char* id, size_t max_size) const {
|
||||
return GetUniqueIdFromFile(fd_, id, max_size);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1121,7 +1131,7 @@ class PosixEnv : public Env {
|
||||
} else {
|
||||
int fd = fileno(f);
|
||||
SetFD_CLOEXEC(fd, nullptr);
|
||||
result->reset(new PosixLogger(f, &PosixEnv::gettid));
|
||||
result->reset(new PosixLogger(f, &PosixEnv::gettid, this));
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
+97
-9
@@ -580,6 +580,7 @@ void PrintBucketCounts(const vector<uint64_t>& bucket_counts, int ttl_start,
|
||||
}
|
||||
|
||||
const string InternalDumpCommand::ARG_COUNT_ONLY = "count_only";
|
||||
const string InternalDumpCommand::ARG_COUNT_DELIM = "count_delim";
|
||||
const string InternalDumpCommand::ARG_STATS = "stats";
|
||||
const string InternalDumpCommand::ARG_INPUT_KEY_HEX = "input_key_hex";
|
||||
|
||||
@@ -589,12 +590,14 @@ InternalDumpCommand::InternalDumpCommand(const vector<string>& params,
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_FROM, ARG_TO, ARG_MAX_KEYS,
|
||||
ARG_COUNT_ONLY, ARG_STATS,
|
||||
ARG_COUNT_ONLY, ARG_COUNT_DELIM, ARG_STATS,
|
||||
ARG_INPUT_KEY_HEX})),
|
||||
has_from_(false),
|
||||
has_to_(false),
|
||||
max_keys_(-1),
|
||||
delim_("."),
|
||||
count_only_(false),
|
||||
count_delim_(false),
|
||||
print_stats_(false),
|
||||
is_input_key_hex_(false) {
|
||||
|
||||
@@ -602,6 +605,15 @@ InternalDumpCommand::InternalDumpCommand(const vector<string>& params,
|
||||
has_to_ = ParseStringOption(options, ARG_TO, &to_);
|
||||
|
||||
ParseIntOption(options, ARG_MAX_KEYS, max_keys_, exec_state_);
|
||||
map<string, string>::const_iterator itr = options.find(ARG_COUNT_DELIM);
|
||||
if (itr != options.end()) {
|
||||
delim_ = itr->second;
|
||||
count_delim_ = true;
|
||||
// fprintf(stdout,"delim = %c\n",delim_[0]);
|
||||
} else {
|
||||
count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM);
|
||||
delim_=".";
|
||||
}
|
||||
|
||||
print_stats_ = IsFlagPresent(flags, ARG_STATS);
|
||||
count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY);
|
||||
@@ -624,6 +636,7 @@ void InternalDumpCommand::Help(string& ret) {
|
||||
ret.append(" [--" + ARG_INPUT_KEY_HEX + "]");
|
||||
ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
|
||||
ret.append(" [--" + ARG_COUNT_ONLY + "]");
|
||||
ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]");
|
||||
ret.append(" [--" + ARG_STATS + "]");
|
||||
ret.append("\n");
|
||||
}
|
||||
@@ -646,7 +659,10 @@ void InternalDumpCommand::DoCommand() {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("DB is not DBImpl");
|
||||
return;
|
||||
}
|
||||
|
||||
string rtype1,rtype2,row,val;
|
||||
rtype2 = "";
|
||||
uint64_t c=0;
|
||||
uint64_t s1=0,s2=0;
|
||||
// Setup internal key iterator
|
||||
auto iter = unique_ptr<Iterator>(idb->TEST_NewInternalIterator());
|
||||
Status st = iter->status();
|
||||
@@ -678,8 +694,32 @@ void InternalDumpCommand::DoCommand() {
|
||||
}
|
||||
|
||||
++count;
|
||||
int k;
|
||||
if (count_delim_) {
|
||||
rtype1 = "";
|
||||
s1=0;
|
||||
row = iter->key().ToString();
|
||||
val = iter->value().ToString();
|
||||
for(k=0;row[k]!='\x01' && row[k]!='\0';k++)
|
||||
s1++;
|
||||
for(k=0;val[k]!='\x01' && val[k]!='\0';k++)
|
||||
s1++;
|
||||
for(int j=0;row[j]!=delim_[0] && row[j]!='\0' && row[j]!='\x01';j++)
|
||||
rtype1+=row[j];
|
||||
if(rtype2.compare("") && rtype2.compare(rtype1)!=0) {
|
||||
fprintf(stdout,"%s => count:%lld\tsize:%lld\n",rtype2.c_str(),
|
||||
(long long)c,(long long)s2);
|
||||
c=1;
|
||||
s2=s1;
|
||||
rtype2 = rtype1;
|
||||
} else {
|
||||
c++;
|
||||
s2+=s1;
|
||||
rtype2=rtype1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!count_only_) {
|
||||
if (!count_only_ && !count_delim_) {
|
||||
string key = ikey.DebugString(is_key_hex_);
|
||||
string value = iter->value().ToString(is_value_hex_);
|
||||
std::cout << key << " => " << value << "\n";
|
||||
@@ -688,12 +728,16 @@ void InternalDumpCommand::DoCommand() {
|
||||
// Terminate if maximum number of keys have been dumped
|
||||
if (max_keys_ > 0 && count >= max_keys_) break;
|
||||
}
|
||||
|
||||
if(count_delim_) {
|
||||
fprintf(stdout,"%s => count:%lld\tsize:%lld\n", rtype2.c_str(),
|
||||
(long long)c,(long long)s2);
|
||||
} else
|
||||
fprintf(stdout, "Internal keys in range: %lld\n", (long long) count);
|
||||
}
|
||||
|
||||
|
||||
const string DBDumperCommand::ARG_COUNT_ONLY = "count_only";
|
||||
const string DBDumperCommand::ARG_COUNT_DELIM = "count_delim";
|
||||
const string DBDumperCommand::ARG_STATS = "stats";
|
||||
const string DBDumperCommand::ARG_TTL_BUCKET = "bucket";
|
||||
|
||||
@@ -702,13 +746,15 @@ DBDumperCommand::DBDumperCommand(const vector<string>& params,
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
|
||||
ARG_VALUE_HEX, ARG_FROM, ARG_TO,
|
||||
ARG_MAX_KEYS, ARG_COUNT_ONLY, ARG_STATS,
|
||||
ARG_TTL_START, ARG_TTL_END,
|
||||
ARG_TTL_BUCKET, ARG_TIMESTAMP})),
|
||||
ARG_MAX_KEYS, ARG_COUNT_ONLY,
|
||||
ARG_COUNT_DELIM, ARG_STATS, ARG_TTL_START,
|
||||
ARG_TTL_END, ARG_TTL_BUCKET,
|
||||
ARG_TIMESTAMP})),
|
||||
null_from_(true),
|
||||
null_to_(true),
|
||||
max_keys_(-1),
|
||||
count_only_(false),
|
||||
count_delim_(false),
|
||||
print_stats_(false) {
|
||||
|
||||
map<string, string>::const_iterator itr = options.find(ARG_FROM);
|
||||
@@ -735,6 +781,14 @@ DBDumperCommand::DBDumperCommand(const vector<string>& params,
|
||||
" has a value out-of-range");
|
||||
}
|
||||
}
|
||||
itr = options.find(ARG_COUNT_DELIM);
|
||||
if (itr != options.end()) {
|
||||
delim_ = itr->second;
|
||||
count_delim_ = true;
|
||||
} else {
|
||||
count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM);
|
||||
delim_=".";
|
||||
}
|
||||
|
||||
print_stats_ = IsFlagPresent(flags, ARG_STATS);
|
||||
count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY);
|
||||
@@ -757,6 +811,7 @@ void DBDumperCommand::Help(string& ret) {
|
||||
ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
|
||||
ret.append(" [--" + ARG_TIMESTAMP + "]");
|
||||
ret.append(" [--" + ARG_COUNT_ONLY + "]");
|
||||
ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]");
|
||||
ret.append(" [--" + ARG_STATS + "]");
|
||||
ret.append(" [--" + ARG_TTL_BUCKET + "=<N>]");
|
||||
ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
|
||||
@@ -811,11 +866,17 @@ void DBDumperCommand::DoCommand() {
|
||||
bucket_size <= 0) {
|
||||
bucket_size = time_range; // Will have just 1 bucket by default
|
||||
}
|
||||
//cretaing variables for row count of each type
|
||||
string rtype1,rtype2,row,val;
|
||||
rtype2 = "";
|
||||
uint64_t c=0;
|
||||
uint64_t s1=0,s2=0;
|
||||
|
||||
// At this point, bucket_size=0 => time_range=0
|
||||
uint64_t num_buckets = (bucket_size >= time_range) ? 1 :
|
||||
((time_range + bucket_size - 1) / bucket_size);
|
||||
vector<uint64_t> bucket_counts(num_buckets, 0);
|
||||
if (is_db_ttl_ && !count_only_ && timestamp_) {
|
||||
if (is_db_ttl_ && !count_only_ && timestamp_ && !count_delim_) {
|
||||
fprintf(stdout, "Dumping key-values from %s to %s\n",
|
||||
ReadableTime(ttl_start).c_str(), ReadableTime(ttl_end).c_str());
|
||||
}
|
||||
@@ -844,7 +905,30 @@ void DBDumperCommand::DoCommand() {
|
||||
rawtime, num_buckets);
|
||||
}
|
||||
++count;
|
||||
if (!count_only_) {
|
||||
if (count_delim_) {
|
||||
rtype1 = "";
|
||||
row = iter->key().ToString();
|
||||
val = iter->value().ToString();
|
||||
s1 = row.size()+val.size();
|
||||
for(int j=0;row[j]!=delim_[0] && row[j]!='\0';j++)
|
||||
rtype1+=row[j];
|
||||
if(rtype2.compare("") && rtype2.compare(rtype1)!=0) {
|
||||
fprintf(stdout,"%s => count:%lld\tsize:%lld\n",rtype2.c_str(),
|
||||
(long long )c,(long long)s2);
|
||||
c=1;
|
||||
s2=s1;
|
||||
rtype2 = rtype1;
|
||||
} else {
|
||||
c++;
|
||||
s2+=s1;
|
||||
rtype2=rtype1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!count_only_ && !count_delim_) {
|
||||
if (is_db_ttl_ && timestamp_) {
|
||||
fprintf(stdout, "%s ", ReadableTime(rawtime).c_str());
|
||||
}
|
||||
@@ -854,9 +938,13 @@ void DBDumperCommand::DoCommand() {
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (num_buckets > 1 && is_db_ttl_) {
|
||||
PrintBucketCounts(bucket_counts, ttl_start, ttl_end, bucket_size,
|
||||
num_buckets);
|
||||
} else if(count_delim_) {
|
||||
fprintf(stdout,"%s => count:%lld\tsize:%lld\n",rtype2.c_str(),
|
||||
(long long )c,(long long)s2);
|
||||
} else {
|
||||
fprintf(stdout, "Keys in range: %lld\n", (long long) count);
|
||||
}
|
||||
|
||||
@@ -395,10 +395,13 @@ private:
|
||||
bool null_to_;
|
||||
string to_;
|
||||
int max_keys_;
|
||||
string delim_;
|
||||
bool count_only_;
|
||||
bool count_delim_;
|
||||
bool print_stats_;
|
||||
|
||||
static const string ARG_COUNT_ONLY;
|
||||
static const string ARG_COUNT_DELIM;
|
||||
static const string ARG_STATS;
|
||||
static const string ARG_TTL_BUCKET;
|
||||
};
|
||||
@@ -421,11 +424,15 @@ private:
|
||||
bool has_to_;
|
||||
string to_;
|
||||
int max_keys_;
|
||||
string delim_;
|
||||
bool count_only_;
|
||||
bool count_delim_;
|
||||
bool print_stats_;
|
||||
bool is_input_key_hex_;
|
||||
|
||||
static const string ARG_DELIM;
|
||||
static const string ARG_COUNT_ONLY;
|
||||
static const string ARG_COUNT_DELIM;
|
||||
static const string ARG_STATS;
|
||||
static const string ARG_INPUT_KEY_HEX;
|
||||
};
|
||||
|
||||
+33
-5
@@ -17,6 +17,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -33,6 +34,8 @@ Options::Options()
|
||||
max_write_buffer_number(2),
|
||||
min_write_buffer_number_to_merge(1),
|
||||
max_open_files(1000),
|
||||
block_cache(nullptr),
|
||||
block_cache_compressed(nullptr),
|
||||
block_size(4096),
|
||||
block_restart_interval(16),
|
||||
compression(kSnappyCompression),
|
||||
@@ -74,6 +77,7 @@ Options::Options()
|
||||
arena_block_size(0),
|
||||
disable_auto_compactions(false),
|
||||
WAL_ttl_seconds(0),
|
||||
WAL_size_limit_MB(0),
|
||||
manifest_preallocation_size(4 * 1024 * 1024),
|
||||
purge_redundant_kvs_while_flush(true),
|
||||
allow_os_buffer(true),
|
||||
@@ -91,10 +95,14 @@ Options::Options()
|
||||
filter_deletes(false),
|
||||
max_sequential_skip_in_iterations(8),
|
||||
memtable_factory(std::shared_ptr<SkipListFactory>(new SkipListFactory)),
|
||||
table_factory(
|
||||
std::shared_ptr<TableFactory>(new BlockBasedTableFactory())),
|
||||
compaction_filter_factory(
|
||||
std::shared_ptr<CompactionFilterFactory>(
|
||||
new DefaultCompactionFilterFactory())),
|
||||
purge_log_after_memtable_flush(true) {
|
||||
purge_log_after_memtable_flush(true),
|
||||
inplace_update_support(false),
|
||||
inplace_update_num_locks(10000) {
|
||||
assert(memtable_factory.get() != nullptr);
|
||||
}
|
||||
|
||||
@@ -114,6 +122,7 @@ Options::Dump(Logger* log) const
|
||||
compaction_filter_factory->Name());
|
||||
Log(log," Options.memtable_factory: %s",
|
||||
memtable_factory->Name());
|
||||
Log(log," Options.table_factory: %s", table_factory->Name());
|
||||
Log(log," Options.error_if_exists: %d", error_if_exists);
|
||||
Log(log," Options.create_if_missing: %d", create_if_missing);
|
||||
Log(log," Options.paranoid_checks: %d", paranoid_checks);
|
||||
@@ -123,10 +132,16 @@ Options::Dump(Logger* log) const
|
||||
Log(log," Options.max_write_buffer_number: %d", max_write_buffer_number);
|
||||
Log(log," Options.max_open_files: %d", max_open_files);
|
||||
Log(log," Options.block_cache: %p", block_cache.get());
|
||||
Log(log," Options.block_cache_compressed: %p",
|
||||
block_cache_compressed.get());
|
||||
if (block_cache) {
|
||||
Log(log," Options.block_cache_size: %zd",
|
||||
Log(log," Options.block_cache_size: %zd",
|
||||
block_cache->GetCapacity());
|
||||
}
|
||||
if (block_cache_compressed) {
|
||||
Log(log,"Options.block_cache_compressed_size: %zd",
|
||||
block_cache_compressed->GetCapacity());
|
||||
}
|
||||
Log(log," Options.block_size: %zd", block_size);
|
||||
Log(log," Options.block_restart_interval: %d", block_restart_interval);
|
||||
if (!compression_per_level.empty()) {
|
||||
@@ -223,6 +238,8 @@ Options::Dump(Logger* log) const
|
||||
disable_auto_compactions);
|
||||
Log(log," Options.WAL_ttl_seconds: %ld",
|
||||
WAL_ttl_seconds);
|
||||
Log(log," Options.WAL_size_limit_MB: %ld",
|
||||
WAL_size_limit_MB);
|
||||
Log(log," Options.manifest_preallocation_size: %ld",
|
||||
manifest_preallocation_size);
|
||||
Log(log," Options.purge_redundant_kvs_while_flush: %d",
|
||||
@@ -253,17 +270,28 @@ Options::Dump(Logger* log) const
|
||||
filter_deletes);
|
||||
Log(log," Options.compaction_style: %d",
|
||||
compaction_style);
|
||||
Log(log," Options.compaction_options_universal.size_ratio: %u",
|
||||
Log(log," Options.compaction_options_universal.size_ratio: %u",
|
||||
compaction_options_universal.size_ratio);
|
||||
Log(log," Options.compaction_options_universal.min_merge_width: %u",
|
||||
Log(log,"Options.compaction_options_universal.min_merge_width: %u",
|
||||
compaction_options_universal.min_merge_width);
|
||||
Log(log," Options.compaction_options_universal.max_merge_width: %u",
|
||||
Log(log,"Options.compaction_options_universal.max_merge_width: %u",
|
||||
compaction_options_universal.max_merge_width);
|
||||
Log(log,"Options.compaction_options_universal."
|
||||
"max_size_amplification_percent: %u",
|
||||
compaction_options_universal.max_size_amplification_percent);
|
||||
Log(log," Options.purge_log_after_memtable_flush: %d",
|
||||
purge_log_after_memtable_flush);
|
||||
std::string collector_names;
|
||||
for (auto collector : table_stats_collectors) {
|
||||
collector_names.append(collector->Name());
|
||||
collector_names.append("; ");
|
||||
}
|
||||
Log(log," Options.table_stats_collectors: %s",
|
||||
collector_names.c_str());
|
||||
Log(log," Options.inplace_update_support: %d",
|
||||
inplace_update_support);
|
||||
Log(log," Options.inplace_update_num_locks: %zd",
|
||||
inplace_update_num_locks);
|
||||
} // Options::Dump
|
||||
|
||||
//
|
||||
|
||||
@@ -22,6 +22,7 @@ void PerfContext::Reset() {
|
||||
block_decompress_time = 0;
|
||||
internal_key_skipped_count = 0;
|
||||
internal_delete_skipped_count = 0;
|
||||
wal_write_time = 0;
|
||||
}
|
||||
|
||||
__thread PerfContext perf_context;
|
||||
|
||||
+16
-3
@@ -33,12 +33,20 @@ class PosixLogger : public Logger {
|
||||
uint64_t (*gettid_)(); // Return the thread id for the current thread
|
||||
std::atomic_size_t log_size_;
|
||||
int fd_;
|
||||
const static uint64_t flush_every_seconds_ = 5;
|
||||
std::atomic_uint_fast64_t last_flush_micros_;
|
||||
Env* env_;
|
||||
public:
|
||||
PosixLogger(FILE* f, uint64_t (*gettid)()) :
|
||||
file_(f), gettid_(gettid), log_size_(0), fd_(fileno(f)) { }
|
||||
PosixLogger(FILE* f, uint64_t (*gettid)(), Env* env) :
|
||||
file_(f), gettid_(gettid), log_size_(0), fd_(fileno(f)),
|
||||
last_flush_micros_(0), env_(env) { }
|
||||
virtual ~PosixLogger() {
|
||||
fclose(file_);
|
||||
}
|
||||
virtual void Flush() {
|
||||
fflush(file_);
|
||||
last_flush_micros_ = env_->NowMicros();
|
||||
}
|
||||
virtual void Logv(const char* format, va_list ap) {
|
||||
const uint64_t thread_id = (*gettid_)();
|
||||
|
||||
@@ -118,9 +126,14 @@ class PosixLogger : public Logger {
|
||||
size_t sz = fwrite(base, 1, write_size, file_);
|
||||
assert(sz == write_size);
|
||||
if (sz > 0) {
|
||||
fflush(file_);
|
||||
log_size_ += write_size;
|
||||
}
|
||||
uint64_t now_micros = static_cast<uint64_t>(now_tv.tv_sec) * 1000000 +
|
||||
now_tv.tv_usec;
|
||||
if (now_micros - last_flush_micros_ >= flush_every_seconds_ * 1000000) {
|
||||
fflush(file_);
|
||||
last_flush_micros_ = now_micros;
|
||||
}
|
||||
if (base != buffer) {
|
||||
delete[] base;
|
||||
}
|
||||
|
||||
@@ -51,10 +51,16 @@ class TransformRep : public MemTableRep {
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator(
|
||||
const Slice& slice) override;
|
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetDynamicPrefixIterator()
|
||||
override {
|
||||
return std::make_shared<DynamicPrefixIterator>(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> GetTransformIterator(
|
||||
const Slice& transformed);
|
||||
|
||||
private:
|
||||
friend class DynamicPrefixIterator;
|
||||
typedef std::set<const char*, Compare> Bucket;
|
||||
typedef std::unordered_map<Slice, std::shared_ptr<Bucket>> BucketMap;
|
||||
|
||||
@@ -148,6 +154,80 @@ class TransformRep : public MemTableRep {
|
||||
private:
|
||||
const ReadLock l_;
|
||||
};
|
||||
|
||||
|
||||
class DynamicPrefixIterator : public MemTableRep::Iterator {
|
||||
private:
|
||||
// the underlying memtable rep
|
||||
const TransformRep& memtable_rep_;
|
||||
// the result of a prefix seek
|
||||
std::unique_ptr<MemTableRep::Iterator> bucket_iterator_;
|
||||
|
||||
public:
|
||||
explicit DynamicPrefixIterator(const TransformRep& memtable_rep)
|
||||
: memtable_rep_(memtable_rep) {}
|
||||
|
||||
virtual ~DynamicPrefixIterator() { };
|
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const {
|
||||
return bucket_iterator_ && bucket_iterator_->Valid();
|
||||
}
|
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const {
|
||||
assert(Valid());
|
||||
return bucket_iterator_->key();
|
||||
}
|
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() {
|
||||
assert(Valid());
|
||||
bucket_iterator_->Next();
|
||||
}
|
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() {
|
||||
assert(Valid());
|
||||
bucket_iterator_->Prev();
|
||||
}
|
||||
|
||||
// Advance to the first entry with a key >= target within the
|
||||
// same bucket as target
|
||||
virtual void Seek(const char* target) {
|
||||
Slice prefix = memtable_rep_.transform_->Transform(
|
||||
memtable_rep_.UserKey(target));
|
||||
|
||||
ReadLock l(&memtable_rep_.rwlock_);
|
||||
auto bucket = memtable_rep_.buckets_.find(prefix);
|
||||
if (bucket == memtable_rep_.buckets_.end()) {
|
||||
bucket_iterator_.reset(nullptr);
|
||||
} else {
|
||||
bucket_iterator_.reset(
|
||||
new TransformIterator(bucket->second, memtable_rep_.GetLock(prefix)));
|
||||
bucket_iterator_->Seek(target);
|
||||
}
|
||||
}
|
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToFirst() {
|
||||
// Prefix iterator does not support total order.
|
||||
// We simply set the iterator to invalid state
|
||||
bucket_iterator_.reset(nullptr);
|
||||
}
|
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToLast() {
|
||||
// Prefix iterator does not support total order.
|
||||
// We simply set the iterator to invalid state
|
||||
bucket_iterator_.reset(nullptr);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class PrefixHashRep : public TransformRep {
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
// 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.
|
||||
//
|
||||
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/arena.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "port/port.h"
|
||||
#include "port/atomic_pointer.h"
|
||||
#include "util/murmurhash.h"
|
||||
#include "db/skiplist.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace {
|
||||
|
||||
class TransformRepNoLock : public MemTableRep {
|
||||
public:
|
||||
TransformRepNoLock(MemTableRep::KeyComparator& compare, Arena* arena,
|
||||
const SliceTransform* transform, size_t bucket_size);
|
||||
|
||||
virtual void Insert(const char* key) override;
|
||||
|
||||
virtual bool Contains(const char* key) const override;
|
||||
|
||||
virtual size_t ApproximateMemoryUsage() override;
|
||||
|
||||
virtual ~TransformRepNoLock();
|
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator() override;
|
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator(
|
||||
const Slice& slice) override;
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> GetTransformIterator(
|
||||
const Slice& transformed);
|
||||
|
||||
private:
|
||||
typedef SkipList<const char*, MemTableRep::KeyComparator&> Bucket;
|
||||
|
||||
size_t bucket_size_;
|
||||
|
||||
// Maps slices (which are transformed user keys) to buckets of keys sharing
|
||||
// the same transform.
|
||||
port::AtomicPointer* buckets_;
|
||||
|
||||
// The user-supplied transform whose domain is the user keys.
|
||||
const SliceTransform* transform_;
|
||||
|
||||
MemTableRep::KeyComparator& compare_;
|
||||
// immutable after construction
|
||||
Arena* const arena_;
|
||||
|
||||
inline size_t GetHash(const Slice& slice) const {
|
||||
return MurmurHash(slice.data(), slice.size(), 0) % bucket_size_;
|
||||
}
|
||||
inline Bucket* GetBucket(size_t i) const {
|
||||
return static_cast<Bucket*>(buckets_[i].Acquire_Load());
|
||||
}
|
||||
inline Bucket* GetBucket(const Slice& slice) const {
|
||||
return GetBucket(GetHash(slice));
|
||||
}
|
||||
// Get a bucket from buckets_. If the bucket hasn't been initialized yet,
|
||||
// initialize it before returning.
|
||||
Bucket* GetInitializedBucket(const Slice& transformed);
|
||||
|
||||
class Iterator : public MemTableRep::Iterator {
|
||||
public:
|
||||
explicit Iterator(Bucket* list, bool own_list = true)
|
||||
: list_(list),
|
||||
iter_(list),
|
||||
own_list_(own_list) {}
|
||||
|
||||
virtual ~Iterator() {
|
||||
// if we own the list, we should also delete it
|
||||
if (own_list_) {
|
||||
delete list_;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const {
|
||||
return iter_.Valid();
|
||||
}
|
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const {
|
||||
return iter_.key();
|
||||
}
|
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() {
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() {
|
||||
iter_.Prev();
|
||||
}
|
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
virtual void Seek(const char* target) {
|
||||
iter_.Seek(target);
|
||||
}
|
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToFirst() {
|
||||
iter_.SeekToFirst();
|
||||
}
|
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToLast() {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
private:
|
||||
Bucket* list_;
|
||||
Bucket::Iterator iter_;
|
||||
// here we track if we own list_. If we own it, we are also
|
||||
// responsible for it's cleaning. This is a poor man's shared_ptr
|
||||
bool own_list_;
|
||||
};
|
||||
|
||||
class EmptyIterator : public MemTableRep::Iterator {
|
||||
// This is used when there wasn't a bucket. It is cheaper than
|
||||
// instantiating an empty bucket over which to iterate.
|
||||
public:
|
||||
EmptyIterator() { }
|
||||
virtual bool Valid() const {
|
||||
return false;
|
||||
}
|
||||
virtual const char* key() const {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
virtual void Next() { }
|
||||
virtual void Prev() { }
|
||||
virtual void Seek(const char* target) { }
|
||||
virtual void SeekToFirst() { }
|
||||
virtual void SeekToLast() { }
|
||||
private:
|
||||
};
|
||||
|
||||
std::shared_ptr<EmptyIterator> empty_iterator_;
|
||||
};
|
||||
|
||||
class PrefixHashRepNoLock : public TransformRepNoLock {
|
||||
public:
|
||||
PrefixHashRepNoLock(MemTableRep::KeyComparator& compare, Arena* arena,
|
||||
const SliceTransform* transform, size_t bucket_size)
|
||||
: TransformRepNoLock(compare, arena, transform, bucket_size) { }
|
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetPrefixIterator(
|
||||
const Slice& prefix) override;
|
||||
};
|
||||
|
||||
TransformRepNoLock::TransformRepNoLock(MemTableRep::KeyComparator& compare,
|
||||
Arena* arena, const SliceTransform* transform, size_t bucket_size)
|
||||
: bucket_size_(bucket_size),
|
||||
transform_(transform),
|
||||
compare_(compare),
|
||||
arena_(arena),
|
||||
empty_iterator_(std::make_shared<EmptyIterator>()) {
|
||||
|
||||
buckets_ = new port::AtomicPointer[bucket_size];
|
||||
|
||||
for (size_t i = 0; i < bucket_size_; ++i) {
|
||||
buckets_[i].NoBarrier_Store(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TransformRepNoLock::~TransformRepNoLock() {
|
||||
delete[] buckets_;
|
||||
}
|
||||
|
||||
TransformRepNoLock::Bucket* TransformRepNoLock::GetInitializedBucket(
|
||||
const Slice& transformed) {
|
||||
size_t hash = GetHash(transformed);
|
||||
auto bucket = GetBucket(hash);
|
||||
if (bucket == nullptr) {
|
||||
auto addr = arena_->AllocateAligned(sizeof(Bucket));
|
||||
bucket = new (addr) Bucket(compare_, arena_);
|
||||
buckets_[hash].Release_Store(static_cast<void*>(bucket));
|
||||
}
|
||||
return bucket;
|
||||
}
|
||||
|
||||
void TransformRepNoLock::Insert(const char* key) {
|
||||
assert(!Contains(key));
|
||||
auto transformed = transform_->Transform(UserKey(key));
|
||||
auto bucket = GetInitializedBucket(transformed);
|
||||
bucket->Insert(key);
|
||||
}
|
||||
|
||||
bool TransformRepNoLock::Contains(const char* key) const {
|
||||
auto transformed = transform_->Transform(UserKey(key));
|
||||
auto bucket = GetBucket(transformed);
|
||||
if (bucket == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return bucket->Contains(key);
|
||||
}
|
||||
|
||||
size_t TransformRepNoLock::ApproximateMemoryUsage() {
|
||||
return sizeof(buckets_);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRepNoLock::GetIterator() {
|
||||
auto list = new Bucket(compare_, arena_);
|
||||
for (size_t i = 0; i < bucket_size_; ++i) {
|
||||
auto bucket = GetBucket(i);
|
||||
if (bucket != nullptr) {
|
||||
Bucket::Iterator itr(bucket);
|
||||
for (itr.SeekToFirst(); itr.Valid(); itr.Next()) {
|
||||
list->Insert(itr.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::make_shared<Iterator>(list);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRepNoLock::GetTransformIterator(
|
||||
const Slice& transformed) {
|
||||
auto bucket = GetBucket(transformed);
|
||||
if (bucket == nullptr) {
|
||||
return empty_iterator_;
|
||||
}
|
||||
return std::make_shared<Iterator>(bucket, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRepNoLock::GetIterator(
|
||||
const Slice& slice) {
|
||||
auto transformed = transform_->Transform(slice);
|
||||
return GetTransformIterator(transformed);
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
std::shared_ptr<MemTableRep> TransformRepNoLockFactory::CreateMemTableRep(
|
||||
MemTableRep::KeyComparator& compare, Arena* arena) {
|
||||
return std::make_shared<TransformRepNoLock>(compare, arena, transform_,
|
||||
bucket_count_);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep> PrefixHashRepNoLockFactory::CreateMemTableRep(
|
||||
MemTableRep::KeyComparator& compare, Arena* arena) {
|
||||
return std::make_shared<PrefixHashRepNoLock>(compare, arena, transform_,
|
||||
bucket_count_);
|
||||
}
|
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> PrefixHashRepNoLock::GetPrefixIterator(
|
||||
const Slice& prefix) {
|
||||
return TransformRepNoLock::GetTransformIterator(prefix);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -272,7 +272,7 @@ Status DBWithTTL::GetLiveFiles(std::vector<std::string>& vec, uint64_t* mfs,
|
||||
return db_->GetLiveFiles(vec, mfs, flush_memtable);
|
||||
}
|
||||
|
||||
SequenceNumber DBWithTTL::GetLatestSequenceNumber() {
|
||||
SequenceNumber DBWithTTL::GetLatestSequenceNumber() const {
|
||||
return db_->GetLatestSequenceNumber();
|
||||
}
|
||||
|
||||
@@ -280,8 +280,8 @@ Status DBWithTTL::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
return db_->GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
Status DBWithTTL::DeleteWalFiles(const VectorLogPtr& files){
|
||||
return db_->DeleteWalFiles(files);
|
||||
Status DBWithTTL::DeleteFile(std::string name) {
|
||||
return db_->DeleteFile(name);
|
||||
}
|
||||
|
||||
Status DBWithTTL::GetUpdatesSince(
|
||||
|
||||
+10
-7
@@ -78,9 +78,9 @@ class DBWithTTL : public StackableDB {
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files);
|
||||
|
||||
virtual Status DeleteWalFiles(const VectorLogPtr& files);
|
||||
virtual Status DeleteFile(std::string name);
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber();
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const;
|
||||
|
||||
virtual Status GetUpdatesSince(SequenceNumber seq_number,
|
||||
unique_ptr<TransactionLogIterator>* iter);
|
||||
@@ -236,12 +236,15 @@ class TtlCompactionFilterFactory : public CompactionFilterFactory {
|
||||
: ttl_(ttl),
|
||||
user_comp_filter_factory_(comp_filter_factory) { }
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter() {
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) {
|
||||
return std::unique_ptr<TtlCompactionFilter>(
|
||||
new TtlCompactionFilter(
|
||||
ttl_,
|
||||
nullptr,
|
||||
std::move(user_comp_filter_factory_->CreateCompactionFilter())));
|
||||
new TtlCompactionFilter(
|
||||
ttl_,
|
||||
nullptr,
|
||||
std::move(user_comp_filter_factory_->CreateCompactionFilter(context))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
|
||||
@@ -284,7 +284,8 @@ class TtlTest {
|
||||
}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter>
|
||||
CreateCompactionFilter() override {
|
||||
CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new TestFilter(kSampleSize_, kNewValue_));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user