mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b858da709a | ||
|
|
61f1baaedf | ||
|
|
0f78fad9f5 | ||
|
|
4deaa0d48b | ||
|
|
96be2c4ee0 | ||
|
|
5ef6bb8c37 | ||
|
|
09de7a3b6a | ||
|
|
3cc1af2062 | ||
|
|
79f4fd2b62 | ||
|
|
6acbe0fc45 | ||
|
|
f561b3a324 | ||
|
|
836534debd | ||
|
|
00124683de | ||
|
|
39ee47fbf4 | ||
|
|
8926b72751 | ||
|
|
bff718d81c | ||
|
|
2a52e1dcb6 | ||
|
|
1afdf28701 | ||
|
|
7a5f71d19a | ||
|
|
0c2a2dd5e8 | ||
|
|
778e179046 | ||
|
|
4985a9f73b | ||
|
|
bdf1085944 | ||
|
|
5679107b07 | ||
|
|
e673d5d26d | ||
|
|
3c35eda9bd | ||
|
|
1b69f1e584 | ||
|
|
4a8554d5bb | ||
|
|
c5de1b9391 | ||
|
|
a4913c5170 | ||
|
|
184343a061 | ||
|
|
7a6bd8e975 | ||
|
|
3bb9449906 | ||
|
|
e982b5a489 | ||
|
|
8ef328ee6a | ||
|
|
5cf7a00bda | ||
|
|
db1f0cddf3 | ||
|
|
d8c7c45ea0 | ||
|
|
d91b42ee27 | ||
|
|
ee522d0032 | ||
|
|
043573b24f | ||
|
|
d9f538e1a9 | ||
|
|
2b1fb5b01d | ||
|
|
d897d33bf1 | ||
|
|
ab8d2f6ab2 | ||
|
|
2df65c118c | ||
|
|
a8d807ee91 | ||
|
|
fb684da082 | ||
|
|
4c47d8f345 | ||
|
|
4b29651206 | ||
|
|
ef15b9d178 | ||
|
|
0e879c93de | ||
|
|
26541860d3 | ||
|
|
898f79372f | ||
|
|
c2e2460f8a | ||
|
|
760dd4750f | ||
|
|
87d0af15d8 | ||
|
|
d1aaaf718c | ||
|
|
c3c13db346 | ||
|
|
3827403c51 | ||
|
|
839f6db77e | ||
|
|
15ccd10c7f | ||
|
|
8a59ed9bc7 | ||
|
|
e1174306c5 | ||
|
|
446151cd20 | ||
|
|
8a48410f09 | ||
|
|
8c9411c637 | ||
|
|
accd3debbb | ||
|
|
4ca3c67bd3 | ||
|
|
73c0a33346 | ||
|
|
3102628873 | ||
|
|
8d58ecdc29 | ||
|
|
d98e56315d | ||
|
|
85cccc5092 | ||
|
|
c605e12479 | ||
|
|
ff1a0801fc | ||
|
|
a8d3aa2c26 | ||
|
|
988c20b9f7 | ||
|
|
3c4efc4462 | ||
|
|
05e8854085 | ||
|
|
37e97b1297 | ||
|
|
d786b25e2d | ||
|
|
41cb922b34 | ||
|
|
49fbd5531b | ||
|
|
fb96ec1686 | ||
|
|
ae558b5c42 | ||
|
|
06d3487b3f | ||
|
|
958b9c80e1 | ||
|
|
eb6d139666 | ||
|
|
344e832f55 | ||
|
|
3cb7bf8170 | ||
|
|
b4243e5a3d | ||
|
|
b1ff9ac9c5 | ||
|
|
e0b60923ee | ||
|
|
1255dcd446 | ||
|
|
a29fc171a6 | ||
|
|
7c6c3c0ff4 | ||
|
|
9b81d3c406 | ||
|
|
013e9ebbf1 | ||
|
|
dae7379050 | ||
|
|
94d86b25a9 | ||
|
|
db8b404120 | ||
|
|
6936f9bb8e | ||
|
|
6594fef7ef | ||
|
|
e21ba94a69 | ||
|
|
f51b375062 | ||
|
|
7730587120 | ||
|
|
adb4e4509b | ||
|
|
faa32a72a6 | ||
|
|
574b76f710 | ||
|
|
9b3134f5ca | ||
|
|
26f68d3939 | ||
|
|
6203e4ccc0 | ||
|
|
0e40185a7d | ||
|
|
a77bc3d14c | ||
|
|
2b9a360c8b | ||
|
|
d0c46a65bb | ||
|
|
ca789a10cc | ||
|
|
6763110867 | ||
|
|
d815082159 | ||
|
|
e937d47180 | ||
|
|
645ff8f231 | ||
|
|
3b51605b8d | ||
|
|
8e9c781ae5 | ||
|
|
7fdd5f5b33 | ||
|
|
63f216ee0a | ||
|
|
ecd8db0200 | ||
|
|
a8bf8fe504 | ||
|
|
9b70529c86 | ||
|
|
27c15fb67e | ||
|
|
38d54832f7 | ||
|
|
91660e048f | ||
|
|
d0798f67f4 | ||
|
|
ad96563b79 | ||
|
|
2adddeefcb | ||
|
|
a6f4275403 | ||
|
|
48abc06049 | ||
|
|
b1bea58457 | ||
|
|
487168cdcf | ||
|
|
72d14eafd3 | ||
|
|
02c459805b | ||
|
|
7b9db9c98e | ||
|
|
f04cc368f7 | ||
|
|
a78fb5e8bc | ||
|
|
5a8c8845a9 | ||
|
|
e93dc3c0b8 |
No files matched your search
@@ -18,7 +18,7 @@ $(shell ./build_detect_platform build_config.mk)
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include build_config.mk
|
||||
|
||||
WARNING_FLAGS = -Wall -Werror -Wno-unused-parameter -Wno-sign-compare
|
||||
WARNING_FLAGS = -Wall -Werror
|
||||
CFLAGS += -g $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += -g $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -std=gnu++0x
|
||||
|
||||
@@ -53,23 +53,25 @@ TESTS = \
|
||||
memenv_test \
|
||||
skiplist_test \
|
||||
table_test \
|
||||
ttl_test \
|
||||
block_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
reduce_levels_test \
|
||||
write_batch_test \
|
||||
auto_roll_logger_test \
|
||||
filelock_test
|
||||
|
||||
filelock_test \
|
||||
merge_test \
|
||||
redis_test \
|
||||
manual_compaction_test
|
||||
|
||||
TOOLS = \
|
||||
manifest_dump \
|
||||
sst_dump \
|
||||
db_stress \
|
||||
ldb \
|
||||
db_repl_stress
|
||||
|
||||
PROGRAMS = db_bench $(TESTS) $(TOOLS)
|
||||
PROGRAMS = db_bench signal_test $(TESTS) $(TOOLS)
|
||||
BENCHMARKS = db_bench_sqlite3 db_bench_tree_db
|
||||
|
||||
LIBRARY = libleveldb.a
|
||||
@@ -116,6 +118,14 @@ check: all $(PROGRAMS) $(TESTS) $(TOOLS) ldb_tests
|
||||
ldb_tests: all $(PROGRAMS) $(TOOLS)
|
||||
python tools/ldb_test.py
|
||||
|
||||
crash_test: blackbox_crash_test whitebox_crash_test
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest2.py
|
||||
|
||||
valgrind_check: all $(PROGRAMS) $(TESTS)
|
||||
echo TESTS THAT HAVE VALGRIND ERRORS > $(VALGRIND_DIR)/valgrind_failed_tests; \
|
||||
echo TIMES in seconds TAKEN BY TESTS ON VALGRIND > $(VALGRIND_DIR)/valgrind_tests_times; \
|
||||
@@ -130,7 +140,7 @@ valgrind_check: all $(PROGRAMS) $(TESTS)
|
||||
done
|
||||
|
||||
clean:
|
||||
-rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) $(THRIFTSERVER) */*.o */*/*.o ios-x86/*/*.o ios-arm/*/*.o build_config.mk
|
||||
-rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) $(THRIFTSERVER) */*.o */*/*.o ios-x86/*/*.o ios-arm/*/*.o build_config.mk */*.d */*/*.d
|
||||
-rm -rf ios-x86/* ios-arm/*
|
||||
|
||||
$(LIBRARY): $(LIBOBJECTS)
|
||||
@@ -153,6 +163,9 @@ db_bench_sqlite3: doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
db_bench_tree_db: doc/bench/db_bench_tree_db.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) doc/bench/db_bench_tree_db.o $(LIBOBJECTS) $(TESTUTIL) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) -lkyotocabinet
|
||||
|
||||
signal_test: util/signal_test.o $(LIBOBJECTS)
|
||||
$(CXX) util/signal_test.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
@@ -168,6 +181,12 @@ cache_test: util/cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
coding_test: util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
stringappend_test: utilities/merge_operators/string_append/stringappend_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/merge_operators/string_append/stringappend_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
redis_test: utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
histogram_test: util/histogram_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/histogram_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o$@ $(LDFLAGS)
|
||||
|
||||
@@ -180,6 +199,9 @@ crc32c_test: util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_test: db/db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/db_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
@@ -216,6 +238,9 @@ reduce_levels_test: tools/reduce_levels_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
write_batch_test: db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
merge_test: db/merge_test.o $(LIBOBJECTS)
|
||||
$(CXX) db/merge_test.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
$(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
rm -f $@
|
||||
$(AR) -rs $@ $(MEMENVOBJECTS)
|
||||
@@ -223,15 +248,15 @@ $(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
memenv_test : helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS)
|
||||
$(CXX) helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
manual_compaction_test: util/manual_compaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/manual_compaction_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
leveldb_shell: tools/shell/ShellContext.o tools/shell/ShellState.o tools/shell/LeveldbShell.o tools/shell/DBClientProxy.o tools/shell/ShellContext.h tools/shell/ShellState.h tools/shell/DBClientProxy.h $(LIBOBJECTS)
|
||||
$(CXX) tools/shell/ShellContext.o tools/shell/ShellState.o tools/shell/LeveldbShell.o tools/shell/DBClientProxy.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
DBClientProxy_test: tools/shell/test/DBClientProxyTest.o tools/shell/DBClientProxy.o $(LIBRARY)
|
||||
DBClientProxy_test: tools/shell/test/DBClientProxyTest.o tools/shell/DBClientProxy.o $(LIBRARY)
|
||||
$(CXX) tools/shell/test/DBClientProxyTest.o tools/shell/DBClientProxy.o $(LIBRARY) $(EXEC_LDFLAGS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
manifest_dump: tools/manifest_dump.o $(LIBOBJECTS)
|
||||
$(CXX) tools/manifest_dump.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
filelock_test: util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
@@ -244,6 +269,11 @@ sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
ldb: tools/ldb.o $(LIBOBJECTS)
|
||||
$(CXX) tools/ldb.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
.PHONY: tags
|
||||
tags:
|
||||
ctags `find . -name '*.cc'` `find . -name '*.h'`
|
||||
cscope -b `find . -name '*.cc'` `find . -name '*.h'`
|
||||
|
||||
ifeq ($(PLATFORM), IOS)
|
||||
# For iOS, create universal object files to be used on both the simulator and
|
||||
# a device.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
* Detailed instructions on how to compile using fbcode and jemalloc
|
||||
|
||||
* Latest release is 1.5.7.fb
|
||||
* Latest release is 1.5.9.fb
|
||||
+15
-12
@@ -31,9 +31,9 @@ fi
|
||||
# Default to fbcode gcc on internal fb machines
|
||||
if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
source fbcode.gcc471.sh
|
||||
source ./fbcode.gcc471.sh
|
||||
else
|
||||
source fbcode.clang31.sh
|
||||
source ./fbcode.clang31.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -64,6 +64,9 @@ PLATFORM_SHARED_LDFLAGS="${EXEC_LDFLAGS_SHARED} -shared -Wl,-soname -Wl,"
|
||||
PLATFORM_SHARED_CFLAGS="-fPIC"
|
||||
PLATFORM_SHARED_VERSIONED=true
|
||||
|
||||
# generic port files (working on all platform by #ifdef) go directly in /port
|
||||
GENERIC_PORT_FILES=`find port -name '*.cc' | tr "\n" " "`
|
||||
|
||||
# On GCC, we pick libc's memcmp over GCC's memcmp via -fno-builtin-memcmp
|
||||
case "$TARGET_OS" in
|
||||
Darwin)
|
||||
@@ -71,7 +74,7 @@ case "$TARGET_OS" in
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -DOS_MACOSX"
|
||||
PLATFORM_SHARED_EXT=dylib
|
||||
PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name "
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/darwin/darwin_specific.cc
|
||||
;;
|
||||
Linux)
|
||||
PLATFORM=OS_LINUX
|
||||
@@ -80,43 +83,43 @@ case "$TARGET_OS" in
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp"
|
||||
fi
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/linux/linux_specific.cc
|
||||
;;
|
||||
SunOS)
|
||||
PLATFORM=OS_SOLARIS
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_SOLARIS"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/sunos/sunos_specific.cc
|
||||
;;
|
||||
FreeBSD)
|
||||
PLATFORM=OS_FREEBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/freebsd/freebsd_specific.cc
|
||||
;;
|
||||
NetBSD)
|
||||
PLATFORM=OS_NETBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_NETBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lgcc_s"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/netbsd/netbsd_specific.cc
|
||||
;;
|
||||
OpenBSD)
|
||||
PLATFORM=OS_OPENBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_OPENBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -pthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/openbsd/openbsd_specific.cc
|
||||
;;
|
||||
DragonFly)
|
||||
PLATFORM=OS_DRAGONFLYBSD
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_DRAGONFLYBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/dragonfly/dragonfly_specific.cc
|
||||
;;
|
||||
OS_ANDROID_CROSSCOMPILE)
|
||||
PLATFORM=OS_ANDROID
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS " # All pthread features are in the Android C library
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# PORT_FILES=port/android/android.cc
|
||||
CROSS_COMPILE=true
|
||||
;;
|
||||
*)
|
||||
@@ -130,7 +133,7 @@ esac
|
||||
# except for the test and benchmark files. By default, find will output a list
|
||||
# of all files matching either rule, so we need to append -print to make the
|
||||
# prune take effect.
|
||||
DIRS="util db table"
|
||||
DIRS="util db table utilities"
|
||||
if test "$USE_THRIFT"; then
|
||||
DIRS="$DIRS thrift/server_utils.cpp thrift/gen-cpp "
|
||||
THRIFTSERVER=leveldb_server
|
||||
@@ -149,7 +152,7 @@ set +f # re-enable globbing
|
||||
|
||||
# The sources consist of the portable files, plus the platform-specific port
|
||||
# file.
|
||||
echo "SOURCES=$PORTABLE_FILES $PORT_FILE" >> $OUTPUT
|
||||
echo "SOURCES=$PORTABLE_FILES $GENERIC_PORT_FILES $PORT_FILES" >> $OUTPUT
|
||||
echo "SOURCESCPP=$PORTABLE_CPP" >> $OUTPUT
|
||||
echo "MEMENV_SOURCES=helpers/memenv/memenv.cc" >> $OUTPUT
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+56
-11
@@ -6,17 +6,20 @@
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Status BuildTable(const std::string& dbname,
|
||||
Env* env,
|
||||
const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
TableCache* table_cache,
|
||||
Iterator* iter,
|
||||
FileMetaData* meta,
|
||||
@@ -38,7 +41,7 @@ Status BuildTable(const std::string& dbname,
|
||||
std::string fname = TableFileName(dbname, meta->number);
|
||||
if (iter->Valid()) {
|
||||
unique_ptr<WritableFile> file;
|
||||
s = env->NewWritableFile(fname, &file);
|
||||
s = env->NewWritableFile(fname, &file, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -48,36 +51,75 @@ Status BuildTable(const std::string& dbname,
|
||||
Slice key = iter->key();
|
||||
meta->smallest.DecodeFrom(key);
|
||||
|
||||
MergeHelper merge(user_comparator, options.merge_operator,
|
||||
options.info_log.get(),
|
||||
true /* internal key corruption is not ok */);
|
||||
|
||||
if (purge) {
|
||||
ParsedInternalKey ikey;
|
||||
// Ugly walkaround to avoid compiler error for release build
|
||||
// TODO: find a clean way to treat in memory key corruption
|
||||
ikey.type = kTypeValue;
|
||||
ParsedInternalKey prev_ikey;
|
||||
std::string prev_value;
|
||||
std::string prev_key;
|
||||
|
||||
// store first key-value
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(prev_ikey.sequence >= earliest_seqno_in_memtable);
|
||||
// Ugly walkaround to avoid compiler error for release build
|
||||
// TODO: find a clean way to treat in memory key corruption
|
||||
auto ok __attribute__((unused)) = ParseInternalKey(key, &ikey);
|
||||
// in-memory key corruption is not ok;
|
||||
assert(ok);
|
||||
|
||||
for (iter->Next(); iter->Valid(); iter->Next()) {
|
||||
if (ikey.type == kTypeMerge) {
|
||||
// merge values if the first entry is of merge type
|
||||
merge.MergeUntil(iter, 0 /* don't worry about snapshot */);
|
||||
prev_key.assign(merge.key().data(), merge.key().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
prev_value.assign(merge.value().data(), merge.value().size());
|
||||
} else {
|
||||
// store first key-value
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
assert(prev_ikey.sequence >= earliest_seqno_in_memtable);
|
||||
iter->Next();
|
||||
}
|
||||
|
||||
while (iter->Valid()) {
|
||||
bool iterator_at_next = false;
|
||||
ParsedInternalKey this_ikey;
|
||||
Slice key = iter->key();
|
||||
ParseInternalKey(key, &this_ikey);
|
||||
ok = ParseInternalKey(key, &this_ikey);
|
||||
assert(ok);
|
||||
assert(this_ikey.sequence >= earliest_seqno_in_memtable);
|
||||
|
||||
if (user_comparator->Compare(prev_ikey.user_key, this_ikey.user_key)) {
|
||||
// This key is different from previous key.
|
||||
// Output prev key and remember current key
|
||||
builder->Add(Slice(prev_key), Slice(prev_value));
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
if (this_ikey.type == kTypeMerge) {
|
||||
merge.MergeUntil(iter, 0 /* don't worry about snapshot */);
|
||||
iterator_at_next = true;
|
||||
prev_key.assign(merge.key().data(), merge.key().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
prev_value.assign(merge.value().data(), merge.value().size());
|
||||
} else {
|
||||
prev_key.assign(key.data(), key.size());
|
||||
prev_value.assign(iter->value().data(), iter->value().size());
|
||||
ok = ParseInternalKey(Slice(prev_key), &prev_ikey);
|
||||
assert(ok);
|
||||
}
|
||||
} else {
|
||||
// seqno within the same key are in decreasing order
|
||||
assert(this_ikey.sequence < prev_ikey.sequence);
|
||||
// This key is an earlier version of the same key in prev_key.
|
||||
// Skip current key.
|
||||
}
|
||||
|
||||
if (!iterator_at_next) iter->Next();
|
||||
}
|
||||
// output last key
|
||||
builder->Add(Slice(prev_key), Slice(prev_value));
|
||||
@@ -106,8 +148,10 @@ Status BuildTable(const std::string& dbname,
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !options.disableDataSync) {
|
||||
if (options.use_fsync) {
|
||||
StopWatch sw(env, options.statistics, TABLE_SYNC_MICROS);
|
||||
s = file->Fsync();
|
||||
} else {
|
||||
StopWatch sw(env, options.statistics, TABLE_SYNC_MICROS);
|
||||
s = file->Sync();
|
||||
}
|
||||
}
|
||||
@@ -118,6 +162,7 @@ Status BuildTable(const std::string& dbname,
|
||||
if (s.ok()) {
|
||||
// Verify that the table is usable
|
||||
Iterator* it = table_cache->NewIterator(ReadOptions(),
|
||||
soptions,
|
||||
meta->number,
|
||||
meta->file_size);
|
||||
s = it->status();
|
||||
|
||||
@@ -15,6 +15,7 @@ struct Options;
|
||||
struct FileMetaData;
|
||||
|
||||
class Env;
|
||||
class EnvOptions;
|
||||
class Iterator;
|
||||
class TableCache;
|
||||
class VersionEdit;
|
||||
@@ -27,6 +28,7 @@ class VersionEdit;
|
||||
extern Status BuildTable(const std::string& dbname,
|
||||
Env* env,
|
||||
const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
TableCache* table_cache,
|
||||
Iterator* iter,
|
||||
FileMetaData* meta,
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#include <sys/types.h>
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "leveldb/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"
|
||||
@@ -40,6 +40,7 @@ class CorruptionTest {
|
||||
|
||||
db_ = nullptr;
|
||||
options_.create_if_missing = true;
|
||||
options_.block_size_deviation = 0; // make unit test pass for now
|
||||
Reopen();
|
||||
options_.create_if_missing = false;
|
||||
}
|
||||
@@ -55,6 +56,7 @@ class CorruptionTest {
|
||||
Options opt = (options ? *options : options_);
|
||||
opt.env = &env_;
|
||||
opt.block_cache = tiny_cache_;
|
||||
opt.block_size_deviation = 0;
|
||||
return DB::Open(opt, dbname_, &db_);
|
||||
}
|
||||
|
||||
|
||||
+670
-115
File diff suppressed because it is too large.
Load diff
@@ -17,12 +17,14 @@ namespace leveldb {
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
MutexLock l(&mutex_);
|
||||
disable_delete_obsolete_files_ = true;
|
||||
Log(options_.info_log, "File Deletions Disabled");
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::EnableFileDeletions() {
|
||||
MutexLock l(&mutex_);
|
||||
disable_delete_obsolete_files_ = false;
|
||||
Log(options_.info_log, "File Deletions Enabled");
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
+519
-176
File diff suppressed because it is too large.
Load diff
+52
-10
@@ -5,8 +5,10 @@
|
||||
#ifndef STORAGE_LEVELDB_DB_DB_IMPL_H_
|
||||
#define STORAGE_LEVELDB_DB_DB_IMPL_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/log_file.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -36,11 +38,16 @@ class DBImpl : public DB {
|
||||
|
||||
// Implementations of the DB interface
|
||||
virtual Status Put(const WriteOptions&, const Slice& key, const Slice& value);
|
||||
virtual Status Merge(const WriteOptions&, const Slice& key,
|
||||
const Slice& value);
|
||||
virtual Status Delete(const WriteOptions&, const Slice& key);
|
||||
virtual Status Write(const WriteOptions& options, WriteBatch* updates);
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key,
|
||||
std::string* value);
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values);
|
||||
virtual Iterator* NewIterator(const ReadOptions&);
|
||||
virtual const Snapshot* GetSnapshot();
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot);
|
||||
@@ -61,7 +68,7 @@ class DBImpl : public DB {
|
||||
|
||||
// Extra methods (for testing) that are not in the public DB interface
|
||||
|
||||
// Compact any files in the named level that overlap [*begin,*end]
|
||||
// Compact any files in the named level that overlap [*begin, *end]
|
||||
void TEST_CompactRange(int level, const Slice* begin, const Slice* end);
|
||||
|
||||
// Force current memtable contents to be compacted.
|
||||
@@ -87,6 +94,10 @@ class DBImpl : public DB {
|
||||
|
||||
// Return the current manifest file no.
|
||||
uint64_t TEST_Current_Manifest_FileNo();
|
||||
|
||||
// Trigger's a background call for testing.
|
||||
void TEST_PurgeObsoleteteWAL();
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
@@ -141,7 +152,7 @@ class DBImpl : public DB {
|
||||
// for the entire period. The second method WriteLevel0Table supports
|
||||
// concurrent flush memtables to storage.
|
||||
Status WriteLevel0TableForRecovery(MemTable* mem, VersionEdit* edit);
|
||||
Status WriteLevel0Table(MemTable* mem, VersionEdit* edit,
|
||||
Status WriteLevel0Table(std::vector<MemTable*> &mems, VersionEdit* edit,
|
||||
uint64_t* filenumber);
|
||||
|
||||
Status MakeRoomForWrite(bool force /* compact even if there is room? */);
|
||||
@@ -194,12 +205,18 @@ class DBImpl : public DB {
|
||||
Status FindProbableWALFiles(std::vector<LogFile>* const allLogs,
|
||||
std::vector<LogFile>* const result,
|
||||
const SequenceNumber target);
|
||||
|
||||
// return true if
|
||||
bool CheckFileExistsAndEmpty(const LogFile& file);
|
||||
|
||||
Status ReadFirstRecord(const LogFile& file, WriteBatch* const result);
|
||||
|
||||
|
||||
Status ReadFirstLine(const std::string& fname, WriteBatch* const batch);
|
||||
|
||||
void PrintStatistics();
|
||||
|
||||
// dump leveldb.stats to LOG
|
||||
void MaybeDumpStats();
|
||||
|
||||
// Constant after construction
|
||||
const InternalFilterPolicy internal_filter_policy_;
|
||||
bool owns_info_log_;
|
||||
@@ -223,7 +240,7 @@ class DBImpl : public DB {
|
||||
|
||||
// Queue of writers.
|
||||
std::deque<Writer*> writers_;
|
||||
WriteBatch* tmp_batch_;
|
||||
WriteBatch tmp_batch_;
|
||||
|
||||
SnapshotList snapshots_;
|
||||
|
||||
@@ -251,7 +268,7 @@ class DBImpl : public DB {
|
||||
// Have we encountered a background error in paranoid mode?
|
||||
Status bg_error_;
|
||||
|
||||
StatsLogger* logger_;
|
||||
std::unique_ptr<StatsLogger> logger_;
|
||||
|
||||
int64_t volatile last_log_ts;
|
||||
|
||||
@@ -261,6 +278,12 @@ class DBImpl : public DB {
|
||||
// last time when DeleteObsoleteFiles was invoked
|
||||
uint64_t delete_obsolete_files_last_run_;
|
||||
|
||||
// last time when PurgeObsoleteWALFiles ran.
|
||||
uint64_t purge_wal_files_last_run_;
|
||||
|
||||
// last time stats were dumped to LOG
|
||||
std::atomic<uint64_t> last_stats_dump_time_microsec_;
|
||||
|
||||
// These count the number of microseconds for which MakeRoomForWrite stalls.
|
||||
uint64_t stall_level0_slowdown_;
|
||||
uint64_t stall_memtable_compaction_;
|
||||
@@ -315,7 +338,20 @@ class DBImpl : public DB {
|
||||
}
|
||||
};
|
||||
|
||||
CompactionStats* stats_;
|
||||
std::vector<CompactionStats> stats_;
|
||||
|
||||
// Used to compute per-interval statistics
|
||||
struct StatsSnapshot {
|
||||
uint64_t bytes_read_;
|
||||
uint64_t bytes_written_;
|
||||
uint64_t bytes_new_;
|
||||
double seconds_up_;
|
||||
|
||||
StatsSnapshot() : bytes_read_(0), bytes_written_(0),
|
||||
bytes_new_(0), seconds_up_(0) {}
|
||||
};
|
||||
|
||||
StatsSnapshot last_stats_;
|
||||
|
||||
static const int KEEP_LOG_FILE_NUM = 1000;
|
||||
std::string db_absolute_path_;
|
||||
@@ -327,6 +363,9 @@ class DBImpl : public DB {
|
||||
// Used by transaction log iterator.
|
||||
SequenceNumber last_flushed_sequence_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions storage_options_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
@@ -334,9 +373,12 @@ class DBImpl : public DB {
|
||||
// dump the delayed_writes_ to the log file and reset counter.
|
||||
void DelayLoggingAndReset();
|
||||
|
||||
// find the earliest snapshot where seqno is visible
|
||||
inline SequenceNumber findEarliestVisibleSnapshot(SequenceNumber in,
|
||||
std::vector<SequenceNumber>& snapshots);
|
||||
// Return the earliest snapshot where seqno is visible.
|
||||
// Store the snapshot right before that, if any, in prev_snapshot
|
||||
inline SequenceNumber findEarliestVisibleSnapshot(
|
||||
SequenceNumber in,
|
||||
std::vector<SequenceNumber>& snapshots,
|
||||
SequenceNumber* prev_snapshot);
|
||||
};
|
||||
|
||||
// Sanitize db options. The caller should delete result.info_log if
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/status.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "leveldb/table_builder.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"
|
||||
@@ -54,10 +54,10 @@ Status DBImplReadOnly::Get(const ReadOptions& options,
|
||||
Version* current = versions_->current();
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
LookupKey lkey(key, snapshot);
|
||||
if (mem->Get(lkey, value, &s)) {
|
||||
if (mem->Get(lkey, value, &s, options_)) {
|
||||
} else {
|
||||
Version::GetStats stats;
|
||||
s = current->Get(options, lkey, value, &stats);
|
||||
current->Get(options, lkey, value, &s, &stats, options_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& options) {
|
||||
SequenceNumber latest_snapshot;
|
||||
Iterator* internal_iter = NewInternalIterator(options, &latest_snapshot);
|
||||
return NewDBIterator(
|
||||
&dbname_, env_, user_comparator(), internal_iter,
|
||||
&dbname_, env_, options_, user_comparator(),internal_iter,
|
||||
(options.snapshot != nullptr
|
||||
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
|
||||
: latest_snapshot));
|
||||
|
||||
@@ -37,6 +37,10 @@ public:
|
||||
virtual Status Put(const WriteOptions&, const Slice& key, const Slice& value) {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status Merge(const WriteOptions&, const Slice& key,
|
||||
const Slice& value) {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status Delete(const WriteOptions&, const Slice& key) {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
+148
-30
@@ -3,11 +3,14 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "port/port.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -36,6 +39,7 @@ namespace {
|
||||
// numbers, deletion markers, overwrites, etc.
|
||||
class DBIter: public Iterator {
|
||||
public:
|
||||
// The following is grossly complicated. TODO: clean it up
|
||||
// Which direction is the iterator currently moving?
|
||||
// (1) When moving forward, the internal iterator is positioned at
|
||||
// the exact entry that yields this->key(), this->value()
|
||||
@@ -46,27 +50,34 @@ class DBIter: public Iterator {
|
||||
kReverse
|
||||
};
|
||||
|
||||
DBIter(const std::string* dbname, Env* env,
|
||||
DBIter(const std::string* dbname, Env* env, const Options& options,
|
||||
const Comparator* cmp, Iterator* iter, SequenceNumber s)
|
||||
: dbname_(dbname),
|
||||
env_(env),
|
||||
logger_(options.info_log),
|
||||
user_comparator_(cmp),
|
||||
user_merge_operator_(options.merge_operator),
|
||||
iter_(iter),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
valid_(false) {
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(options.statistics) {
|
||||
RecordTick(statistics_, NO_ITERATORS, 1);
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
RecordTick(statistics_, NO_ITERATORS, -1);
|
||||
delete iter_;
|
||||
}
|
||||
virtual bool Valid() const { return valid_; }
|
||||
virtual Slice key() const {
|
||||
assert(valid_);
|
||||
return (direction_ == kForward) ? ExtractUserKey(iter_->key()) : saved_key_;
|
||||
return saved_key_;
|
||||
}
|
||||
virtual Slice value() const {
|
||||
assert(valid_);
|
||||
return (direction_ == kForward) ? iter_->value() : saved_value_;
|
||||
return (direction_ == kForward && !current_entry_is_merged_) ?
|
||||
iter_->value() : saved_value_;
|
||||
}
|
||||
virtual Status status() const {
|
||||
if (status_.ok()) {
|
||||
@@ -83,9 +94,10 @@ class DBIter: public Iterator {
|
||||
virtual void SeekToLast();
|
||||
|
||||
private:
|
||||
void FindNextUserEntry(bool skipping, std::string* skip);
|
||||
void FindNextUserEntry(bool skipping);
|
||||
void FindPrevUserEntry();
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
void MergeValuesNewToOld();
|
||||
|
||||
inline void SaveKey(const Slice& k, std::string* dst) {
|
||||
dst->assign(k.data(), k.size());
|
||||
@@ -102,15 +114,20 @@ class DBIter: public Iterator {
|
||||
|
||||
const std::string* const dbname_;
|
||||
Env* const env_;
|
||||
shared_ptr<Logger> logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
const MergeOperator* const user_merge_operator_;
|
||||
Iterator* const iter_;
|
||||
SequenceNumber const sequence_;
|
||||
|
||||
Status status_;
|
||||
std::string saved_key_; // == current key when direction_==kReverse
|
||||
std::string saved_value_; // == current raw value when direction_==kReverse
|
||||
std::string skip_key_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
@@ -120,6 +137,8 @@ class DBIter: public Iterator {
|
||||
inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
if (!ParseInternalKey(iter_->key(), ikey)) {
|
||||
status_ = Status::Corruption("corrupted internal key in DBIter");
|
||||
Log(logger_, "corrupted internal key in DBIter: %s",
|
||||
iter_->key().ToString(true).c_str());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -146,47 +165,136 @@ void DBIter::Next() {
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily use saved_key_ as storage for key to skip.
|
||||
std::string* skip = &saved_key_;
|
||||
SaveKey(ExtractUserKey(iter_->key()), skip);
|
||||
FindNextUserEntry(true, skip);
|
||||
// If the current value is merged, we might already hit end of iter_
|
||||
if (!iter_->Valid()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
FindNextUserEntry(true /* skipping the current user key */);
|
||||
}
|
||||
|
||||
void DBIter::FindNextUserEntry(bool skipping, std::string* skip) {
|
||||
|
||||
// PRE: saved_key_ has the current user key if skipping
|
||||
// POST: saved_key_ should have the next user key if valid_,
|
||||
// if the current entry is a result of merge
|
||||
// current_entry_is_merged_ => true
|
||||
// saved_value_ => the merged value
|
||||
//
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker
|
||||
void DBIter::FindNextUserEntry(bool skipping) {
|
||||
// Loop until we hit an acceptable entry to yield
|
||||
assert(iter_->Valid());
|
||||
assert(direction_ == kForward);
|
||||
current_entry_is_merged_ = false;
|
||||
do {
|
||||
ParsedInternalKey ikey;
|
||||
if (ParseKey(&ikey) && ikey.sequence <= sequence_) {
|
||||
switch (ikey.type) {
|
||||
case kTypeDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
SaveKey(ikey.user_key, skip);
|
||||
skipping = true;
|
||||
break;
|
||||
case kTypeValue:
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, *skip) <= 0) {
|
||||
// Entry hidden
|
||||
} else {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_) <= 0) {
|
||||
// skip this entry
|
||||
} else {
|
||||
skipping = false;
|
||||
switch (ikey.type) {
|
||||
case kTypeDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
SaveKey(ikey.user_key, &saved_key_);
|
||||
skipping = true;
|
||||
break;
|
||||
case kTypeValue:
|
||||
valid_ = true;
|
||||
saved_key_.clear();
|
||||
SaveKey(ikey.user_key, &saved_key_);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
SaveKey(ikey.user_key, &saved_key_);
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
// Go to a different state machine
|
||||
MergeValuesNewToOld();
|
||||
// TODO: what if !iter_->Valid()
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
iter_->Next();
|
||||
} while (iter_->Valid());
|
||||
saved_key_.clear();
|
||||
valid_ = false;
|
||||
}
|
||||
|
||||
// Merge values of the same user key starting from the current iter_ position
|
||||
// Scan from the newer entries to older entries.
|
||||
// PRE: iter_->key() points to the first merge type entry
|
||||
// saved_key_ stores the user key
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
void DBIter::MergeValuesNewToOld() {
|
||||
|
||||
const Slice value = iter_->value();
|
||||
std::string operand(value.data(), value.size());
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
if (!ParseKey(&ikey)) {
|
||||
// skip corrupted key
|
||||
continue;
|
||||
}
|
||||
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_) != 0) {
|
||||
// hit the next user key, stop right here
|
||||
break;
|
||||
}
|
||||
|
||||
if (kTypeDeletion == ikey.type) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
break;
|
||||
}
|
||||
|
||||
if (kTypeValue == ikey.type) {
|
||||
// hit a put, merge the put value with operand and store it in the
|
||||
// final result saved_value_. We are done!
|
||||
const Slice value = iter_->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, Slice(operand),
|
||||
&saved_value_, logger_.get());
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeMerge == ikey.type) {
|
||||
// hit a merge, merge the value with operand and continue.
|
||||
// saved_value_ is used as a scratch area. The result is put
|
||||
// back in operand
|
||||
const Slice value = iter_->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, operand,
|
||||
&saved_value_, logger_.get());
|
||||
swap(saved_value_, operand);
|
||||
}
|
||||
}
|
||||
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge opexrator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
user_merge_operator_->Merge(ikey.user_key, nullptr, operand,
|
||||
&saved_value_, logger_.get());
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
|
||||
// TODO: support backward iteration
|
||||
// Throw an exception now if merge_operator is provided
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "Prev not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::Prev backward iteration not supported"
|
||||
" if merge_operator is provided");
|
||||
}
|
||||
|
||||
if (direction_ == kForward) { // Switch directions?
|
||||
// iter_ is pointing at the current entry. Scan backwards until
|
||||
// the key changes so we can use the normal reverse scanning code.
|
||||
@@ -261,7 +369,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
&saved_key_, ParsedInternalKey(target, sequence_, kValueTypeForSeek));
|
||||
iter_->Seek(saved_key_);
|
||||
if (iter_->Valid()) {
|
||||
FindNextUserEntry(false, &saved_key_ /* temporary storage */);
|
||||
FindNextUserEntry(false /*not skipping */);
|
||||
} else {
|
||||
valid_ = false;
|
||||
}
|
||||
@@ -272,13 +380,21 @@ void DBIter::SeekToFirst() {
|
||||
ClearSavedValue();
|
||||
iter_->SeekToFirst();
|
||||
if (iter_->Valid()) {
|
||||
FindNextUserEntry(false, &saved_key_ /* temporary storage */);
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
} else {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
// TODO: support backward iteration
|
||||
// throw an exception for now if merge_operator is provided
|
||||
if (user_merge_operator_) {
|
||||
Log(logger_, "SeekToLast not supported yet if merge_operator is provided");
|
||||
throw std::logic_error("DBIter::SeekToLast: backward iteration not"
|
||||
" supported if merge_operator is provided");
|
||||
}
|
||||
|
||||
direction_ = kReverse;
|
||||
ClearSavedValue();
|
||||
iter_->SeekToLast();
|
||||
@@ -290,10 +406,12 @@ void DBIter::SeekToLast() {
|
||||
Iterator* NewDBIterator(
|
||||
const std::string* dbname,
|
||||
Env* env,
|
||||
const Comparator* user_key_comparator,
|
||||
const Options& options,
|
||||
const Comparator *user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence) {
|
||||
return new DBIter(dbname, env, user_key_comparator, internal_iter, sequence);
|
||||
return new DBIter(dbname, env, options, user_key_comparator,
|
||||
internal_iter, sequence);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
+2
-1
@@ -17,7 +17,8 @@ namespace leveldb {
|
||||
extern Iterator* NewDBIterator(
|
||||
const std::string* dbname,
|
||||
Env* env,
|
||||
const Comparator* user_key_comparator,
|
||||
const Options& options,
|
||||
const Comparator *user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence);
|
||||
|
||||
|
||||
+6
-5
@@ -8,6 +8,7 @@
|
||||
#include <cassert>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "leveldb/statistics.h"
|
||||
#include "util/histogram.h"
|
||||
@@ -39,11 +40,6 @@ class DBStatistics: public Statistics {
|
||||
allHistograms_[histogramType].Add(value);
|
||||
}
|
||||
|
||||
virtual void measureTime(Histograms histogramType, double value) {
|
||||
assert(histogramType < HISTOGRAM_ENUM_MAX);
|
||||
allHistograms_[histogramType].Add(value);
|
||||
}
|
||||
|
||||
virtual void histogramData(Histograms histogramType,
|
||||
HistogramData * const data) {
|
||||
assert(histogramType < HISTOGRAM_ENUM_MAX);
|
||||
@@ -53,6 +49,11 @@ class DBStatistics: public Statistics {
|
||||
std::vector<Ticker> allTickers_;
|
||||
std::vector<HistogramImpl> allHistograms_;
|
||||
};
|
||||
|
||||
std::shared_ptr<Statistics> CreateDBStatistics() {
|
||||
return std::make_shared<DBStatistics>();
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // LEVELDB_STORAGE_DB_DB_STATISTICS_H_
|
||||
|
||||
+323
-114
@@ -12,13 +12,15 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/compaction_filter.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "table/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"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -100,7 +102,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
manifest_write_error_.Release_Store(nullptr);
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r) {
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r,
|
||||
const EnvOptions& soptions) {
|
||||
class SSTableFile : public WritableFile {
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
@@ -157,7 +160,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
return Status::IOError("simulated write error");
|
||||
}
|
||||
|
||||
Status s = target()->NewWritableFile(f, r);
|
||||
Status s = target()->NewWritableFile(f, r, soptions);
|
||||
if (s.ok()) {
|
||||
if (strstr(f.c_str(), ".sst") != nullptr) {
|
||||
r->reset(new SSTableFile(this, std::move(*r)));
|
||||
@@ -169,7 +172,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
Status NewRandomAccessFile(const std::string& f,
|
||||
unique_ptr<RandomAccessFile>* r) {
|
||||
unique_ptr<RandomAccessFile>* r,
|
||||
const EnvOptions& soptions) {
|
||||
class CountingFile : public RandomAccessFile {
|
||||
private:
|
||||
unique_ptr<RandomAccessFile> target_;
|
||||
@@ -186,7 +190,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
}
|
||||
};
|
||||
|
||||
Status s = target()->NewRandomAccessFile(f, r);
|
||||
Status s = target()->NewRandomAccessFile(f, r, soptions);
|
||||
if (s.ok() && count_random_reads_) {
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_));
|
||||
}
|
||||
@@ -206,6 +210,7 @@ class DBTest {
|
||||
// Sequence of option configurations to try
|
||||
enum OptionConfig {
|
||||
kDefault,
|
||||
kMergePut,
|
||||
kFilter,
|
||||
kUncompressed,
|
||||
kNumLevel_3,
|
||||
@@ -217,6 +222,8 @@ class DBTest {
|
||||
};
|
||||
int option_config_;
|
||||
|
||||
std::shared_ptr<MergeOperator> merge_operator_;
|
||||
|
||||
public:
|
||||
std::string dbname_;
|
||||
SpecialEnv* env_;
|
||||
@@ -225,6 +232,7 @@ class DBTest {
|
||||
Options last_options_;
|
||||
|
||||
DBTest() : option_config_(kDefault),
|
||||
merge_operator_(MergeOperators::CreatePutOperator()),
|
||||
env_(new SpecialEnv(Env::Default())) {
|
||||
filter_policy_ = NewBloomFilterPolicy(10);
|
||||
dbname_ = test::TmpDir() + "/db_test";
|
||||
@@ -256,6 +264,9 @@ class DBTest {
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
switch (option_config_) {
|
||||
case kMergePut:
|
||||
options.merge_operator = merge_operator_.get();
|
||||
break;
|
||||
case kFilter:
|
||||
options.filter_policy = filter_policy_;
|
||||
break;
|
||||
@@ -323,8 +334,12 @@ class DBTest {
|
||||
return DB::Open(opts, dbname_, &db_);
|
||||
}
|
||||
|
||||
Status Put(const std::string& k, const std::string& v) {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
if (kMergePut == option_config_ ) {
|
||||
return db_->Merge(WriteOptions(), k, v);
|
||||
} else {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
}
|
||||
}
|
||||
|
||||
Status Delete(const std::string& k) {
|
||||
@@ -397,6 +412,10 @@ class DBTest {
|
||||
case kTypeValue:
|
||||
result += iter->value().ToString();
|
||||
break;
|
||||
case kTypeMerge:
|
||||
// keep it the same as kTypeValue for testing kMergePut
|
||||
result += iter->value().ToString();
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
result += "DEL";
|
||||
break;
|
||||
@@ -514,6 +533,26 @@ class DBTest {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Options OptionsForLogIterTest() {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.WAL_ttl_seconds = 1000;
|
||||
return options;
|
||||
}
|
||||
|
||||
std::unique_ptr<TransactionLogIterator> OpenTransactionLogIter(
|
||||
const SequenceNumber seq) {
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(seq, &iter);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
return std::move(iter);
|
||||
}
|
||||
|
||||
std::string DummyString(size_t len, char c = 'a') {
|
||||
return std::string(len, c);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DBTest, Empty) {
|
||||
@@ -551,6 +590,7 @@ TEST(DBTest, LevelLimitReopen) {
|
||||
}
|
||||
|
||||
options.num_levels = 1;
|
||||
options.max_bytes_for_level_multiplier_additional.resize(1, 1);
|
||||
Status s = TryReopen(&options);
|
||||
ASSERT_EQ(s.IsCorruption(), true);
|
||||
ASSERT_EQ(s.ToString(),
|
||||
@@ -558,13 +598,15 @@ TEST(DBTest, LevelLimitReopen) {
|
||||
"more levels than options.num_levels");
|
||||
|
||||
options.num_levels = 10;
|
||||
options.max_bytes_for_level_multiplier_additional.resize(10, 1);
|
||||
ASSERT_OK(TryReopen(&options));
|
||||
}
|
||||
|
||||
TEST(DBTest, Preallocation) {
|
||||
const std::string src = dbname_ + "/alloc_test";
|
||||
unique_ptr<WritableFile> srcfile;
|
||||
ASSERT_OK(env_->NewWritableFile(src, &srcfile));
|
||||
const EnvOptions soptions;
|
||||
ASSERT_OK(env_->NewWritableFile(src, &srcfile, soptions));
|
||||
srcfile->SetPreallocationBlockSize(1024 * 1024);
|
||||
|
||||
// No writes should mean no preallocation
|
||||
@@ -911,8 +953,11 @@ TEST(DBTest, IterMultiWithDelete) {
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("c");
|
||||
ASSERT_EQ(IterStatus(iter), "c->vc");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter), "a->va");
|
||||
if (!CurrentOptions().merge_operator) {
|
||||
// TODO: merge operator does not support backward iteration yet
|
||||
iter->Prev();
|
||||
ASSERT_EQ(IterStatus(iter), "a->va");
|
||||
}
|
||||
delete iter;
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
@@ -1326,40 +1371,69 @@ TEST(DBTest, RepeatedWritesToSameKey) {
|
||||
// kvs during the compaction process.
|
||||
static int cfilter_count;
|
||||
static std::string NEW_VALUE = "NewValue";
|
||||
static bool keep_filter(void* arg, int level, const Slice& key,
|
||||
const Slice& value, Slice** new_value) {
|
||||
assert(arg == nullptr);
|
||||
cfilter_count++;
|
||||
return false;
|
||||
}
|
||||
static bool delete_filter(void*argv, int level, const Slice& key,
|
||||
const Slice& value, Slice** new_value) {
|
||||
assert(argv == nullptr);
|
||||
cfilter_count++;
|
||||
return true;
|
||||
}
|
||||
static bool change_filter(void*argv, int level, const Slice& key,
|
||||
const Slice& value, Slice** new_value) {
|
||||
assert(argv == (void*)100);
|
||||
assert(new_value != nullptr);
|
||||
*new_value = new Slice(NEW_VALUE);
|
||||
return false;
|
||||
}
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key,
|
||||
const Slice& value, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
cfilter_count++;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "KeepFilter";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DeleteFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key,
|
||||
const Slice& value, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
cfilter_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "DeleteFilter";
|
||||
}
|
||||
};
|
||||
|
||||
class ChangeFilter : public CompactionFilter {
|
||||
public:
|
||||
ChangeFilter(int argv) : argv_(argv) {}
|
||||
|
||||
virtual bool Filter(int level, const Slice& key,
|
||||
const Slice& value, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
assert(argv_ == 100);
|
||||
assert(new_value != nullptr);
|
||||
*new_value = NEW_VALUE;
|
||||
*value_changed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual const char* Name() const override {
|
||||
return "ChangeFilter";
|
||||
}
|
||||
|
||||
private:
|
||||
const int argv_;
|
||||
};
|
||||
|
||||
TEST(DBTest, CompactionFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.max_mem_compaction_level = 0;
|
||||
options.CompactionFilter = keep_filter;
|
||||
auto keep_filter = std::make_shared<KeepFilter>();
|
||||
options.compaction_filter = keep_filter.get();
|
||||
Reopen(&options);
|
||||
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
// in L0. We do this so that the current snapshot points
|
||||
// to the 100001 key.The compaction filter is not invoked
|
||||
// on keys that are visible via a snapshot because we
|
||||
// anyways cannot delete it.
|
||||
// Write 100K keys, these are written to a few files in L0.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(key, value);
|
||||
@@ -1386,6 +1460,7 @@ TEST(DBTest, CompactionFilter) {
|
||||
// has sequence number zero. The 100001st record
|
||||
// is at the tip of this snapshot and cannot
|
||||
// be zeroed out.
|
||||
// TODO: figure out sequence number squashtoo
|
||||
int count = 0;
|
||||
int total = 0;
|
||||
Iterator* iter = dbfull()->TEST_NewInternalIterator();
|
||||
@@ -1401,12 +1476,12 @@ TEST(DBTest, CompactionFilter) {
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(total, 100001);
|
||||
ASSERT_EQ(total, 100000);
|
||||
ASSERT_EQ(count, 1);
|
||||
delete iter;
|
||||
|
||||
// overwrite all the 100K+1 keys once again.
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
// overwrite all the 100K keys once again.
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(key, value);
|
||||
@@ -1428,12 +1503,13 @@ TEST(DBTest, CompactionFilter) {
|
||||
|
||||
// create a new database with the compaction
|
||||
// filter in such a way that it deletes all keys
|
||||
options.CompactionFilter = delete_filter;
|
||||
auto delete_filter = std::make_shared<DeleteFilter>();
|
||||
options.compaction_filter = delete_filter.get();
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
// write all the keys once again.
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(key, value);
|
||||
@@ -1456,10 +1532,7 @@ TEST(DBTest, CompactionFilter) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Scan the entire database to ensure that only the
|
||||
// 100001th key is left in the db. The 100001th key
|
||||
// is part of the default-most-current snapshot and
|
||||
// cannot be deleted.
|
||||
// Scan the entire database to ensure that nothing is left
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
iter->SeekToFirst();
|
||||
count = 0;
|
||||
@@ -1467,12 +1540,14 @@ TEST(DBTest, CompactionFilter) {
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(count, 0);
|
||||
delete iter;
|
||||
|
||||
// The sequence number of the remaining record
|
||||
// is not zeroed out even though it is at the
|
||||
// level Lmax because this record is at the tip
|
||||
// TODO: remove the following or design a different
|
||||
// test
|
||||
count = 0;
|
||||
iter = dbfull()->TEST_NewInternalIterator();
|
||||
iter->SeekToFirst();
|
||||
@@ -1480,11 +1555,11 @@ TEST(DBTest, CompactionFilter) {
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
ASSERT_NE(ikey.sequence, 0);
|
||||
ASSERT_NE(ikey.sequence, (unsigned)0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(count, 0);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
@@ -1492,8 +1567,8 @@ TEST(DBTest, CompactionFilterWithValueChange) {
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.max_mem_compaction_level = 0;
|
||||
options.compaction_filter_args = (void *)100;
|
||||
options.CompactionFilter = change_filter;
|
||||
auto change_filter = std::make_shared<ChangeFilter>(100);
|
||||
options.compaction_filter = change_filter.get();
|
||||
Reopen(&options);
|
||||
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
@@ -2307,6 +2382,7 @@ TEST(DBTest, BloomFilter) {
|
||||
|
||||
TEST(DBTest, SnapshotFiles) {
|
||||
Options options = CurrentOptions();
|
||||
const EnvOptions soptions;
|
||||
options.write_buffer_size = 100000000; // Large write buffer
|
||||
Reopen(&options);
|
||||
|
||||
@@ -2360,9 +2436,9 @@ TEST(DBTest, SnapshotFiles) {
|
||||
}
|
||||
}
|
||||
unique_ptr<SequentialFile> srcfile;
|
||||
ASSERT_OK(env_->NewSequentialFile(src, &srcfile));
|
||||
ASSERT_OK(env_->NewSequentialFile(src, &srcfile, soptions));
|
||||
unique_ptr<WritableFile> destfile;
|
||||
ASSERT_OK(env_->NewWritableFile(dest, &destfile));
|
||||
ASSERT_OK(env_->NewWritableFile(dest, &destfile, soptions));
|
||||
|
||||
char buffer[4096];
|
||||
Slice slice;
|
||||
@@ -2518,20 +2594,20 @@ TEST(DBTest, CompactOnFlush) {
|
||||
db_->ReleaseSnapshot(snapshot1);
|
||||
}
|
||||
|
||||
void ListLogFiles(Env* env,
|
||||
const std::string& path,
|
||||
std::vector<uint64_t>* logFiles) {
|
||||
std::vector<std::uint64_t> ListLogFiles(Env* env, const std::string& path) {
|
||||
std::vector<std::string> files;
|
||||
std::vector<uint64_t> log_files;
|
||||
env->GetChildren(path, &files);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
if (ParseFileName(files[i], &number, &type)) {
|
||||
if (type == kLogFile) {
|
||||
logFiles->push_back(number);
|
||||
log_files.push_back(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::move(log_files);
|
||||
}
|
||||
|
||||
TEST(DBTest, WALArchival) {
|
||||
@@ -2550,94 +2626,157 @@ TEST(DBTest, WALArchival) {
|
||||
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));
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logFiles;
|
||||
ListLogFiles(env_, dbname_, &logFiles);
|
||||
std::vector<uint64_t> logFiles = ListLogFiles(env_, dbname_);
|
||||
|
||||
options.create_if_missing = false;
|
||||
Reopen(&options);
|
||||
|
||||
std::vector<uint64_t> logs;
|
||||
ListLogFiles(env_, archiveDir, &logs);
|
||||
std::vector<uint64_t> logs = ListLogFiles(env_, archiveDir);
|
||||
std::set<uint64_t> archivedFiles(logs.begin(), logs.end());
|
||||
|
||||
for (std::vector<uint64_t>::iterator it = logFiles.begin();
|
||||
it != logFiles.end();
|
||||
++it) {
|
||||
ASSERT_TRUE(archivedFiles.find(*it) != archivedFiles.end());
|
||||
for (auto& log : logFiles) {
|
||||
ASSERT_TRUE(archivedFiles.find(log) != archivedFiles.end());
|
||||
}
|
||||
}
|
||||
|
||||
// REOPEN database with 0 TTL. all files should have been deleted.
|
||||
std::vector<uint64_t> logFiles;
|
||||
ListLogFiles(env_, archiveDir, &logFiles);
|
||||
std::vector<uint64_t> logFiles = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(logFiles.size() > 0);
|
||||
options.WAL_ttl_seconds = 1;
|
||||
env_->SleepForMicroseconds(2*1000*1000);
|
||||
Reopen(&options);
|
||||
|
||||
logFiles.clear();
|
||||
ListLogFiles(env_, archiveDir, &logFiles);
|
||||
logFiles = ListLogFiles(env_, archiveDir);
|
||||
ASSERT_TRUE(logFiles.size() == 0);
|
||||
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIterator) {
|
||||
std::string value(1024, '1');
|
||||
TEST(DBTest, WALClear) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.WAL_ttl_seconds = 1000;
|
||||
options.WAL_ttl_seconds = 1;
|
||||
|
||||
for (int j = 0; j < 10; ++j)
|
||||
for (int i = 0; i < 10; ++i)
|
||||
ASSERT_OK(Put(Key(10*i+j), 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);
|
||||
dbfull()->TEST_PurgeObsoleteteWAL();
|
||||
log_files = ListLogFiles(env_, archive_dir);
|
||||
ASSERT_TRUE(log_files.empty());
|
||||
}
|
||||
|
||||
void ExpectRecords(
|
||||
const int expected_no_records,
|
||||
std::unique_ptr<TransactionLogIterator>& iter) {
|
||||
int i = 0;
|
||||
SequenceNumber lastSequence = 0;
|
||||
while (iter->Valid()) {
|
||||
BatchResult res = iter->GetBatch();
|
||||
ASSERT_TRUE(res.sequence > lastSequence);
|
||||
++i;
|
||||
lastSequence = res.sequence;
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(i, expected_no_records);
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIterator) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
Put("key1", value);
|
||||
Put("key2", value);
|
||||
Put("key2", value);
|
||||
Put("key1", DummyString(1024));
|
||||
Put("key2", DummyString(1024));
|
||||
Put("key2", DummyString(1024));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3U);
|
||||
{
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(0, &iter);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
int i = 0;
|
||||
SequenceNumber lastSequence = 0;
|
||||
while (iter->Valid()) {
|
||||
BatchResult res = iter->GetBatch();
|
||||
ASSERT_TRUE(res.sequence > lastSequence);
|
||||
++i;
|
||||
lastSequence = res.sequence;
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(i, 3);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(3, iter);
|
||||
}
|
||||
Reopen(&options);
|
||||
{
|
||||
Put("key4", value);
|
||||
Put("key5", value);
|
||||
Put("key6", value);
|
||||
env_->SleepForMicroseconds(2 * 1000 * 1000);{
|
||||
Put("key4", DummyString(1024));
|
||||
Put("key5", DummyString(1024));
|
||||
Put("key6", DummyString(1024));
|
||||
}
|
||||
{
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(0, &iter);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
int i = 0;
|
||||
SequenceNumber lastSequence = 0;
|
||||
while (iter->Valid()) {
|
||||
BatchResult res = iter->GetBatch();
|
||||
ASSERT_TRUE(res.sequence > lastSequence);
|
||||
lastSequence = res.sequence;
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
iter->Next();
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(i, 6);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(6, iter);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorMoveOverZeroFiles) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
// Do a plain Reopen.
|
||||
Put("key1", DummyString(1024));
|
||||
// Two reopens should create a zero record WAL file.
|
||||
Reopen(&options);
|
||||
Reopen(&options);
|
||||
|
||||
Put("key2", DummyString(1024));
|
||||
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(2, iter);
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorStallAtLastRecord) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
Put("key1", DummyString(1024));
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
Put("key2", DummyString(1024));
|
||||
iter->Next();
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorJustEmptyFile) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
unique_ptr<TransactionLogIterator> iter;
|
||||
Status status = dbfull()->GetUpdatesSince(0, &iter);
|
||||
ASSERT_TRUE(!status.ok());
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorCheckAfterRestart) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
Put("key1", DummyString(1024));
|
||||
Put("key2", DummyString(1023));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Reopen(&options);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
ExpectRecords(2, iter);
|
||||
}
|
||||
|
||||
TEST(DBTest, TransactionLogIteratorBatchOperations) {
|
||||
Options options = OptionsForLogIterTest();
|
||||
DestroyAndReopen(&options);
|
||||
WriteBatch batch;
|
||||
batch.Put("key1", DummyString(1024));
|
||||
batch.Put("key2", DummyString(1024));
|
||||
batch.Put("key3", DummyString(1024));
|
||||
batch.Delete("key2");
|
||||
dbfull()->Write(WriteOptions(), &batch);
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Reopen(&options);
|
||||
Put("key4", DummyString(1024));
|
||||
auto iter = OpenTransactionLogIter(3);
|
||||
ExpectRecords(1, iter);
|
||||
}
|
||||
|
||||
TEST(DBTest, ReadCompaction) {
|
||||
std::string value(4096, '4'); // a string of size 4K
|
||||
{
|
||||
@@ -2740,7 +2879,7 @@ static void MTThreadBody(void* arg) {
|
||||
// We add some padding for force compactions.
|
||||
snprintf(valbuf, sizeof(valbuf), "%d.%d.%-1000d",
|
||||
key, id, static_cast<int>(counter));
|
||||
ASSERT_OK(db->Put(WriteOptions(), Slice(keybuf), Slice(valbuf)));
|
||||
ASSERT_OK(t->state->test->Put(Slice(keybuf), Slice(valbuf)));
|
||||
} else {
|
||||
// Read a value and verify that it matches the pattern written above.
|
||||
Status s = db->Get(ReadOptions(), Slice(keybuf), &value);
|
||||
@@ -2813,13 +2952,23 @@ class ModelDB: public DB {
|
||||
virtual Status Put(const WriteOptions& o, const Slice& k, const Slice& v) {
|
||||
return DB::Put(o, k, v);
|
||||
}
|
||||
virtual Status Merge(const WriteOptions& o, const Slice& k, const Slice& v) {
|
||||
return DB::Merge(o, k, v);
|
||||
}
|
||||
virtual Status Delete(const WriteOptions& o, const Slice& key) {
|
||||
return DB::Delete(o, key);
|
||||
}
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key, std::string* value) {
|
||||
assert(false); // Not implemented
|
||||
return Status::NotFound(key);
|
||||
return Status::NotSupported(key);
|
||||
}
|
||||
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) {
|
||||
std::vector<Status> s(keys.size(),
|
||||
Status::NotSupported("Not implemented."));
|
||||
return s;
|
||||
}
|
||||
virtual Iterator* NewIterator(const ReadOptions& options) {
|
||||
if (options.snapshot == nullptr) {
|
||||
@@ -2848,6 +2997,10 @@ class ModelDB: public DB {
|
||||
virtual void Put(const Slice& key, const Slice& value) {
|
||||
(*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
virtual void Merge(const Slice& key, const Slice& value) {
|
||||
// ignore merge for now
|
||||
//(*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
virtual void Delete(const Slice& key) {
|
||||
map_->erase(key.ToString());
|
||||
}
|
||||
@@ -3063,6 +3216,61 @@ TEST(DBTest, Randomized) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST(DBTest, MultiGetSimple) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(),"k1","v1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(),"k2","v2"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(),"k3","v3"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(),"k4","v4"));
|
||||
ASSERT_OK(db_->Delete(WriteOptions(),"k4"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(),"k5","v5"));
|
||||
ASSERT_OK(db_->Delete(WriteOptions(),"no_key"));
|
||||
|
||||
std::vector<Slice> keys(6);
|
||||
keys[0] = "k1";
|
||||
keys[1] = "k2";
|
||||
keys[2] = "k3";
|
||||
keys[3] = "k4";
|
||||
keys[4] = "k5";
|
||||
keys[5] = "no_key";
|
||||
|
||||
std::vector<std::string> values(20,"Temporary data to be overwritten");
|
||||
|
||||
std::vector<Status> s = db_->MultiGet(ReadOptions(),keys,&values);
|
||||
ASSERT_EQ(values.size(),keys.size());
|
||||
ASSERT_EQ(values[0], "v1");
|
||||
ASSERT_EQ(values[1], "v2");
|
||||
ASSERT_EQ(values[2], "v3");
|
||||
ASSERT_EQ(values[4], "v5");
|
||||
|
||||
ASSERT_OK(s[0]);
|
||||
ASSERT_OK(s[1]);
|
||||
ASSERT_OK(s[2]);
|
||||
ASSERT_TRUE(s[3].IsNotFound());
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_TRUE(s[5].IsNotFound());
|
||||
}
|
||||
|
||||
TEST(DBTest, MultiGetEmpty) {
|
||||
// Empty Key Set
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> s = db_->MultiGet(ReadOptions(),keys,&values);
|
||||
ASSERT_EQ((int)s.size(),0);
|
||||
|
||||
// Empty Database, Empty Key Set
|
||||
DestroyAndReopen();
|
||||
s = db_->MultiGet(ReadOptions(), keys, &values);
|
||||
ASSERT_EQ((int)s.size(),0);
|
||||
|
||||
// Empty Database, Search for Keys
|
||||
keys.resize(2);
|
||||
keys[0] = "a";
|
||||
keys[1] = "b";
|
||||
s = db_->MultiGet(ReadOptions(),keys,&values);
|
||||
ASSERT_EQ((int)s.size(), 2);
|
||||
ASSERT_TRUE(s[0].IsNotFound() && s[1].IsNotFound());
|
||||
}
|
||||
|
||||
std::string MakeKey(unsigned int num) {
|
||||
char buf[30];
|
||||
snprintf(buf, sizeof(buf), "%016u", num);
|
||||
@@ -3090,7 +3298,8 @@ void BM_LogAndApply(int iters, int num_base_files) {
|
||||
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
Options options;
|
||||
VersionSet vset(dbname, &options, nullptr, &cmp);
|
||||
EnvOptions sopt;
|
||||
VersionSet vset(dbname, &options, sopt, nullptr, &cmp);
|
||||
ASSERT_OK(vset.Recover());
|
||||
VersionEdit vbase(vset.NumberLevels());
|
||||
uint64_t fnum = 1;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ std::string InternalKey::DebugString(bool hex) const {
|
||||
}
|
||||
|
||||
const char* InternalKeyComparator::Name() const {
|
||||
return "leveldb.InternalKeyComparator";
|
||||
return name_.c_str();
|
||||
}
|
||||
|
||||
int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const {
|
||||
|
||||
+10
-4
@@ -24,7 +24,8 @@ class InternalKey;
|
||||
// data structures.
|
||||
enum ValueType {
|
||||
kTypeDeletion = 0x0,
|
||||
kTypeValue = 0x1
|
||||
kTypeValue = 0x1,
|
||||
kTypeMerge = 0x2
|
||||
};
|
||||
// kValueTypeForSeek defines the ValueType that should be passed when
|
||||
// constructing a ParsedInternalKey object for seeking to a particular
|
||||
@@ -32,7 +33,7 @@ enum ValueType {
|
||||
// and the value type is embedded as the low 8 bits in the sequence
|
||||
// number in internal keys, we need to use the highest-numbered
|
||||
// ValueType, not the lowest).
|
||||
static const ValueType kValueTypeForSeek = kTypeValue;
|
||||
static const ValueType kValueTypeForSeek = kTypeMerge;
|
||||
|
||||
// We leave eight bits empty at the bottom so a type and sequence#
|
||||
// can be packed together into 64-bits.
|
||||
@@ -85,8 +86,13 @@ inline ValueType ExtractValueType(const Slice& internal_key) {
|
||||
class InternalKeyComparator : public Comparator {
|
||||
private:
|
||||
const Comparator* user_comparator_;
|
||||
std::string name_;
|
||||
public:
|
||||
explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c) { }
|
||||
explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c),
|
||||
name_("leveldb.InternalKeyComparator:" +
|
||||
std::string(user_comparator_->Name())) {
|
||||
}
|
||||
|
||||
virtual const char* Name() const;
|
||||
virtual int Compare(const Slice& a, const Slice& b) const;
|
||||
virtual void FindShortestSeparator(
|
||||
@@ -154,7 +160,7 @@ inline bool ParseInternalKey(const Slice& internal_key,
|
||||
result->sequence = num >> 8;
|
||||
result->type = static_cast<ValueType>(c);
|
||||
result->user_key = Slice(internal_key.data(), n - 8);
|
||||
return (c <= static_cast<unsigned char>(kTypeValue));
|
||||
return (c <= static_cast<unsigned char>(kValueTypeForSeek));
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ enum RecordType {
|
||||
};
|
||||
static const int kMaxRecordType = kLastType;
|
||||
|
||||
static const int kBlockSize = 32768;
|
||||
static const unsigned int kBlockSize = 32768;
|
||||
|
||||
// Header is checksum (4 bytes), type (1 byte), length (2 bytes).
|
||||
static const int kHeaderSize = 4 + 1 + 2;
|
||||
|
||||
@@ -59,6 +59,17 @@ class Reader {
|
||||
// Undefined before the first call to ReadRecord.
|
||||
uint64_t LastRecordOffset();
|
||||
|
||||
// returns true if the reader has encountered an eof condition.
|
||||
bool IsEOF() {
|
||||
return eof_;
|
||||
}
|
||||
|
||||
// when we know more data has been written to the file. we can use this
|
||||
// function to force the reader to look again in the file.
|
||||
void UnmarkEOF() {
|
||||
eof_ = false;
|
||||
}
|
||||
|
||||
SequentialFile* file() { return file_.get(); }
|
||||
|
||||
private:
|
||||
|
||||
+2
-2
@@ -450,13 +450,13 @@ TEST(LogTest, ErrorJoinsRecords) {
|
||||
Write("correct");
|
||||
|
||||
// Wipe the middle block
|
||||
for (int offset = kBlockSize; offset < 2*kBlockSize; offset++) {
|
||||
for (unsigned int offset = kBlockSize; offset < 2*kBlockSize; offset++) {
|
||||
SetByte(offset, 'x');
|
||||
}
|
||||
|
||||
ASSERT_EQ("correct", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
const int dropped = DroppedBytes();
|
||||
const unsigned int dropped = DroppedBytes();
|
||||
ASSERT_LE(dropped, 2*kBlockSize + 100);
|
||||
ASSERT_GE(dropped, 2*kBlockSize);
|
||||
}
|
||||
|
||||
+52
-6
@@ -7,6 +7,7 @@
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace leveldb {
|
||||
@@ -26,7 +27,8 @@ MemTable::MemTable(const InternalKeyComparator& cmp, int numlevel)
|
||||
flush_completed_(false),
|
||||
file_number_(0),
|
||||
edit_(numlevel),
|
||||
first_seqno_(0) {
|
||||
first_seqno_(0),
|
||||
mem_logfile_number_(0) {
|
||||
}
|
||||
|
||||
MemTable::~MemTable() {
|
||||
@@ -116,11 +118,23 @@ void MemTable::Add(SequenceNumber s, ValueType type,
|
||||
}
|
||||
}
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options) {
|
||||
Slice memkey = key.memtable_key();
|
||||
Table::Iterator iter(&table_);
|
||||
iter.Seek(memkey.data());
|
||||
if (iter.Valid()) {
|
||||
|
||||
bool merge_in_progress = false;
|
||||
std::string operand;
|
||||
if (s->IsMergeInProgress()) {
|
||||
swap(*value, operand);
|
||||
merge_in_progress = true;
|
||||
}
|
||||
|
||||
|
||||
auto merge_operator = options.merge_operator;
|
||||
auto logger = options.info_log;
|
||||
for (; iter.Valid(); iter.Next()) {
|
||||
// entry format is:
|
||||
// klength varint32
|
||||
// userkey char[klength-8]
|
||||
@@ -141,15 +155,47 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s) {
|
||||
switch (static_cast<ValueType>(tag & 0xff)) {
|
||||
case kTypeValue: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
value->assign(v.data(), v.size());
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), &v, operand,
|
||||
value, logger.get());
|
||||
} else {
|
||||
value->assign(v.data(), v.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case kTypeDeletion:
|
||||
*s = Status::NotFound(Slice());
|
||||
case kTypeMerge: {
|
||||
Slice v = GetLengthPrefixedSlice(key_ptr + key_length);
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), &v, operand,
|
||||
value, logger.get());
|
||||
swap(*value, operand);
|
||||
} else {
|
||||
assert(merge_operator);
|
||||
merge_in_progress = true;
|
||||
operand.assign(v.data(), v.size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kTypeDeletion: {
|
||||
if (merge_in_progress) {
|
||||
merge_operator->Merge(key.user_key(), nullptr, operand,
|
||||
value, logger.get());
|
||||
} else {
|
||||
*s = Status::NotFound(Slice());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// exit loop if user key does not match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (merge_in_progress) {
|
||||
swap(*value, operand);
|
||||
*s = Status::MergeInProgress("");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+17
-1
@@ -62,8 +62,13 @@ class MemTable {
|
||||
// If memtable contains a value for key, store it in *value and return true.
|
||||
// If memtable contains a deletion for key, store a NotFound() error
|
||||
// in *status and return true.
|
||||
// If memtable contains Merge operation as the most recent entry for a key,
|
||||
// and the merge process does not stop (not reaching a value or delete),
|
||||
// store the current merged result in value and MergeInProgress in s.
|
||||
// return false
|
||||
// Else, return false.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s);
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options);
|
||||
|
||||
// Returns the edits area that is needed for flushing the memtable
|
||||
VersionEdit* GetEdits() { return &edit_; }
|
||||
@@ -72,6 +77,14 @@ class MemTable {
|
||||
// into the memtable
|
||||
SequenceNumber GetFirstSequenceNumber() { return first_seqno_; }
|
||||
|
||||
// Returns the logfile number that can be safely deleted when this
|
||||
// memstore is flushed to storage
|
||||
uint64_t GetLogNumber() { return mem_logfile_number_; }
|
||||
|
||||
// Sets the logfile number that can be safely deleted when this
|
||||
// memstore is flushed to storage
|
||||
void SetLogNumber(uint64_t num) { mem_logfile_number_ = num; }
|
||||
|
||||
private:
|
||||
~MemTable(); // Private since only Unref() should be used to delete it
|
||||
|
||||
@@ -103,6 +116,9 @@ class MemTable {
|
||||
// The sequence number of the kv that was inserted first
|
||||
SequenceNumber first_seqno_;
|
||||
|
||||
// The log files earlier than this number can be deleted.
|
||||
uint64_t mem_logfile_number_;
|
||||
|
||||
// No copying allowed
|
||||
MemTable(const MemTable&);
|
||||
void operator=(const MemTable&);
|
||||
|
||||
+69
-49
@@ -43,17 +43,16 @@ int MemTableList::size() {
|
||||
|
||||
// Returns true if there is at least one memtable on which flush has
|
||||
// not yet started.
|
||||
bool MemTableList::IsFlushPending() {
|
||||
if (num_flush_not_started_ > 0) {
|
||||
bool MemTableList::IsFlushPending(int min_write_buffer_number_to_merge) {
|
||||
if (num_flush_not_started_ >= min_write_buffer_number_to_merge) {
|
||||
assert(imm_flush_needed.NoBarrier_Load() != nullptr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the earliest memtable that needs to be flushed.
|
||||
// Returns null, if no such memtable exist.
|
||||
MemTable* MemTableList::PickMemtableToFlush() {
|
||||
// Returns the memtables that need to be flushed.
|
||||
void MemTableList::PickMemtablesToFlush(std::vector<MemTable*>* ret) {
|
||||
for (list<MemTable*>::reverse_iterator it = memlist_.rbegin();
|
||||
it != memlist_.rend(); it++) {
|
||||
MemTable* m = *it;
|
||||
@@ -64,37 +63,48 @@ MemTable* MemTableList::PickMemtableToFlush() {
|
||||
imm_flush_needed.Release_Store(nullptr);
|
||||
}
|
||||
m->flush_in_progress_ = true; // flushing will start very soon
|
||||
return m;
|
||||
ret->push_back(m);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Record a successful flush in the manifest file
|
||||
Status MemTableList::InstallMemtableFlushResults(MemTable* m,
|
||||
Status MemTableList::InstallMemtableFlushResults(
|
||||
const std::vector<MemTable*> &mems,
|
||||
VersionSet* vset, Status flushStatus,
|
||||
port::Mutex* mu, Logger* info_log,
|
||||
uint64_t file_number,
|
||||
std::set<uint64_t>& pending_outputs) {
|
||||
mu->AssertHeld();
|
||||
assert(m->flush_in_progress_);
|
||||
assert(m->file_number_ == 0);
|
||||
|
||||
// If the flush was not successful, then just reset state.
|
||||
// Maybe a suceeding attempt to flush will be successful.
|
||||
if (!flushStatus.ok()) {
|
||||
m->flush_in_progress_ = false;
|
||||
m->flush_completed_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
imm_flush_needed.Release_Store((void *)1);
|
||||
pending_outputs.erase(file_number);
|
||||
for (MemTable* m : mems) {
|
||||
assert(m->flush_in_progress_);
|
||||
assert(m->file_number_ == 0);
|
||||
|
||||
m->flush_in_progress_ = false;
|
||||
m->flush_completed_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
imm_flush_needed.Release_Store((void *)1);
|
||||
pending_outputs.erase(file_number);
|
||||
}
|
||||
return flushStatus;
|
||||
}
|
||||
|
||||
// flush was sucessful
|
||||
m->flush_completed_ = true;
|
||||
m->file_number_ = file_number;
|
||||
bool first = true;
|
||||
for (MemTable* m : mems) {
|
||||
|
||||
// All the edits are associated with the first memtable of this batch.
|
||||
assert(first || m->GetEdits()->NumEntries() == 0);
|
||||
first = false;
|
||||
|
||||
m->flush_completed_ = true;
|
||||
m->file_number_ = file_number;
|
||||
}
|
||||
|
||||
// if some other thread is already commiting, then return
|
||||
Status s;
|
||||
@@ -106,12 +116,15 @@ Status MemTableList::InstallMemtableFlushResults(MemTable* m,
|
||||
commit_in_progress_ = true;
|
||||
|
||||
// scan all memtables from the earliest, and commit those
|
||||
// (in that order) that have finished flushing.
|
||||
while (!memlist_.empty()) {
|
||||
m = memlist_.back(); // get the last element
|
||||
// (in that order) that have finished flushing. Memetables
|
||||
// are always committed in the order that they were created.
|
||||
while (!memlist_.empty() && s.ok()) {
|
||||
MemTable* m = memlist_.back(); // get the last element
|
||||
if (!m->flush_completed_) {
|
||||
break;
|
||||
}
|
||||
first = true;
|
||||
|
||||
Log(info_log,
|
||||
"Level-0 commit table #%llu: started",
|
||||
(unsigned long long)m->file_number_);
|
||||
@@ -119,33 +132,39 @@ Status MemTableList::InstallMemtableFlushResults(MemTable* m,
|
||||
// this can release and reacquire the mutex.
|
||||
s = vset->LogAndApply(&m->edit_, mu);
|
||||
|
||||
if (s.ok()) { // commit new state
|
||||
Log(info_log, "Level-0 commit table #%llu: done",
|
||||
(unsigned long long)m->file_number_);
|
||||
memlist_.remove(m);
|
||||
assert(m->file_number_ > 0);
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
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");
|
||||
memlist_.remove(m);
|
||||
assert(m->file_number_ > 0);
|
||||
|
||||
// pending_outputs can be cleared only after the newly created file
|
||||
// has been written to a committed version so that other concurrently
|
||||
// executing compaction threads do not mistakenly assume that this
|
||||
// file is not live.
|
||||
pending_outputs.erase(m->file_number_);
|
||||
m->Unref();
|
||||
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_);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
pending_outputs.erase(m->file_number_);
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.Release_Store((void *)1);
|
||||
s = Status::IOError("Unable to commit flushed memtable");
|
||||
break;
|
||||
}
|
||||
// pending_outputs can be cleared only after the newly created file
|
||||
// has been written to a committed version so that other concurrently
|
||||
// executing compaction threads do not mistakenly assume that this
|
||||
// file is not live.
|
||||
pending_outputs.erase(m->file_number_);
|
||||
m->Unref();
|
||||
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_);
|
||||
m->flush_completed_ = false;
|
||||
m->flush_in_progress_ = false;
|
||||
m->edit_.Clear();
|
||||
num_flush_not_started_++;
|
||||
pending_outputs.erase(m->file_number_);
|
||||
m->file_number_ = 0;
|
||||
imm_flush_needed.Release_Store((void *)1);
|
||||
s = Status::IOError("Unable to commit flushed memtable");
|
||||
}
|
||||
first = false;
|
||||
} while (!memlist_.empty() && (m = memlist_.back()) &&
|
||||
m->file_number_ == file_number);
|
||||
}
|
||||
commit_in_progress_ = false;
|
||||
return s;
|
||||
@@ -174,10 +193,11 @@ size_t MemTableList::ApproximateMemoryUsage() {
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
bool MemTableList::Get(const LookupKey& key, std::string* value, Status* s) {
|
||||
bool MemTableList::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options ) {
|
||||
for (list<MemTable*>::iterator it = memlist_.begin();
|
||||
it != memlist_.end(); ++it) {
|
||||
if ((*it)->Get(key, value, s)) {
|
||||
if ((*it)->Get(key, value, s, options)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -18,7 +18,7 @@ class Mutex;
|
||||
class MemTableListIterator;
|
||||
|
||||
//
|
||||
// This class stores refeernces to all the immutable memtables.
|
||||
// This class stores references to all the immutable memtables.
|
||||
// The memtables are flushed to L0 as soon as possible and in
|
||||
// any order. If there are more than one immutable memtable, their
|
||||
// flushes can occur concurrently. However, they are 'committed'
|
||||
@@ -49,14 +49,13 @@ class MemTableList {
|
||||
|
||||
// Returns true if there is at least one memtable on which flush has
|
||||
// not yet started.
|
||||
bool IsFlushPending();
|
||||
bool IsFlushPending(int min_write_buffer_number_to_merge);
|
||||
|
||||
// Returns the earliest memtable that needs to be flushed.
|
||||
// Returns null, if no such memtable exist.
|
||||
MemTable* PickMemtableToFlush();
|
||||
// Returns the earliest memtables that needs to be flushed.
|
||||
void PickMemtablesToFlush(std::vector<MemTable*>* mems);
|
||||
|
||||
// Commit a successful flush in the manifest file
|
||||
Status InstallMemtableFlushResults(MemTable* m,
|
||||
Status InstallMemtableFlushResults(const std::vector<MemTable*> &m,
|
||||
VersionSet* vset, Status flushStatus,
|
||||
port::Mutex* mu, Logger* info_log,
|
||||
uint64_t file_number,
|
||||
@@ -71,7 +70,8 @@ class MemTableList {
|
||||
|
||||
// Search all the memtables starting from the most recent one.
|
||||
// Return the most recent value found, if any.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s);
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
const Options& options);
|
||||
|
||||
// Returns the list of underlying memtables.
|
||||
void GetMemTables(std::vector<MemTable*>* list);
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#include "merge_helper.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// PRE: iter points to the first merge type entry
|
||||
// POST: iter points to the first entry beyond the merge process (or the end)
|
||||
// key_, value_ are updated to reflect the merge result
|
||||
void MergeHelper::MergeUntil(Iterator* iter, SequenceNumber stop_before,
|
||||
bool at_bottom) {
|
||||
// get a copy of the internal key, before it's invalidated by iter->Next()
|
||||
key_.assign(iter->key().data(), iter->key().size());
|
||||
// we need to parse the internal key again as the parsed key is
|
||||
// backed by the internal key!
|
||||
ParsedInternalKey orig_ikey;
|
||||
// Assume no internal key corruption as it has been successfully parsed
|
||||
// by the caller.
|
||||
// TODO: determine a good alternative of assert (exception?)
|
||||
ParseInternalKey(key_, &orig_ikey);
|
||||
std::string operand(iter->value().data(), iter->value().size());
|
||||
|
||||
bool hit_the_next_user_key = false;
|
||||
ParsedInternalKey ikey;
|
||||
for (iter->Next(); iter->Valid(); iter->Next()) {
|
||||
if (!ParseInternalKey(iter->key(), &ikey)) {
|
||||
// stop at corrupted key
|
||||
if (assert_valid_internal_key_) {
|
||||
assert(!"corrupted internal key is not expected");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (user_comparator_->Compare(ikey.user_key, orig_ikey.user_key) != 0) {
|
||||
// hit a different user key, stop right here
|
||||
hit_the_next_user_key = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (stop_before && ikey.sequence <= stop_before) {
|
||||
// hit an entry that's visible by the previous snapshot, can't touch that
|
||||
break;
|
||||
}
|
||||
|
||||
if (kTypeDeletion == ikey.type) {
|
||||
// hit a delete
|
||||
// => merge nullptr with operand
|
||||
// => change the entry type to kTypeValue
|
||||
// We are done!
|
||||
user_merge_operator_->Merge(ikey.user_key, nullptr, operand,
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
// move iter to the next entry
|
||||
iter->Next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeValue == ikey.type) {
|
||||
// hit a put
|
||||
// => merge the put value with operand
|
||||
// => change the entry type to kTypeValue
|
||||
// We are done!
|
||||
const Slice value = iter->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, Slice(operand),
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
// move iter to the next entry
|
||||
iter->Next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (kTypeMerge == ikey.type) {
|
||||
// hit a merge
|
||||
// => merge the value with operand.
|
||||
// => put the result back to operand and continue
|
||||
const Slice value = iter->value();
|
||||
user_merge_operator_->Merge(ikey.user_key, &value, operand,
|
||||
&value_, logger_);
|
||||
swap(value_, operand);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// We have seen the root history of this key if we are at the
|
||||
// bottem level and exhausted all internal keys of this user key
|
||||
// NOTE: !iter->Valid() does not necessarily mean we hit the
|
||||
// beginning of a user key, as versions of a user key might be
|
||||
// split into multiple files and some files might not be included
|
||||
// in the merge.
|
||||
bool seen_the_beginning = hit_the_next_user_key && at_bottom;
|
||||
|
||||
if (seen_the_beginning) {
|
||||
// do a final merge with nullptr as the existing value and say
|
||||
// bye to the merge type (it's now converted to a Put)
|
||||
assert(kTypeMerge == orig_ikey.type);
|
||||
user_merge_operator_->Merge(orig_ikey.user_key, nullptr, operand,
|
||||
&value_, logger_);
|
||||
orig_ikey.type = kTypeValue;
|
||||
UpdateInternalKey(&key_[0], key_.size(),
|
||||
orig_ikey.sequence, orig_ikey.type);
|
||||
} else {
|
||||
swap(value_, operand);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef MERGE_HELPER_H
|
||||
#define MERGE_HELPER_H
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include <string>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Comparator;
|
||||
class Iterator;
|
||||
class Logger;
|
||||
class MergeOperator;
|
||||
|
||||
class MergeHelper {
|
||||
public:
|
||||
MergeHelper(const Comparator* user_comparator,
|
||||
const MergeOperator* user_merge_operator,
|
||||
Logger* logger,
|
||||
bool assert_valid_internal_key)
|
||||
: user_comparator_(user_comparator),
|
||||
user_merge_operator_(user_merge_operator),
|
||||
logger_(logger),
|
||||
assert_valid_internal_key_(assert_valid_internal_key) {}
|
||||
|
||||
// Merge entries until we hit
|
||||
// - a corrupted key
|
||||
// - a Put/Delete,
|
||||
// - a different user key,
|
||||
// - a specific sequence number (snapshot boundary),
|
||||
// or - the end of iteration
|
||||
// iter: (IN) points to the first merge type entry
|
||||
// (OUT) points to the first entry not included in the merge process
|
||||
// stop_before: (IN) a sequence number that merge should not cross.
|
||||
// 0 means no restriction
|
||||
// at_bottom: (IN) true if the iterator covers the bottem level, which means
|
||||
// we could reach the start of the history of this user key.
|
||||
void MergeUntil(Iterator* iter, SequenceNumber stop_before = 0,
|
||||
bool at_bottom = false);
|
||||
|
||||
// Query the merge result
|
||||
// These are valid until the next MergeUtil call
|
||||
// IMPORTANT: the key type could change after the MergeUntil call.
|
||||
// Put/Delete + Merge + ... + Merge => Put
|
||||
// Merge + ... + Merge => Merge
|
||||
Slice key() { return Slice(key_); }
|
||||
Slice value() { return Slice(value_); }
|
||||
|
||||
private:
|
||||
const Comparator* user_comparator_;
|
||||
const MergeOperator* user_merge_operator_;
|
||||
Logger* logger_;
|
||||
Iterator* iter_; // in: the internal iterator, positioned at the first merge entry
|
||||
bool assert_valid_internal_key_; // enforce no internal key corruption?
|
||||
|
||||
// the scratch area that holds the result of MergeUntil
|
||||
// valid up to the next MergeUntil call
|
||||
std::string key_;
|
||||
std::string value_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,253 @@
|
||||
#include <assert.h>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace leveldb;
|
||||
|
||||
auto mergeOperator = MergeOperators::CreateUInt64AddOperator();
|
||||
|
||||
std::shared_ptr<DB> OpenDb() {
|
||||
DB* db;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = mergeOperator.get();
|
||||
Status s = DB::Open(options, "/tmp/testdb", &db);
|
||||
if (!s.ok()) {
|
||||
cerr << s.ToString() << endl;
|
||||
assert(false);
|
||||
}
|
||||
return std::shared_ptr<DB>(db);
|
||||
}
|
||||
|
||||
// Imagine we are maintaining a set of uint64 counters.
|
||||
// Each counter has a distinct name. And we would like
|
||||
// to support four high level operations:
|
||||
// set, add, get and remove
|
||||
// This is a quick implementation without a Merge operation.
|
||||
class Counters {
|
||||
|
||||
protected:
|
||||
std::shared_ptr<DB> db_;
|
||||
|
||||
WriteOptions put_option_;
|
||||
ReadOptions get_option_;
|
||||
WriteOptions delete_option_;
|
||||
|
||||
uint64_t default_;
|
||||
|
||||
public:
|
||||
Counters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
: db_(db),
|
||||
put_option_(),
|
||||
get_option_(),
|
||||
delete_option_(),
|
||||
default_(defaultCount) {
|
||||
assert(db_);
|
||||
}
|
||||
|
||||
virtual ~Counters() {}
|
||||
|
||||
// public interface of Counters.
|
||||
// All four functions return false
|
||||
// if the underlying level db operation failed.
|
||||
|
||||
// mapped to a levedb Put
|
||||
bool set(const string& key, uint64_t value) {
|
||||
// just treat the internal rep of int64 as the string
|
||||
Slice slice((char *)&value, sizeof(value));
|
||||
auto s = db_->Put(put_option_, key, slice);
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// mapped to a leveldb Delete
|
||||
bool remove(const string& key) {
|
||||
auto s = db_->Delete(delete_option_, key);
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// mapped to a leveldb Get
|
||||
bool get(const string& key, uint64_t *value) {
|
||||
string str;
|
||||
auto s = db_->Get(get_option_, key, &str);
|
||||
|
||||
if (s.IsNotFound()) {
|
||||
// return default value if not found;
|
||||
*value = default_;
|
||||
return true;
|
||||
} else if (s.ok()) {
|
||||
// deserialization
|
||||
if (str.size() != sizeof(uint64_t)) {
|
||||
cerr << "value corruption\n";
|
||||
return false;
|
||||
}
|
||||
*value = DecodeFixed64(&str[0]);
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 'add' is implemented as get -> modify -> set
|
||||
// An alternative is a single merge operation, see MergeBasedCounters
|
||||
virtual bool add(const string& key, uint64_t value) {
|
||||
uint64_t base = default_;
|
||||
return get(key, &base) && set(key, base + value);
|
||||
}
|
||||
|
||||
|
||||
// convenience functions for testing
|
||||
void assert_set(const string& key, uint64_t value) {
|
||||
assert(set(key, value));
|
||||
}
|
||||
|
||||
void assert_remove(const string& key) {
|
||||
assert(remove(key));
|
||||
}
|
||||
|
||||
uint64_t assert_get(const string& key) {
|
||||
uint64_t value = default_;
|
||||
assert(get(key, &value));
|
||||
return value;
|
||||
}
|
||||
|
||||
void assert_add(const string& key, uint64_t value) {
|
||||
assert(add(key, value));
|
||||
}
|
||||
};
|
||||
|
||||
// Implement 'add' directly with the new Merge operation
|
||||
class MergeBasedCounters : public Counters {
|
||||
private:
|
||||
WriteOptions merge_option_; // for merge
|
||||
|
||||
public:
|
||||
MergeBasedCounters(std::shared_ptr<DB> db, uint64_t defaultCount = 0)
|
||||
: Counters(db, defaultCount),
|
||||
merge_option_() {
|
||||
}
|
||||
|
||||
// mapped to a leveldb Merge operation
|
||||
virtual bool add(const string& key, uint64_t value) override {
|
||||
char encoded[sizeof(uint64_t)];
|
||||
EncodeFixed64(encoded, value);
|
||||
Slice slice(encoded, sizeof(uint64_t));
|
||||
auto s = db_->Merge(merge_option_, key, slice);
|
||||
|
||||
if (s.ok()) {
|
||||
return true;
|
||||
} else {
|
||||
cerr << s.ToString() << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void dumpDb(DB* db) {
|
||||
auto it = unique_ptr<Iterator>(db->NewIterator(ReadOptions()));
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
uint64_t value = DecodeFixed64(it->value().data());
|
||||
cout << it->key().ToString() << ": " << value << endl;
|
||||
}
|
||||
assert(it->status().ok()); // Check for any errors found during the scan
|
||||
}
|
||||
|
||||
void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
|
||||
FlushOptions o;
|
||||
o.wait = true;
|
||||
|
||||
counters.assert_set("a", 1);
|
||||
|
||||
if (test_compaction) db->Flush(o);
|
||||
|
||||
assert(counters.assert_get("a") == 1);
|
||||
|
||||
counters.assert_remove("b");
|
||||
|
||||
// defaut value is 0 if non-existent
|
||||
assert(counters.assert_get("b") == 0);
|
||||
|
||||
counters.assert_add("a", 2);
|
||||
|
||||
if (test_compaction) db->Flush(o);
|
||||
|
||||
// 1+2 = 3
|
||||
assert(counters.assert_get("a")== 3);
|
||||
|
||||
dumpDb(db);
|
||||
|
||||
std::cout << "1\n";
|
||||
|
||||
// 1+...+49 = ?
|
||||
uint64_t sum = 0;
|
||||
for (int i = 1; i < 50; i++) {
|
||||
counters.assert_add("b", i);
|
||||
sum += i;
|
||||
}
|
||||
assert(counters.assert_get("b") == sum);
|
||||
|
||||
std::cout << "2\n";
|
||||
dumpDb(db);
|
||||
|
||||
std::cout << "3\n";
|
||||
|
||||
if (test_compaction) {
|
||||
db->Flush(o);
|
||||
|
||||
cout << "Compaction started ...\n";
|
||||
db->CompactRange(nullptr, nullptr);
|
||||
cout << "Compaction ended\n";
|
||||
|
||||
dumpDb(db);
|
||||
|
||||
assert(counters.assert_get("a")== 3);
|
||||
assert(counters.assert_get("b") == sum);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
auto db = OpenDb();
|
||||
|
||||
{
|
||||
cout << "Test read-modify-write counters... \n";
|
||||
Counters counters(db, 0);
|
||||
testCounters(counters, db.get(), true);
|
||||
}
|
||||
|
||||
bool compact = false;
|
||||
if (argc > 1) {
|
||||
compact = true;
|
||||
cout << "Turn on Compaction\n";
|
||||
}
|
||||
|
||||
{
|
||||
cout << "Test merge-based counters... \n";
|
||||
MergeBasedCounters counters(db, 0);
|
||||
testCounters(counters, db.get(), compact);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+7
-5
@@ -52,7 +52,7 @@ class Repairer {
|
||||
options_(SanitizeOptions(dbname, &icmp_, &ipolicy_, options)),
|
||||
next_file_number_(1) {
|
||||
// TableCache can be small since we expect each table to be opened once.
|
||||
table_cache_ = new TableCache(dbname_, &options_, 10);
|
||||
table_cache_ = new TableCache(dbname_, &options_, storage_options_, 10);
|
||||
edit_ = new VersionEdit(options.num_levels);
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ class Repairer {
|
||||
std::vector<uint64_t> logs_;
|
||||
std::vector<TableInfo> tables_;
|
||||
uint64_t next_file_number_;
|
||||
const EnvOptions storage_options_;
|
||||
|
||||
Status FindFiles() {
|
||||
std::vector<std::string> filenames;
|
||||
@@ -169,7 +170,7 @@ class Repairer {
|
||||
// Open the log file
|
||||
std::string logname = LogFileName(dbname_, log);
|
||||
unique_ptr<SequentialFile> lfile;
|
||||
Status status = env_->NewSequentialFile(logname, &lfile);
|
||||
Status status = env_->NewSequentialFile(logname, &lfile, storage_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -216,7 +217,8 @@ class Repairer {
|
||||
FileMetaData meta;
|
||||
meta.number = next_file_number_++;
|
||||
Iterator* iter = mem->NewIterator();
|
||||
status = BuildTable(dbname_, env_, options_, table_cache_, iter, &meta,
|
||||
status = BuildTable(dbname_, env_, options_, storage_options_,
|
||||
table_cache_, iter, &meta,
|
||||
icmp_.user_comparator(), 0, 0);
|
||||
delete iter;
|
||||
mem->Unref();
|
||||
@@ -258,7 +260,7 @@ class Repairer {
|
||||
Status status = env_->GetFileSize(fname, &t->meta.file_size);
|
||||
if (status.ok()) {
|
||||
Iterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), t->meta.number, t->meta.file_size);
|
||||
ReadOptions(), storage_options_, t->meta.number, t->meta.file_size);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->max_sequence = 0;
|
||||
@@ -296,7 +298,7 @@ class Repairer {
|
||||
Status WriteDescriptor() {
|
||||
std::string tmp = TempFileName(dbname_, 1);
|
||||
unique_ptr<WritableFile> file;
|
||||
Status status = env_->NewWritableFile(tmp, &file);
|
||||
Status status = env_->NewWritableFile(tmp, &file, storage_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
+1
-4
@@ -59,12 +59,9 @@ class SnapshotList {
|
||||
|
||||
// retrieve all snapshot numbers. They are sorted in ascending order.
|
||||
void getAll(std::vector<SequenceNumber>& ret) {
|
||||
SnapshotImpl* s = &list_;
|
||||
if (empty()) return;
|
||||
SequenceNumber prev __attribute__((unused)) = 0;
|
||||
SnapshotImpl* s = &list_;
|
||||
while (s->next_ != &list_) {
|
||||
assert(prev <= s->next_->number_);
|
||||
assert(prev = s->next_->number_); // assignment
|
||||
ret.push_back(s->next_->number_);
|
||||
s = s ->next_;
|
||||
}
|
||||
|
||||
+30
-26
@@ -6,23 +6,16 @@
|
||||
|
||||
#include "db/filename.h"
|
||||
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "leveldb/statistics.h"
|
||||
#include "table/table.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
struct TableAndFile {
|
||||
unique_ptr<Table> table;
|
||||
};
|
||||
|
||||
static class Statistics* dbstatistics;
|
||||
|
||||
static void DeleteEntry(const Slice& key, void* value) {
|
||||
TableAndFile* tf = reinterpret_cast<TableAndFile*>(value);
|
||||
RecordTick(dbstatistics, NO_FILE_CLOSES);
|
||||
delete tf;
|
||||
Table* table = reinterpret_cast<Table*>(value);
|
||||
delete table;
|
||||
}
|
||||
|
||||
static void UnrefEntry(void* arg1, void* arg2) {
|
||||
@@ -33,18 +26,19 @@ static void UnrefEntry(void* arg1, void* arg2) {
|
||||
|
||||
TableCache::TableCache(const std::string& dbname,
|
||||
const Options* options,
|
||||
const EnvOptions& storage_options,
|
||||
int entries)
|
||||
: env_(options->env),
|
||||
dbname_(dbname),
|
||||
options_(options),
|
||||
cache_(NewLRUCache(entries, options->table_cache_numshardbits)) {
|
||||
dbstatistics = options->statistics;
|
||||
}
|
||||
storage_options_(storage_options),
|
||||
cache_(NewLRUCache(entries, options->table_cache_numshardbits)) {}
|
||||
|
||||
TableCache::~TableCache() {
|
||||
}
|
||||
|
||||
Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
|
||||
Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
Cache::Handle** handle, bool* tableIO) {
|
||||
Status s;
|
||||
char buf[sizeof(file_number)];
|
||||
@@ -58,10 +52,14 @@ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
|
||||
std::string fname = TableFileName(dbname_, file_number);
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
unique_ptr<Table> table;
|
||||
s = env_->NewRandomAccessFile(fname, &file);
|
||||
s = env_->NewRandomAccessFile(fname, &file, toptions);
|
||||
RecordTick(options_->statistics, NO_FILE_OPENS);
|
||||
if (s.ok()) {
|
||||
s = Table::Open(*options_, std::move(file), file_size, &table);
|
||||
if (options_->advise_random_on_open) {
|
||||
file->Hint(RandomAccessFile::RANDOM);
|
||||
}
|
||||
StopWatch sw(env_, options_->statistics, TABLE_OPEN_IO_MICROS);
|
||||
s = Table::Open(*options_, toptions, std::move(file), file_size, &table);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -70,36 +68,41 @@ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
|
||||
// We do not cache error results so that if the error is transient,
|
||||
// or somebody repairs the file, we recover automatically.
|
||||
} else {
|
||||
TableAndFile* tf = new TableAndFile;
|
||||
tf->table = std::move(table);
|
||||
assert(file.get() == nullptr);
|
||||
*handle = cache_->Insert(key, tf, 1, &DeleteEntry);
|
||||
*handle = cache_->Insert(key, table.release(), 1, &DeleteEntry);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
const EnvOptions& toptions,
|
||||
uint64_t file_number,
|
||||
uint64_t file_size,
|
||||
Table** tableptr) {
|
||||
Table** tableptr,
|
||||
bool for_compaction) {
|
||||
if (tableptr != nullptr) {
|
||||
*tableptr = nullptr;
|
||||
}
|
||||
|
||||
Cache::Handle* handle = nullptr;
|
||||
Status s = FindTable(file_number, file_size, &handle);
|
||||
Status s = FindTable(toptions, file_number, file_size, &handle);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
}
|
||||
|
||||
Table* table =
|
||||
reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table.get();
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
Iterator* result = table->NewIterator(options);
|
||||
result->RegisterCleanup(&UnrefEntry, cache_.get(), handle);
|
||||
if (tableptr != nullptr) {
|
||||
*tableptr = table;
|
||||
}
|
||||
|
||||
if (for_compaction) {
|
||||
table->SetupForCompaction();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -108,13 +111,14 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
uint64_t file_size,
|
||||
const Slice& k,
|
||||
void* arg,
|
||||
void (*saver)(void*, const Slice&, const Slice&, bool),
|
||||
bool (*saver)(void*, const Slice&, const Slice&, bool),
|
||||
bool* tableIO) {
|
||||
Cache::Handle* handle = nullptr;
|
||||
Status s = FindTable(file_number, file_size, &handle, tableIO);
|
||||
Status s = FindTable(storage_options_, file_number, file_size,
|
||||
&handle, tableIO);
|
||||
if (s.ok()) {
|
||||
Table* t =
|
||||
reinterpret_cast<TableAndFile*>(cache_->Value(handle))->table.get();
|
||||
reinterpret_cast<Table*>(cache_->Value(handle));
|
||||
s = t->InternalGet(options, k, arg, saver);
|
||||
cache_->Release(handle);
|
||||
}
|
||||
|
||||
+13
-6
@@ -10,9 +10,10 @@
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "port/port.h"
|
||||
#include "table/table.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -20,7 +21,8 @@ class Env;
|
||||
|
||||
class TableCache {
|
||||
public:
|
||||
TableCache(const std::string& dbname, const Options* options, int entries);
|
||||
TableCache(const std::string& dbname, const Options* options,
|
||||
const EnvOptions& storage_options, int entries);
|
||||
~TableCache();
|
||||
|
||||
// Return an iterator for the specified file number (the corresponding
|
||||
@@ -31,18 +33,21 @@ class TableCache {
|
||||
// the cache and should not be deleted, and is valid for as long as the
|
||||
// returned iterator is live.
|
||||
Iterator* NewIterator(const ReadOptions& options,
|
||||
const EnvOptions& toptions,
|
||||
uint64_t file_number,
|
||||
uint64_t file_size,
|
||||
Table** tableptr = nullptr);
|
||||
Table** tableptr = nullptr,
|
||||
bool for_compaction = false);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call (*handle_result)(arg, found_key, found_value).
|
||||
// call (*handle_result)(arg, found_key, found_value) repeatedly until
|
||||
// it returns false.
|
||||
Status Get(const ReadOptions& options,
|
||||
uint64_t file_number,
|
||||
uint64_t file_size,
|
||||
const Slice& k,
|
||||
void* arg,
|
||||
void (*handle_result)(void*, const Slice&, const Slice&, bool),
|
||||
bool (*handle_result)(void*, const Slice&, const Slice&, bool),
|
||||
bool* tableIO);
|
||||
|
||||
// Evict any entry for the specified file number
|
||||
@@ -52,9 +57,11 @@ class TableCache {
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
const Options* options_;
|
||||
const EnvOptions& storage_options_;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
|
||||
Status FindTable(uint64_t file_number, uint64_t file_size, Cache::Handle**,
|
||||
Status FindTable(const EnvOptions& toptions,
|
||||
uint64_t file_number, uint64_t file_size, Cache::Handle**,
|
||||
bool* tableIO = nullptr);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
#include "db/transaction_log_iterator_impl.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/filename.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TransactionLogIteratorImpl::TransactionLogIteratorImpl(
|
||||
const std::string& dbname,
|
||||
const Options* options,
|
||||
const EnvOptions& soptions,
|
||||
SequenceNumber& seq,
|
||||
std::vector<LogFile>* files,
|
||||
std::unique_ptr<std::vector<LogFile>> files,
|
||||
SequenceNumber const * const lastFlushedSequence) :
|
||||
dbname_(dbname),
|
||||
options_(options),
|
||||
sequenceNumber_(seq),
|
||||
files_(files),
|
||||
soptions_(soptions),
|
||||
startingSequenceNumber_(seq),
|
||||
files_(std::move(files)),
|
||||
started_(false),
|
||||
isValid_(true),
|
||||
isValid_(false),
|
||||
currentFileIndex_(0),
|
||||
lastFlushedSequence_(lastFlushedSequence) {
|
||||
assert(files_ != nullptr);
|
||||
assert(files_.get() != nullptr);
|
||||
assert(lastFlushedSequence_);
|
||||
}
|
||||
|
||||
LogReporter
|
||||
TransactionLogIteratorImpl::NewLogReporter(const uint64_t logNumber) {
|
||||
LogReporter reporter;
|
||||
reporter.env = options_->env;
|
||||
reporter.info_log = options_->info_log.get();
|
||||
reporter.log_number = logNumber;
|
||||
return reporter;
|
||||
reporter_.env = options_->env;
|
||||
reporter_.info_log = options_->info_log.get();
|
||||
}
|
||||
|
||||
Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
@@ -36,15 +33,15 @@ Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
Env* env = options_->env;
|
||||
if (logFile.type == kArchivedLogFile) {
|
||||
std::string fname = ArchivedLogFileName(dbname_, logFile.logNumber);
|
||||
return env->NewSequentialFile(fname, file);
|
||||
return env->NewSequentialFile(fname, file, soptions_);
|
||||
} else {
|
||||
std::string fname = LogFileName(dbname_, logFile.logNumber);
|
||||
Status status = env->NewSequentialFile(fname, file);
|
||||
Status status = env->NewSequentialFile(fname, file, soptions_);
|
||||
if (!status.ok()) {
|
||||
// If cannot open file in DB directory.
|
||||
// Try the archive dir, as it could have moved in the meanwhile.
|
||||
fname = ArchivedLogFileName(dbname_, logFile.logNumber);
|
||||
status = env->NewSequentialFile(fname, file);
|
||||
status = env->NewSequentialFile(fname, file, soptions_);
|
||||
if (!status.ok()) {
|
||||
return Status::IOError(" Requested file not present in the dir");
|
||||
}
|
||||
@@ -70,95 +67,84 @@ bool TransactionLogIteratorImpl::Valid() {
|
||||
}
|
||||
|
||||
void TransactionLogIteratorImpl::Next() {
|
||||
LogFile currentLogFile = files_.get()->at(currentFileIndex_);
|
||||
|
||||
// First seek to the given seqNo. in the current file.
|
||||
LogFile currentLogFile = files_->at(currentFileIndex_);
|
||||
LogReporter reporter = NewLogReporter(currentLogFile.logNumber);
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
|
||||
if (!started_) {
|
||||
started_ = true; // this piece only runs onced.
|
||||
isValid_ = false;
|
||||
if (sequenceNumber_ > *lastFlushedSequence_) {
|
||||
if (startingSequenceNumber_ > *lastFlushedSequence_) {
|
||||
currentStatus_ = Status::IOError("Looking for a sequence, "
|
||||
"which is not flushed yet.");
|
||||
return;
|
||||
}
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status status = OpenLogFile(currentLogFile, &file);
|
||||
if (!status.ok()) {
|
||||
currentStatus_ = status;
|
||||
Status s = OpenLogReader(currentLogFile);
|
||||
if (!s.ok()) {
|
||||
currentStatus_ = s;
|
||||
isValid_ = false;
|
||||
return;
|
||||
}
|
||||
assert(file);
|
||||
unique_ptr<log::Reader> reader(
|
||||
new log::Reader(std::move(file), &reporter, true, 0));
|
||||
assert(reader);
|
||||
while (reader->ReadRecord(&record, &scratch)) {
|
||||
while (currentLogReader_->ReadRecord(&record, &scratch)) {
|
||||
if (record.size() < 12) {
|
||||
reporter.Corruption(
|
||||
reporter_.Corruption(
|
||||
record.size(), Status::Corruption("log record too small"));
|
||||
continue;
|
||||
}
|
||||
UpdateCurrentWriteBatch(record);
|
||||
if (currentSequence_ >= sequenceNumber_) {
|
||||
assert(currentSequence_ < *lastFlushedSequence_);
|
||||
if (currentSequence_ >= startingSequenceNumber_) {
|
||||
assert(currentSequence_ <= *lastFlushedSequence_);
|
||||
isValid_ = true;
|
||||
currentLogReader_ = std::move(reader);
|
||||
break;
|
||||
} else {
|
||||
isValid_ = false;
|
||||
}
|
||||
}
|
||||
if (!isValid_) {
|
||||
// TODO read the entire first file. and did not find the seq number.
|
||||
// Error out.
|
||||
currentStatus_ =
|
||||
Status::NotFound("Did not find the Seq no. in first file");
|
||||
if (isValid_) {
|
||||
// Done for this iteration
|
||||
return;
|
||||
}
|
||||
started_ = true;
|
||||
} else {
|
||||
LOOK_NEXT_FILE:
|
||||
}
|
||||
bool openNextFile = true;
|
||||
while(openNextFile) {
|
||||
assert(currentLogReader_);
|
||||
bool openNextFile = true;
|
||||
|
||||
if (currentSequence_ == *lastFlushedSequence_) {
|
||||
// The last update has been read. and next is being called.
|
||||
isValid_ = false;
|
||||
currentStatus_ = Status::OK();
|
||||
}
|
||||
|
||||
while (currentLogReader_->ReadRecord(&record, &scratch) &&
|
||||
currentSequence_ < *lastFlushedSequence_) {
|
||||
if (record.size() < 12) {
|
||||
reporter.Corruption(
|
||||
record.size(), Status::Corruption("log record too small"));
|
||||
continue;
|
||||
} else {
|
||||
UpdateCurrentWriteBatch(record);
|
||||
openNextFile = false;
|
||||
break;
|
||||
if (currentSequence_ < *lastFlushedSequence_) {
|
||||
if (currentLogReader_->IsEOF()) {
|
||||
currentLogReader_->UnmarkEOF();
|
||||
}
|
||||
while (currentLogReader_->ReadRecord(&record, &scratch)) {
|
||||
if (record.size() < 12) {
|
||||
reporter_.Corruption(
|
||||
record.size(), Status::Corruption("log record too small"));
|
||||
continue;
|
||||
} else {
|
||||
UpdateCurrentWriteBatch(record);
|
||||
openNextFile = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openNextFile) {
|
||||
if (currentFileIndex_ < files_->size() - 1) {
|
||||
if (currentFileIndex_ < files_.get()->size() - 1) {
|
||||
++currentFileIndex_;
|
||||
currentLogReader_.reset();
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status status = OpenLogFile(files_->at(currentFileIndex_), &file);
|
||||
Status status = OpenLogReader(files_.get()->at(currentFileIndex_));
|
||||
if (!status.ok()) {
|
||||
isValid_ = false;
|
||||
currentStatus_ = status;
|
||||
return;
|
||||
}
|
||||
currentLogReader_.reset(
|
||||
new log::Reader(std::move(file), &reporter, true, 0));
|
||||
goto LOOK_NEXT_FILE;
|
||||
} else {
|
||||
// LOOKED AT FILES. WE ARE DONE HERE.
|
||||
isValid_ = false;
|
||||
currentStatus_ = Status::IOError(" NO MORE DATA LEFT");
|
||||
openNextFile = false;
|
||||
if (currentSequence_ == *lastFlushedSequence_) {
|
||||
currentStatus_ = Status::OK();
|
||||
} else {
|
||||
currentStatus_ = Status::IOError(" NO MORE DATA LEFT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +153,20 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
WriteBatchInternal::SetContents(batch, record);
|
||||
currentSequence_ = WriteBatchInternal::Sequence(batch);
|
||||
currentBatch_.reset(batch);
|
||||
isValid_ = true;
|
||||
currentStatus_ = Status::OK();
|
||||
}
|
||||
|
||||
Status TransactionLogIteratorImpl::OpenLogReader(const LogFile& logFile) {
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status status = OpenLogFile(logFile, &file);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
assert(file);
|
||||
currentLogReader_.reset(
|
||||
new log::Reader(std::move(file), &reporter_, true, 0)
|
||||
);
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace leveldb
|
||||
@@ -16,10 +16,8 @@ namespace leveldb {
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
uint64_t log_number;
|
||||
virtual void Corruption(size_t bytes, const Status& s) {
|
||||
Log(info_log, "%ld: dropping %d bytes; %s",
|
||||
log_number, static_cast<int>(bytes), s.ToString().c_str());
|
||||
Log(info_log, "dropping %zu bytes; %s", bytes, s.ToString().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,15 +25,11 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
public:
|
||||
TransactionLogIteratorImpl(const std::string& dbname,
|
||||
const Options* options,
|
||||
const EnvOptions& soptions,
|
||||
SequenceNumber& seqNum,
|
||||
std::vector<LogFile>* files,
|
||||
std::unique_ptr<std::vector<LogFile>> files,
|
||||
SequenceNumber const * const lastFlushedSequence);
|
||||
|
||||
virtual ~TransactionLogIteratorImpl() {
|
||||
// TODO move to cc file.
|
||||
delete files_;
|
||||
}
|
||||
|
||||
virtual bool Valid();
|
||||
|
||||
virtual void Next();
|
||||
@@ -47,8 +41,9 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
private:
|
||||
const std::string& dbname_;
|
||||
const Options* options_;
|
||||
const uint64_t sequenceNumber_;
|
||||
const std::vector<LogFile>* files_;
|
||||
const EnvOptions& soptions_;
|
||||
const uint64_t startingSequenceNumber_;
|
||||
std::unique_ptr<std::vector<LogFile>> files_;
|
||||
bool started_;
|
||||
bool isValid_; // not valid when it starts of.
|
||||
Status currentStatus_;
|
||||
@@ -56,12 +51,13 @@ class TransactionLogIteratorImpl : public TransactionLogIterator {
|
||||
std::unique_ptr<WriteBatch> currentBatch_;
|
||||
unique_ptr<log::Reader> currentLogReader_;
|
||||
Status OpenLogFile(const LogFile& logFile, unique_ptr<SequentialFile>* file);
|
||||
LogReporter NewLogReporter(uint64_t logNumber);
|
||||
LogReporter reporter_;
|
||||
SequenceNumber const * const lastFlushedSequence_;
|
||||
// represents the sequence number being read currently.
|
||||
SequenceNumber currentSequence_;
|
||||
|
||||
void UpdateCurrentWriteBatch(const Slice& record);
|
||||
Status OpenLogReader(const LogFile& file);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,11 @@ class VersionEdit {
|
||||
deleted_files_.insert(std::make_pair(level, file));
|
||||
}
|
||||
|
||||
// Number of edits
|
||||
int NumEntries() {
|
||||
return new_files_.size() + deleted_files_.size();
|
||||
}
|
||||
|
||||
void EncodeTo(std::string* dst) const;
|
||||
Status DecodeFrom(const Slice& src);
|
||||
|
||||
|
||||
+194
-66
@@ -12,11 +12,13 @@
|
||||
#include "db/memtable.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
#include "leveldb/table_builder.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -178,32 +180,39 @@ class Version::LevelFileNumIterator : public Iterator {
|
||||
|
||||
static Iterator* GetFileIterator(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& file_value) {
|
||||
const EnvOptions& soptions,
|
||||
const Slice& file_value,
|
||||
bool for_compaction) {
|
||||
TableCache* cache = reinterpret_cast<TableCache*>(arg);
|
||||
if (file_value.size() != 16) {
|
||||
return NewErrorIterator(
|
||||
Status::Corruption("FileReader invoked with unexpected value"));
|
||||
} else {
|
||||
return cache->NewIterator(options,
|
||||
soptions,
|
||||
DecodeFixed64(file_value.data()),
|
||||
DecodeFixed64(file_value.data() + 8));
|
||||
DecodeFixed64(file_value.data() + 8),
|
||||
nullptr /* don't need reference to table*/,
|
||||
for_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* Version::NewConcatenatingIterator(const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
int level) const {
|
||||
return NewTwoLevelIterator(
|
||||
new LevelFileNumIterator(vset_->icmp_, &files_[level]),
|
||||
&GetFileIterator, vset_->table_cache_, options);
|
||||
&GetFileIterator, vset_->table_cache_, options, soptions);
|
||||
}
|
||||
|
||||
void Version::AddIterators(const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
std::vector<Iterator*>* iters) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (size_t i = 0; i < files_[0].size(); i++) {
|
||||
iters->push_back(
|
||||
vset_->table_cache_->NewIterator(
|
||||
options, files_[0][i]->number, files_[0][i]->file_size));
|
||||
options, soptions, files_[0][i]->number, files_[0][i]->file_size));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
@@ -211,7 +220,7 @@ void Version::AddIterators(const ReadOptions& options,
|
||||
// lazily.
|
||||
for (int level = 1; level < vset_->NumberLevels(); level++) {
|
||||
if (!files_[level].empty()) {
|
||||
iters->push_back(NewConcatenatingIterator(options, level));
|
||||
iters->push_back(NewConcatenatingIterator(options, soptions, level));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,29 +232,78 @@ enum SaverState {
|
||||
kFound,
|
||||
kDeleted,
|
||||
kCorrupt,
|
||||
kMerge // value contains the current merge result (the operand)
|
||||
};
|
||||
struct Saver {
|
||||
SaverState state;
|
||||
const Comparator* ucmp;
|
||||
Slice user_key;
|
||||
std::string* value;
|
||||
const MergeOperator* merge_operator;
|
||||
Logger* logger;
|
||||
bool didIO; // did we do any disk io?
|
||||
};
|
||||
}
|
||||
static void SaveValue(void* arg, const Slice& ikey, const Slice& v, bool didIO){
|
||||
static bool SaveValue(void* arg, const Slice& ikey, const Slice& v, bool didIO){
|
||||
Saver* s = reinterpret_cast<Saver*>(arg);
|
||||
ParsedInternalKey parsed_key;
|
||||
// TODO: didIO and Merge?
|
||||
s->didIO = didIO;
|
||||
if (!ParseInternalKey(ikey, &parsed_key)) {
|
||||
// TODO: what about corrupt during Merge?
|
||||
s->state = kCorrupt;
|
||||
} else {
|
||||
if (s->ucmp->Compare(parsed_key.user_key, s->user_key) == 0) {
|
||||
s->state = (parsed_key.type == kTypeValue) ? kFound : kDeleted;
|
||||
if (s->state == kFound) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
switch (parsed_key.type) {
|
||||
case kTypeValue:
|
||||
if (kNotFound == s->state) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, &v, operand,
|
||||
s->value, s->logger);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
s->state = kFound;
|
||||
return false;
|
||||
|
||||
case kTypeMerge:
|
||||
if (kNotFound == s->state) {
|
||||
s->state = kMerge;
|
||||
s->value->assign(v.data(), v.size());
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, &v, operand,
|
||||
s->value, s->logger);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return true;
|
||||
|
||||
case kTypeDeletion:
|
||||
if (kNotFound == s->state) {
|
||||
s->state = kDeleted;
|
||||
} else if (kMerge == s->state) {
|
||||
std::string operand;
|
||||
swap(operand, *s->value);
|
||||
s->merge_operator->Merge(s->user_key, nullptr, operand,
|
||||
s->value, s->logger);
|
||||
s->state = kFound;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// s->state could be Corrupt, merge or notfound
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool NewestFirst(FileMetaData* a, FileMetaData* b) {
|
||||
@@ -265,14 +323,28 @@ Version::Version(VersionSet* vset, uint64_t version_number)
|
||||
files_ = new std::vector<FileMetaData*>[vset->NumberLevels()];
|
||||
}
|
||||
|
||||
Status Version::Get(const ReadOptions& options,
|
||||
const LookupKey& k,
|
||||
std::string* value,
|
||||
GetStats* stats) {
|
||||
void Version::Get(const ReadOptions& options,
|
||||
const LookupKey& k,
|
||||
std::string* value,
|
||||
Status *status,
|
||||
GetStats* stats,
|
||||
const Options& db_options) {
|
||||
Slice ikey = k.internal_key();
|
||||
Slice user_key = k.user_key();
|
||||
const Comparator* ucmp = vset_->icmp_.user_comparator();
|
||||
Status s;
|
||||
|
||||
auto merge_operator = db_options.merge_operator;
|
||||
auto logger = db_options.info_log;
|
||||
|
||||
assert(status->ok() || status->IsMergeInProgress());
|
||||
Saver saver;
|
||||
saver.state = status->ok()? kNotFound : kMerge;
|
||||
saver.ucmp = ucmp;
|
||||
saver.user_key = user_key;
|
||||
saver.value = value;
|
||||
saver.merge_operator = merge_operator;
|
||||
saver.logger = logger.get();
|
||||
saver.didIO = false;
|
||||
|
||||
stats->seek_file = nullptr;
|
||||
stats->seek_file_level = -1;
|
||||
@@ -321,24 +393,21 @@ Status Version::Get(const ReadOptions& options,
|
||||
} else {
|
||||
files = &tmp2;
|
||||
num_files = 1;
|
||||
// TODO, is level 1-n files all disjoint in user key space?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < num_files; ++i) {
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < num_files; ++i) {
|
||||
FileMetaData* f = files[i];
|
||||
Saver saver;
|
||||
saver.state = kNotFound;
|
||||
saver.ucmp = ucmp;
|
||||
saver.user_key = user_key;
|
||||
saver.value = value;
|
||||
saver.didIO = false;
|
||||
bool tableIO = false;
|
||||
s = vset_->table_cache_->Get(options, f->number, f->file_size,
|
||||
ikey, &saver, SaveValue, &tableIO);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
*status = vset_->table_cache_->Get(options, f->number, f->file_size,
|
||||
ikey, &saver, SaveValue, &tableIO);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_file_read != nullptr && stats->seek_file == nullptr) {
|
||||
@@ -363,18 +432,33 @@ Status Version::Get(const ReadOptions& options,
|
||||
case kNotFound:
|
||||
break; // Keep searching in other files
|
||||
case kFound:
|
||||
return s;
|
||||
return;
|
||||
case kDeleted:
|
||||
s = Status::NotFound(Slice()); // Use empty error message for speed
|
||||
return s;
|
||||
*status = Status::NotFound(Slice()); // Use empty error message for speed
|
||||
return;
|
||||
case kCorrupt:
|
||||
s = Status::Corruption("corrupted key for ", user_key);
|
||||
return s;
|
||||
*status = Status::Corruption("corrupted key for ", user_key);
|
||||
return;
|
||||
case kMerge:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::NotFound(Slice()); // Use an empty error message for speed
|
||||
|
||||
if (kMerge == saver.state) {
|
||||
// merge operand is in *value and we hit the beginning of the key history
|
||||
// do a final merge of nullptr and operand;
|
||||
std::string operand;
|
||||
swap(operand, *value);
|
||||
merge_operator->Merge(user_key, nullptr, operand,
|
||||
value, logger.get());
|
||||
*status = Status::OK();
|
||||
return;
|
||||
} else {
|
||||
*status = Status::NotFound(Slice()); // Use an empty error message for speed
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool Version::UpdateStats(const GetStats& stats) {
|
||||
@@ -559,7 +643,7 @@ void Version::ExtendOverlappingInputs(
|
||||
const Slice& user_begin,
|
||||
const Slice& user_end,
|
||||
std::vector<FileMetaData*>* inputs,
|
||||
int midIndex) {
|
||||
unsigned int midIndex) {
|
||||
|
||||
const Comparator* user_cmp = vset_->icmp_.user_comparator();
|
||||
#ifndef NDEBUG
|
||||
@@ -736,13 +820,16 @@ class VersionSet::Builder {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CheckConsistencyForDeletes(VersionEdit* edit, int number, int level) {
|
||||
void CheckConsistencyForDeletes(
|
||||
VersionEdit* edit,
|
||||
unsigned int number,
|
||||
int level) {
|
||||
#ifndef NDEBUG
|
||||
// a file to be deleted better exist in the previous version
|
||||
bool found = false;
|
||||
for (int l = 0; !found && l < edit->number_levels_; l++) {
|
||||
const std::vector<FileMetaData*>& base_files = base_->files_[l];
|
||||
for (int i = 0; i < base_files.size(); i++) {
|
||||
for (unsigned int i = 0; i < base_files.size(); i++) {
|
||||
FileMetaData* f = base_files[i];
|
||||
if (f->number == number) {
|
||||
found = true;
|
||||
@@ -884,6 +971,7 @@ class VersionSet::Builder {
|
||||
|
||||
VersionSet::VersionSet(const std::string& dbname,
|
||||
const Options* options,
|
||||
const EnvOptions& storage_options,
|
||||
TableCache* table_cache,
|
||||
const InternalKeyComparator* cmp)
|
||||
: env_(options->env),
|
||||
@@ -901,7 +989,9 @@ VersionSet::VersionSet(const std::string& dbname,
|
||||
current_(nullptr),
|
||||
compactions_in_progress_(options_->num_levels),
|
||||
current_version_number_(0),
|
||||
last_observed_manifest_size_(0) {
|
||||
last_observed_manifest_size_(0),
|
||||
storage_options_(storage_options),
|
||||
storage_options_compactions_(storage_options_) {
|
||||
compact_pointer_ = new std::string[options_->num_levels];
|
||||
Init(options_->num_levels);
|
||||
AppendVersion(new Version(this, current_version_number_++));
|
||||
@@ -923,7 +1013,8 @@ void VersionSet::Init(int num_levels) {
|
||||
for (int i = 0; i < num_levels; i++) {
|
||||
if (i > 1) {
|
||||
max_file_size_[i] = max_file_size_[i-1] * target_file_size_multiplier;
|
||||
level_max_bytes_[i] = level_max_bytes_[i-1] * max_bytes_multiplier;
|
||||
level_max_bytes_[i] = level_max_bytes_[i-1] * max_bytes_multiplier *
|
||||
options_->max_bytes_for_level_multiplier_additional[i-1];
|
||||
} else {
|
||||
max_file_size_[i] = options_->target_file_size_base;
|
||||
level_max_bytes_[i] = options_->max_bytes_for_level_base;
|
||||
@@ -999,7 +1090,8 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu,
|
||||
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);
|
||||
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());
|
||||
@@ -1032,8 +1124,10 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu,
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (options_->use_fsync) {
|
||||
StopWatch sw(env_, options_->statistics, MANIFEST_FILE_SYNC_MICROS);
|
||||
s = descriptor_log_->file()->Fsync();
|
||||
} else {
|
||||
StopWatch sw(env_, options_->statistics, MANIFEST_FILE_SYNC_MICROS);
|
||||
s = descriptor_log_->file()->Sync();
|
||||
}
|
||||
}
|
||||
@@ -1144,7 +1238,7 @@ Status VersionSet::Recover() {
|
||||
|
||||
std::string dscname = dbname_ + "/" + current;
|
||||
unique_ptr<SequentialFile> file;
|
||||
s = env_->NewSequentialFile(dscname, &file);
|
||||
s = env_->NewSequentialFile(dscname, &file, storage_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1266,7 +1360,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
|
||||
// Open the specified manifest file.
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s = options.env->NewSequentialFile(dscname, &file);
|
||||
Status s = options.env->NewSequentialFile(dscname, &file, storage_options_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1576,7 +1670,7 @@ bool VersionSet::ManifestContains(const std::string& record) const {
|
||||
std::string fname = DescriptorFileName(dbname_, manifest_file_number_);
|
||||
Log(options_->info_log, "ManifestContains: checking %s\n", fname.c_str());
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s = env_->NewSequentialFile(fname, &file);
|
||||
Status s = env_->NewSequentialFile(fname, &file, storage_options_);
|
||||
if (!s.ok()) {
|
||||
Log(options_->info_log, "ManifestContains: %s\n", s.ToString().c_str());
|
||||
Log(options_->info_log,
|
||||
@@ -1620,7 +1714,8 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
|
||||
// approximate offset of "ikey" within the table.
|
||||
Table* tableptr;
|
||||
Iterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), files[i]->number, files[i]->file_size, &tableptr);
|
||||
ReadOptions(), storage_options_, files[i]->number,
|
||||
files[i]->file_size, &tableptr);
|
||||
if (tableptr != nullptr) {
|
||||
result += tableptr->ApproximateOffsetOf(ikey.Encode());
|
||||
}
|
||||
@@ -1631,14 +1726,26 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void VersionSet::AddLiveFiles(std::set<uint64_t>* live) {
|
||||
void VersionSet::AddLiveFiles(std::vector<uint64_t>* live_list) {
|
||||
// pre-calculate space requirement
|
||||
int64_t total_files = 0;
|
||||
for (Version* v = dummy_versions_.next_;
|
||||
v != &dummy_versions_;
|
||||
v = v->next_) {
|
||||
for (int level = 0; level < NumberLevels(); level++) {
|
||||
const std::vector<FileMetaData*>& files = v->files_[level];
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
live->insert(files[i]->number);
|
||||
total_files += v->files_[level].size();
|
||||
}
|
||||
}
|
||||
|
||||
// just one time extension to the right size
|
||||
live_list->reserve(live_list->size() + total_files);
|
||||
|
||||
for (Version* v = dummy_versions_.next_;
|
||||
v != &dummy_versions_;
|
||||
v = v->next_) {
|
||||
for (int level = 0; level < NumberLevels(); level++) {
|
||||
for (const auto& f : v->files_[level]) {
|
||||
live_list->push_back(f->number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1657,10 +1764,8 @@ void VersionSet::AddLiveFilesCurrentVersion(std::set<uint64_t>* live) {
|
||||
int64_t VersionSet::NumLevelBytes(int level) const {
|
||||
assert(level >= 0);
|
||||
assert(level < NumberLevels());
|
||||
if(current_ && level < (int)current_->files_->size())
|
||||
return TotalFileSize(current_->files_[level]);
|
||||
else
|
||||
return 0;
|
||||
assert(current_);
|
||||
return TotalFileSize(current_->files_[level]);
|
||||
}
|
||||
|
||||
int64_t VersionSet::MaxNextLevelOverlappingBytes() {
|
||||
@@ -1734,13 +1839,16 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
const std::vector<FileMetaData*>& files = c->inputs_[which];
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
list[num++] = table_cache_->NewIterator(
|
||||
options, files[i]->number, files[i]->file_size);
|
||||
options, storage_options_compactions_,
|
||||
files[i]->number, files[i]->file_size, nullptr,
|
||||
true /* for compaction */);
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
list[num++] = NewTwoLevelIterator(
|
||||
new Version::LevelFileNumIterator(icmp_, &c->inputs_[which]),
|
||||
&GetFileIterator, table_cache_, options);
|
||||
&GetFileIterator, table_cache_, options, storage_options_,
|
||||
true /* for compaction */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1953,14 +2061,23 @@ Compaction* VersionSet::PickCompaction() {
|
||||
}
|
||||
|
||||
// Find compactions needed by seeks
|
||||
if (c == nullptr && (current_->file_to_compact_ != nullptr)) {
|
||||
FileMetaData* f = current_->file_to_compact_;
|
||||
if (c == nullptr && f != nullptr && !f->being_compacted) {
|
||||
|
||||
level = current_->file_to_compact_level_;
|
||||
int parent_index = -1;
|
||||
|
||||
// Only allow one level 0 compaction at a time.
|
||||
// Do not pick this file if its parents at level+1 are being compacted.
|
||||
if (level != 0 || compactions_in_progress_[0].empty()) {
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level),
|
||||
MaxGrandParentOverlapBytes(level), NumberLevels(), true);
|
||||
c->inputs_[0].push_back(current_->file_to_compact_);
|
||||
if(!ParentRangeInCompaction(&f->smallest, &f->largest, level,
|
||||
&parent_index)) {
|
||||
c = new Compaction(level, MaxFileSizeForLevel(level),
|
||||
MaxGrandParentOverlapBytes(level), NumberLevels(), true);
|
||||
c->inputs_[0].push_back(f);
|
||||
c->parent_index_ = parent_index;
|
||||
current_->file_to_compact_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1983,6 +2100,11 @@ Compaction* VersionSet::PickCompaction() {
|
||||
// which will include the picked file.
|
||||
c->inputs_[0].clear();
|
||||
current_->GetOverlappingInputs(0, &smallest, &largest, &c->inputs_[0]);
|
||||
|
||||
// If we include more L0 files in the same compaction run it can
|
||||
// cause the 'smallest' and 'largest' key to get extended to a
|
||||
// larger range. So, re-invoke GetRange to get the new key range
|
||||
GetRange(c->inputs_[0], &smallest, &largest);
|
||||
if (ParentRangeInCompaction(&smallest, &largest,
|
||||
level, &c->parent_index_)) {
|
||||
delete c;
|
||||
@@ -2105,16 +2227,23 @@ Compaction* VersionSet::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
const uint64_t limit = MaxFileSizeForLevel(level) *
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if (level > 0) {
|
||||
|
||||
const uint64_t limit = MaxFileSizeForLevel(level) *
|
||||
options_->source_compaction_factor;
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i < inputs.size(); i++) {
|
||||
uint64_t s = inputs[i]->file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
inputs.resize(i + 1);
|
||||
break;
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
uint64_t s = inputs[i]->file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
inputs.resize(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2146,16 +2275,15 @@ Compaction::Compaction(int level, uint64_t target_file_size,
|
||||
overlapped_bytes_(0),
|
||||
base_index_(-1),
|
||||
parent_index_(-1),
|
||||
score_(0) {
|
||||
score_(0),
|
||||
level_ptrs_(std::vector<size_t>(number_levels)) {
|
||||
edit_ = new VersionEdit(number_levels_);
|
||||
level_ptrs_ = new size_t[number_levels_];
|
||||
for (int i = 0; i < number_levels_; i++) {
|
||||
level_ptrs_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
delete[] level_ptrs_;
|
||||
delete edit_;
|
||||
if (input_version_ != nullptr) {
|
||||
input_version_->Unref();
|
||||
|
||||
+20
-10
@@ -63,7 +63,8 @@ class Version {
|
||||
// Append to *iters a sequence of iterators that will
|
||||
// yield the contents of this Version when merged together.
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
void AddIterators(const ReadOptions&, std::vector<Iterator*>* iters);
|
||||
void AddIterators(const ReadOptions&, const EnvOptions& soptions,
|
||||
std::vector<Iterator*>* iters);
|
||||
|
||||
// Lookup the value for key. If found, store it in *val and
|
||||
// return OK. Else return a non-OK status. Fills *stats.
|
||||
@@ -72,8 +73,8 @@ class Version {
|
||||
FileMetaData* seek_file;
|
||||
int seek_file_level;
|
||||
};
|
||||
Status Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
GetStats* stats);
|
||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
Status* status, GetStats* stats, const Options& db_option);
|
||||
|
||||
// Adds "stats" into the current state. Returns true if a new
|
||||
// compaction may need to be triggered, false otherwise.
|
||||
@@ -106,7 +107,7 @@ class Version {
|
||||
const Slice& begin, // nullptr means before all keys
|
||||
const Slice& end, // nullptr means after all keys
|
||||
std::vector<FileMetaData*>* inputs,
|
||||
int index); // start extending from this index
|
||||
unsigned int index); // start extending from this index
|
||||
|
||||
// Returns true iff some file in the specified level overlaps
|
||||
// some part of [*smallest_user_key,*largest_user_key].
|
||||
@@ -136,7 +137,9 @@ class Version {
|
||||
friend class VersionSet;
|
||||
|
||||
class LevelFileNumIterator;
|
||||
Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const;
|
||||
Iterator* NewConcatenatingIterator(const ReadOptions&,
|
||||
const EnvOptions& soptions,
|
||||
int level) const;
|
||||
|
||||
VersionSet* vset_; // VersionSet to which this Version belongs
|
||||
Version* next_; // Next version in linked list
|
||||
@@ -204,6 +207,7 @@ class VersionSet {
|
||||
public:
|
||||
VersionSet(const std::string& dbname,
|
||||
const Options* options,
|
||||
const EnvOptions& storage_options,
|
||||
TableCache* table_cache,
|
||||
const InternalKeyComparator*);
|
||||
~VersionSet();
|
||||
@@ -322,8 +326,7 @@ class VersionSet {
|
||||
}
|
||||
|
||||
// Add all files listed in any live version to *live.
|
||||
// May also mutate some internal state.
|
||||
void AddLiveFiles(std::set<uint64_t>* live);
|
||||
void AddLiveFiles(std::vector<uint64_t>* live_list);
|
||||
|
||||
// Add all files listed in the current version to *live.
|
||||
void AddLiveFilesCurrentVersion(std::set<uint64_t>* live);
|
||||
@@ -378,6 +381,8 @@ class VersionSet {
|
||||
// Get the max file size in a given level.
|
||||
uint64_t MaxFileSizeForLevel(int level);
|
||||
|
||||
double MaxBytesForLevel(int level);
|
||||
|
||||
private:
|
||||
class Builder;
|
||||
struct ManifestWriter;
|
||||
@@ -407,8 +412,6 @@ class VersionSet {
|
||||
|
||||
bool ManifestContains(const std::string& record) const;
|
||||
|
||||
double MaxBytesForLevel(int level);
|
||||
|
||||
int64_t ExpandedCompactionByteSizeLimit(int level);
|
||||
|
||||
int64_t MaxGrandParentOverlapBytes(int level);
|
||||
@@ -454,6 +457,13 @@ class VersionSet {
|
||||
// Save us the cost of checking file size twice in LogAndApply
|
||||
uint64_t last_observed_manifest_size_;
|
||||
|
||||
// storage options for all reads and writes except compactions
|
||||
const EnvOptions& storage_options_;
|
||||
|
||||
// storage options used for compactions. This is a copy of
|
||||
// storage_options_ but with readaheads set to readahead_compactions_.
|
||||
const EnvOptions storage_options_compactions_;
|
||||
|
||||
// No copying allowed
|
||||
VersionSet(const VersionSet&);
|
||||
void operator=(const VersionSet&);
|
||||
@@ -557,7 +567,7 @@ class Compaction {
|
||||
// is that we are positioned at one of the file ranges for each
|
||||
// higher level than the ones involved in this compaction (i.e. for
|
||||
// all L >= level_ + 2).
|
||||
size_t* level_ptrs_;
|
||||
std::vector<size_t> level_ptrs_;
|
||||
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool);
|
||||
|
||||
+27
-1
@@ -7,7 +7,8 @@
|
||||
// count: fixed32
|
||||
// data: record[count]
|
||||
// record :=
|
||||
// kTypeValue varstring varstring |
|
||||
// kTypeValue varstring varstring
|
||||
// kTypeMerge varstring varstring
|
||||
// kTypeDeletion varstring
|
||||
// varstring :=
|
||||
// len: varint32
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "db/memtable.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/coding.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -34,6 +36,10 @@ WriteBatch::~WriteBatch() { }
|
||||
|
||||
WriteBatch::Handler::~Handler() { }
|
||||
|
||||
void WriteBatch::Handler::Merge(const Slice& key, const Slice& value) {
|
||||
throw std::runtime_error("Handler::Merge not implemented!");
|
||||
}
|
||||
|
||||
void WriteBatch::Clear() {
|
||||
rep_.clear();
|
||||
rep_.resize(kHeader);
|
||||
@@ -68,6 +74,14 @@ Status WriteBatch::Iterate(Handler* handler) const {
|
||||
return Status::Corruption("bad WriteBatch Delete");
|
||||
}
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (GetLengthPrefixedSlice(&input, &key) &&
|
||||
GetLengthPrefixedSlice(&input, &value)) {
|
||||
handler->Merge(key, value);
|
||||
} else {
|
||||
return Status::Corruption("bad WriteBatch Merge");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
}
|
||||
@@ -108,6 +122,14 @@ void WriteBatch::Delete(const Slice& key) {
|
||||
PutLengthPrefixedSlice(&rep_, key);
|
||||
}
|
||||
|
||||
void WriteBatch::Merge(const Slice& key, const Slice& value) {
|
||||
WriteBatchInternal::SetCount(this, WriteBatchInternal::Count(this) + 1);
|
||||
rep_.push_back(static_cast<char>(kTypeMerge));
|
||||
PutLengthPrefixedSlice(&rep_, key);
|
||||
PutLengthPrefixedSlice(&rep_, value);
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
class MemTableInserter : public WriteBatch::Handler {
|
||||
public:
|
||||
@@ -118,6 +140,10 @@ class MemTableInserter : public WriteBatch::Handler {
|
||||
mem_->Add(sequence_, kTypeValue, key, value);
|
||||
sequence_++;
|
||||
}
|
||||
virtual void Merge(const Slice& key, const Slice& value) {
|
||||
mem_->Add(sequence_, kTypeMerge, key, value);
|
||||
sequence_++;
|
||||
}
|
||||
virtual void Delete(const Slice& key) {
|
||||
mem_->Add(sequence_, kTypeDeletion, key, Slice());
|
||||
sequence_++;
|
||||
|
||||
@@ -33,6 +33,14 @@ static std::string PrintContents(WriteBatch* b) {
|
||||
state.append(")");
|
||||
count++;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
state.append("Merge(");
|
||||
state.append(ikey.user_key.ToString());
|
||||
state.append(", ");
|
||||
state.append(iter->value().ToString());
|
||||
state.append(")");
|
||||
count++;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
state.append("Delete(");
|
||||
state.append(ikey.user_key.ToString());
|
||||
|
||||
+2
-2
@@ -4,10 +4,10 @@
|
||||
# fbcode settings. It uses the latest g++ compiler and also
|
||||
# uses jemalloc
|
||||
|
||||
TOOLCHAIN_REV=3748fabb2c5e033009597bae1f9ef8bf4b218581
|
||||
TOOLCHAIN_REV=83eb773e262fa705eaebbf3de40db71d53fabf2e
|
||||
TOOLCHAIN_EXECUTABLES="/mnt/gvfs/third-party/$TOOLCHAIN_REV/centos5.2-native"
|
||||
TOOLCHAIN_LIB_BASE="/mnt/gvfs/third-party/$TOOLCHAIN_REV/gcc-4.7.1-glibc-2.14.1"
|
||||
TOOL_JEMALLOC=jemalloc-3.0.0/2f45f3a
|
||||
TOOL_JEMALLOC=jemalloc-3.3.1/2f45f3a
|
||||
|
||||
# location of libhdfs libraries
|
||||
if test "$USE_HDFS"; then
|
||||
|
||||
+6
-3
@@ -235,15 +235,18 @@ class HdfsEnv : public Env {
|
||||
}
|
||||
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
unique_ptr<SequentialFile>* result);
|
||||
unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options);
|
||||
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
unique_ptr<RandomAccessFile>* result) {
|
||||
unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) {
|
||||
return notsup;
|
||||
}
|
||||
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result) {
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) {
|
||||
return notsup;
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,8 @@ class InMemoryEnv : public EnvWrapper {
|
||||
|
||||
// Partial implementation of the Env interface.
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
unique_ptr<SequentialFile>* result) {
|
||||
unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& soptions) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (file_map_.find(fname) == file_map_.end()) {
|
||||
*result = NULL;
|
||||
@@ -245,7 +246,8 @@ class InMemoryEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
unique_ptr<RandomAccessFile>* result) {
|
||||
unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& soptions) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (file_map_.find(fname) == file_map_.end()) {
|
||||
*result = NULL;
|
||||
@@ -257,7 +259,8 @@ class InMemoryEnv : public EnvWrapper {
|
||||
}
|
||||
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result) {
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& soptions) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (file_map_.find(fname) != file_map_.end()) {
|
||||
DeleteFileInternal(fname);
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace leveldb {
|
||||
class MemEnvTest {
|
||||
public:
|
||||
Env* env_;
|
||||
const EnvOptions soptions_;
|
||||
|
||||
MemEnvTest()
|
||||
: env_(NewMemEnv(Env::Default())) {
|
||||
@@ -40,7 +41,7 @@ TEST(MemEnvTest, Basics) {
|
||||
ASSERT_EQ(0U, children.size());
|
||||
|
||||
// Create a file.
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file));
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_));
|
||||
writable_file.reset();
|
||||
|
||||
// Check that the file exists.
|
||||
@@ -52,7 +53,7 @@ TEST(MemEnvTest, Basics) {
|
||||
ASSERT_EQ("f", children[0]);
|
||||
|
||||
// Write to the file.
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file));
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_));
|
||||
ASSERT_OK(writable_file->Append("abc"));
|
||||
writable_file.reset();
|
||||
|
||||
@@ -71,9 +72,11 @@ TEST(MemEnvTest, Basics) {
|
||||
// Check that opening non-existent file fails.
|
||||
unique_ptr<SequentialFile> seq_file;
|
||||
unique_ptr<RandomAccessFile> rand_file;
|
||||
ASSERT_TRUE(!env_->NewSequentialFile("/dir/non_existent", &seq_file).ok());
|
||||
ASSERT_TRUE(!env_->NewSequentialFile("/dir/non_existent", &seq_file,
|
||||
soptions_).ok());
|
||||
ASSERT_TRUE(!seq_file);
|
||||
ASSERT_TRUE(!env_->NewRandomAccessFile("/dir/non_existent", &rand_file).ok());
|
||||
ASSERT_TRUE(!env_->NewRandomAccessFile("/dir/non_existent", &rand_file,
|
||||
soptions_).ok());
|
||||
ASSERT_TRUE(!rand_file);
|
||||
|
||||
// Check that deleting works.
|
||||
@@ -94,13 +97,13 @@ TEST(MemEnvTest, ReadWrite) {
|
||||
|
||||
ASSERT_OK(env_->CreateDir("/dir"));
|
||||
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file));
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_));
|
||||
ASSERT_OK(writable_file->Append("hello "));
|
||||
ASSERT_OK(writable_file->Append("world"));
|
||||
writable_file.reset();
|
||||
|
||||
// Read sequentially.
|
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file));
|
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_));
|
||||
ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello".
|
||||
ASSERT_EQ(0, result.compare("hello"));
|
||||
ASSERT_OK(seq_file->Skip(1));
|
||||
@@ -113,7 +116,7 @@ TEST(MemEnvTest, ReadWrite) {
|
||||
ASSERT_EQ(0U, result.size());
|
||||
|
||||
// Random reads.
|
||||
ASSERT_OK(env_->NewRandomAccessFile("/dir/f", &rand_file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile("/dir/f", &rand_file, soptions_));
|
||||
ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world".
|
||||
ASSERT_EQ(0, result.compare("world"));
|
||||
ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello".
|
||||
@@ -139,7 +142,7 @@ TEST(MemEnvTest, Misc) {
|
||||
ASSERT_TRUE(!test_dir.empty());
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
ASSERT_OK(env_->NewWritableFile("/a/b", &writable_file));
|
||||
ASSERT_OK(env_->NewWritableFile("/a/b", &writable_file, soptions_));
|
||||
|
||||
// These are no-ops, but we test they return success.
|
||||
ASSERT_OK(writable_file->Sync());
|
||||
@@ -158,14 +161,14 @@ TEST(MemEnvTest, LargeWrite) {
|
||||
}
|
||||
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file));
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_));
|
||||
ASSERT_OK(writable_file->Append("foo"));
|
||||
ASSERT_OK(writable_file->Append(write_data));
|
||||
writable_file.reset();
|
||||
|
||||
unique_ptr<SequentialFile> seq_file;
|
||||
Slice result;
|
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file));
|
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_));
|
||||
ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo".
|
||||
ASSERT_EQ(0, result.compare("foo"));
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2013 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_COMPACTION_FILTER_H_
|
||||
#define STORAGE_LEVELDB_INCLUDE_COMPACTION_FILTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Slice;
|
||||
|
||||
// CompactionFilter allows an application to modify/delete a key-value at
|
||||
// the time of compaction.
|
||||
|
||||
class CompactionFilter {
|
||||
public:
|
||||
virtual ~CompactionFilter() {}
|
||||
|
||||
|
||||
// The compaction process invokes this
|
||||
// method for kv that is being compacted. A return value
|
||||
// of false indicates that the kv should be preserved in the
|
||||
// output of this compaction run and a return value of true
|
||||
// indicates that this key-value should be removed from the
|
||||
// output of the compaction. The application can inspect
|
||||
// the existing value of the key and make decision based on it.
|
||||
//
|
||||
// When the value is to be preserved, the application has the option
|
||||
// to modify the existing_value and pass it back through new_value.
|
||||
// value_changed needs to be set to true in this case.
|
||||
virtual bool Filter(int level,
|
||||
const Slice& key,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const = 0;
|
||||
|
||||
// Returns a name that identifies this compaction filter.
|
||||
// The name will be printed to LOG file on start up for diagnosis.
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_INCLUDE_COMPACTION_FILTER_H_
|
||||
+23
-1
@@ -83,6 +83,14 @@ class DB {
|
||||
// Note: consider setting options.sync = true.
|
||||
virtual Status Delete(const WriteOptions& options, const Slice& key) = 0;
|
||||
|
||||
// Merge the database entry for "key" with "value". Returns OK on success,
|
||||
// and a non-OK status on error. The semantics of this operation is
|
||||
// determined by the user provided merge_operator when opening DB.
|
||||
// Note: consider setting options.sync = true.
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
const Slice& key,
|
||||
const Slice& value) = 0;
|
||||
|
||||
// Apply the specified updates to the database.
|
||||
// Returns OK on success, non-OK on failure.
|
||||
// Note: consider setting options.sync = true.
|
||||
@@ -98,6 +106,20 @@ class DB {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
const Slice& key, std::string* value) = 0;
|
||||
|
||||
// If keys[i] does not exist in the database, then the i'th returned
|
||||
// status will be one for which Status::IsNotFound() is true, and
|
||||
// (*values)[i] will be set to some arbitrary value (often ""). Otherwise,
|
||||
// the i'th returned status will have Status::ok() true, and (*values)[i]
|
||||
// will store the value associated with keys[i].
|
||||
//
|
||||
// (*values) will always be resized to be the same size as (keys).
|
||||
// Similarly, the number of returned statuses will be the number of keys.
|
||||
// Note: keys will not be "de-duplicated". Duplicate keys will return
|
||||
// duplicate values in order.
|
||||
virtual std::vector<Status> MultiGet(const ReadOptions& options,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) = 0;
|
||||
|
||||
// Return a heap-allocated iterator over the contents of the database.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
@@ -185,7 +207,7 @@ class DB {
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size) = 0;
|
||||
|
||||
// The sequence number of the most recent transaction.
|
||||
// The sequence number of the most recent transaction.
|
||||
virtual SequenceNumber GetLatestSequenceNumber() = 0;
|
||||
|
||||
// Return's an iterator for all writes since the sequence number
|
||||
|
||||
+65
-10
@@ -28,10 +28,41 @@ class RandomAccessFile;
|
||||
class SequentialFile;
|
||||
class Slice;
|
||||
class WritableFile;
|
||||
class Options;
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::shared_ptr;
|
||||
|
||||
|
||||
// Options while opening a file to read/write
|
||||
struct EnvOptions {
|
||||
|
||||
// construct with default Options
|
||||
EnvOptions();
|
||||
|
||||
// construct from Options
|
||||
EnvOptions(const Options& options);
|
||||
|
||||
// If true, then allow caching of data in environment buffers
|
||||
bool use_os_buffer;
|
||||
|
||||
// If true, then use mmap to read data
|
||||
bool use_mmap_reads;
|
||||
|
||||
// If true, then use mmap to write data
|
||||
bool use_mmap_writes;
|
||||
|
||||
// If true, set the FD_CLOEXEC on open fd.
|
||||
bool set_fd_cloexec;
|
||||
|
||||
// Allows OS to incrementally sync files to disk while they are being
|
||||
// written, in the background. Issue one request for every bytes_per_sync
|
||||
// written. 0 turns it off.
|
||||
// Default: 0
|
||||
uint64_t bytes_per_sync;
|
||||
|
||||
};
|
||||
|
||||
class Env {
|
||||
public:
|
||||
Env() { }
|
||||
@@ -51,7 +82,9 @@ class Env {
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
unique_ptr<SequentialFile>* result) = 0;
|
||||
unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options)
|
||||
= 0;
|
||||
|
||||
// Create a brand new random access read-only file with the
|
||||
// specified name. On success, stores a pointer to the new file in
|
||||
@@ -61,7 +94,9 @@ class Env {
|
||||
//
|
||||
// The returned file may be concurrently accessed by multiple threads.
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
unique_ptr<RandomAccessFile>* result) = 0;
|
||||
unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options)
|
||||
= 0;
|
||||
|
||||
// Create an object that writes to a new file with the specified
|
||||
// name. Deletes any existing file with the same name and creates a
|
||||
@@ -71,7 +106,8 @@ class Env {
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result) = 0;
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) = 0;
|
||||
|
||||
// Returns true iff the named file exists.
|
||||
virtual bool FileExists(const std::string& fname) = 0;
|
||||
@@ -228,7 +264,7 @@ class RandomAccessFile {
|
||||
// the file is opened (and will stay the same while the file is open).
|
||||
// Furthermore, it tries to make this ID at most "max_size" bytes. If such an
|
||||
// ID can be created this function returns the length of the ID and places it
|
||||
// in "id"; otherwise, this function returns 0, in which case "id" may more
|
||||
// in "id"; otherwise, this function returns 0, in which case "id"
|
||||
// may not have been modified.
|
||||
//
|
||||
// This function guarantees, for IDs from a given environment, two unique ids
|
||||
@@ -243,6 +279,12 @@ class RandomAccessFile {
|
||||
return 0; // Default implementation to prevent issues with backwards
|
||||
// compatibility.
|
||||
};
|
||||
|
||||
|
||||
enum AccessPattern { NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED };
|
||||
|
||||
virtual void Hint(AccessPattern pattern) {}
|
||||
|
||||
};
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
@@ -324,6 +366,16 @@ class WritableFile {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Sync a file range with disk.
|
||||
// offset is the starting byte of the file range to be synchronized.
|
||||
// nbytes specifies the length of the range to be synchronized.
|
||||
// This asks the OS to initiate flushing the cached data to disk,
|
||||
// without waiting for completion.
|
||||
// Default implementation does nothing.
|
||||
virtual Status RangeSync(off64_t offset, off64_t nbytes) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t last_preallocated_block_;
|
||||
size_t preallocation_block_size_;
|
||||
@@ -398,15 +450,18 @@ class EnvWrapper : public Env {
|
||||
|
||||
// The following text is boilerplate that forwards all methods to target()
|
||||
Status NewSequentialFile(const std::string& f,
|
||||
unique_ptr<SequentialFile>* r) {
|
||||
return target_->NewSequentialFile(f, r);
|
||||
unique_ptr<SequentialFile>* r,
|
||||
const EnvOptions& options) {
|
||||
return target_->NewSequentialFile(f, r, options);
|
||||
}
|
||||
Status NewRandomAccessFile(const std::string& f,
|
||||
unique_ptr<RandomAccessFile>* r) {
|
||||
return target_->NewRandomAccessFile(f, r);
|
||||
unique_ptr<RandomAccessFile>* r,
|
||||
const EnvOptions& options) {
|
||||
return target_->NewRandomAccessFile(f, r, options);
|
||||
}
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r) {
|
||||
return target_->NewWritableFile(f, r);
|
||||
Status NewWritableFile(const std::string& f, unique_ptr<WritableFile>* r,
|
||||
const EnvOptions& options) {
|
||||
return target_->NewWritableFile(f, r, options);
|
||||
}
|
||||
bool FileExists(const std::string& f) { return target_->FileExists(f); }
|
||||
Status GetChildren(const std::string& dir, std::vector<std::string>* r) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2008-present Facebook. All Rights Reserved.
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_LDB_TOOL_H
|
||||
#define STORAGE_LEVELDB_INCLUDE_LDB_TOOL_H
|
||||
#include "leveldb/options.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class LDBTool {
|
||||
public:
|
||||
void Run(int argc, char** argv, Options = Options());
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
#endif // STORAGE_LEVELDB_INCLUDE_LDB_TOOL_H
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_MERGE_OPERATOR_H_
|
||||
#define STORAGE_LEVELDB_INCLUDE_MERGE_OPERATOR_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Slice;
|
||||
class Logger;
|
||||
|
||||
// The Merge Operator interface.
|
||||
// Client needs to provide an object implementing this interface if Merge
|
||||
// operation is accessed.
|
||||
// Essentially, MergeOperator specifies the SEMANTICS of a merge, which only
|
||||
// client knows. It could be numeric addition, list append, string
|
||||
// concatenation, ... , anything.
|
||||
// The library, on the other hand, is concerned with the exercise of this
|
||||
// interface, at the right time (during get, iteration, compaction...)
|
||||
// Note that, even though in principle we don't require any special property
|
||||
// of the merge operator, the current rocksdb compaction order does imply that
|
||||
// an associative operator could be exercised more naturally (and more
|
||||
// efficiently).
|
||||
//
|
||||
// Refer to my_test.cc for an example of implementation
|
||||
//
|
||||
class MergeOperator {
|
||||
public:
|
||||
virtual ~MergeOperator() {}
|
||||
|
||||
// Gives the client a way to express the read -> modify -> write semantics
|
||||
// key: (IN) The key that's associated with this merge operation.
|
||||
// Client could multiplex the merge operator based on it
|
||||
// if the key space is partitioned and different subspaces
|
||||
// refer to different types of data which have different
|
||||
// merge operation semantics
|
||||
// existing: (IN) null indicates that the key does not exist before this op
|
||||
// value: (IN) The passed-in merge operand value (when Merge is issued)
|
||||
// new_value:(OUT) Client is responsible for filling the merge result here
|
||||
// logger: (IN) Client could use this to log errors during merge.
|
||||
//
|
||||
// Note: Merge does not return anything to indicate if a merge is successful
|
||||
// or not.
|
||||
// Rationale: If a merge failed due to, say de-serialization error, we still
|
||||
// need to define a consistent merge result. Should we throw away
|
||||
// the existing value? the merge operand? Or reset the merged value
|
||||
// to sth? The rocksdb library is not in a position to make the
|
||||
// right choice. On the other hand, client knows exactly what
|
||||
// happened during Merge, thus is able to make the best decision.
|
||||
// Just save the final decision in new_value. logger is passed in,
|
||||
// in case client wants to leave a trace of what went wrong.
|
||||
virtual void Merge(const Slice& key,
|
||||
const Slice* existing_value,
|
||||
const Slice& value,
|
||||
std::string* new_value,
|
||||
Logger* logger) const = 0;
|
||||
|
||||
|
||||
// The name of the MergeOperator. Used to check for MergeOperator
|
||||
// mismatches (i.e., a DB created with one MergeOperator is
|
||||
// accessed using a different MergeOperator)
|
||||
// TODO: the name is currently not stored persistently and thus
|
||||
// no checking is enforced. Client is responsible for providing
|
||||
// consistent MergeOperator between DB opens.
|
||||
virtual const char* Name() const = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_INCLUDE_MERGE_OPERATOR_H_
|
||||
+102
-22
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include "leveldb/slice.h"
|
||||
#include "leveldb/statistics.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -19,8 +20,9 @@ class Comparator;
|
||||
class Env;
|
||||
class FilterPolicy;
|
||||
class Logger;
|
||||
class MergeOperator;
|
||||
class Snapshot;
|
||||
class Statistics;
|
||||
class CompactionFilter;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
@@ -63,6 +65,23 @@ struct Options {
|
||||
// comparator provided to previous open calls on the same DB.
|
||||
const Comparator* comparator;
|
||||
|
||||
// REQUIRES: The client must provide a merge operator if Merge operation
|
||||
// needs to be accessed. Calling Merge on a DB without a merge operator
|
||||
// would result in Status::NotSupported. The client must ensure that the
|
||||
// merge operator supplied here has the same name and *exactly* the same
|
||||
// semantics as the merge operator provided to previous open calls on
|
||||
// the same DB. The only exception is reserved for upgrade, where a DB
|
||||
// previously without a merge operator is introduced to Merge operation
|
||||
// for the first time. It's necessary to specify a merge operator when
|
||||
// openning the DB in this case.
|
||||
// Default: nullptr
|
||||
const MergeOperator* merge_operator;
|
||||
|
||||
// Allows an application to modify/delete a key-value during background
|
||||
// compaction.
|
||||
// Default: nullptr
|
||||
const CompactionFilter* compaction_filter;
|
||||
|
||||
// If true, the database will be created if it is missing.
|
||||
// Default: false
|
||||
bool create_if_missing;
|
||||
@@ -112,6 +131,15 @@ struct Options {
|
||||
// Default: 2
|
||||
int max_write_buffer_number;
|
||||
|
||||
// The minimum number of write buffers that will be merged together
|
||||
// before writing to storage. If set to 1, then
|
||||
// all write buffers are fushed to L0 as individual files and this increases
|
||||
// read amplification because a get request has to check in all of these
|
||||
// files. Also, an in-memory merge may result in writing lesser
|
||||
// data to storage if there are duplicate records in each of these
|
||||
// individual write buffers. Default: 1
|
||||
int min_write_buffer_number_to_merge;
|
||||
|
||||
// Number of open files that can be used by the DB. You may need to
|
||||
// increase this if your database has a large working set (budget
|
||||
// one open file per 2MB of working set).
|
||||
@@ -236,6 +264,12 @@ struct Options {
|
||||
// by default 'max_bytes_for_level_base' is 10.
|
||||
int max_bytes_for_level_multiplier;
|
||||
|
||||
// Different max-size multipliers for different levels.
|
||||
// These are multiplied by max_bytes_for_level_multiplier to arrive
|
||||
// at the max-size of each level.
|
||||
// Default: 1
|
||||
std::vector<int> max_bytes_for_level_multiplier_additional;
|
||||
|
||||
// Maximum number of bytes in all compacted files. We avoid expanding
|
||||
// the lower level file set of a compaction if it would make the
|
||||
// total compaction cover more than
|
||||
@@ -258,7 +292,7 @@ struct Options {
|
||||
// If non-null, then we should collect metrics about database operations
|
||||
// Statistics objects should not be shared between DB instances as
|
||||
// it does not use any locks to prevent concurrent updates.
|
||||
Statistics* statistics;
|
||||
shared_ptr<Statistics> statistics;
|
||||
|
||||
// If true, then the contents of data files are not synced
|
||||
// to stable storage. Their contents remain in the OS buffers till the
|
||||
@@ -325,7 +359,7 @@ struct Options {
|
||||
double rate_limit;
|
||||
|
||||
// Max time a put will be stalled when rate_limit is enforced
|
||||
int rate_limit_delay_milliseconds;
|
||||
unsigned int rate_limit_delay_milliseconds;
|
||||
|
||||
// manifest file is rolled over on reaching this limit.
|
||||
// The older manifest file be deleted.
|
||||
@@ -354,25 +388,6 @@ struct Options {
|
||||
// from the database, because otherwise the read can be very slow.
|
||||
Options* PrepareForBulkLoad();
|
||||
|
||||
// This method allows an application to modify/delete a key-value at
|
||||
// the time of compaction. The compaction process invokes this
|
||||
// method for every kv that is being compacted. A return value
|
||||
// of false indicates that the kv should be preserved in the
|
||||
// output of this compaction run and a return value of true
|
||||
// indicates that this key-value should be removed from the
|
||||
// output of the compaction. The application can inspect
|
||||
// the existing value of the key, modify it if needed and
|
||||
// return back the new value for this key. The application
|
||||
// should allocate memory for the Slice object that is used to
|
||||
// return the new value and the leveldb framework will
|
||||
// free up that memory.
|
||||
// The compaction_filter_args, if specified here, are passed
|
||||
// back to the invocation of the CompactionFilter.
|
||||
void* compaction_filter_args;
|
||||
bool (*CompactionFilter)(void* compaction_filter_args,
|
||||
int level, const Slice& key,
|
||||
const Slice& existing_value, Slice** new_value);
|
||||
|
||||
// Disable automatic compactions. Manual compactions can still
|
||||
// be issued on this database.
|
||||
bool disable_auto_compactions;
|
||||
@@ -396,6 +411,71 @@ struct Options {
|
||||
// Purge duplicate/deleted keys when a memtable is flushed to storage.
|
||||
// Default: true
|
||||
bool purge_redundant_kvs_while_flush;
|
||||
|
||||
// Data being read from file storage may be buffered in the OS
|
||||
// Default: true
|
||||
bool allow_os_buffer;
|
||||
|
||||
// Reading a single block from a file can cause the OS/FS to start
|
||||
// readaheads of other blocks from the file. Default: true
|
||||
// Note: Deprecated
|
||||
bool allow_readahead;
|
||||
|
||||
// The reads triggered by compaction allows data to be readahead
|
||||
// by the OS/FS. This overrides the setting of 'allow_readahead'
|
||||
// for compaction-reads. Default: true
|
||||
// Note: Deprecated
|
||||
bool allow_readahead_compactions;
|
||||
|
||||
// Allow the OS to mmap file for reading sst tables. Default: false
|
||||
bool allow_mmap_reads;
|
||||
|
||||
// Allow the OS to mmap file for writing. Default: true
|
||||
bool allow_mmap_writes;
|
||||
|
||||
// Disable child process inherit open files. Default: true
|
||||
bool is_fd_close_on_exec;
|
||||
|
||||
// Skip log corruption error on recovery (If client is ok with
|
||||
// losing most recent changes)
|
||||
// Default: false
|
||||
bool skip_log_error_on_recovery;
|
||||
|
||||
// if not zero, dump leveldb.stats to LOG every stats_dump_period_sec
|
||||
// Default: 3600 (1 hour)
|
||||
unsigned int stats_dump_period_sec;
|
||||
|
||||
// This is used to close a block before it reaches the configured
|
||||
// 'block_size'. If the percentage of free space in the current block is less
|
||||
// than this specified number and adding a new record to the block will
|
||||
// exceed the configured block size, then this block will be closed and the
|
||||
// new record will be written to the next block.
|
||||
// Default is 10.
|
||||
int block_size_deviation;
|
||||
|
||||
// If set true, will hint the underlying file system that the file
|
||||
// access pattern is random, when a sst file is opened.
|
||||
// Default: true
|
||||
bool advise_random_on_open;
|
||||
|
||||
// Specify the file access pattern once a compaction is started.
|
||||
// It will be applied to all input files of a compaction.
|
||||
// Default: NORMAL
|
||||
enum { NONE, NORMAL, SEQUENTIAL, WILLNEED } access_hint_on_compaction_start;
|
||||
|
||||
// Use adaptive mutex, which spins in the user space before resorting
|
||||
// to kernel. This could reduce context switch when the mutex is not
|
||||
// heavily contended. However, if the mutex is hot, we could end up
|
||||
// wasting spin time.
|
||||
// Default: false
|
||||
bool use_adaptive_mutex;
|
||||
|
||||
// Allows OS to incrementally sync files to disk while they are being
|
||||
// written, asynchronously, in the background.
|
||||
// Issue one request for every bytes_per_sync written. 0 turns it off.
|
||||
// Default: 0
|
||||
uint64_t bytes_per_sync;
|
||||
|
||||
};
|
||||
|
||||
// Options that control read operations
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_STATISTICS_H_
|
||||
#define STORAGE_LEVELDB_INCLUDE_STATISTICS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -16,6 +19,7 @@ namespace leveldb {
|
||||
* Keep adding ticker's here.
|
||||
* Any ticker should have a value less than TICKER_ENUM_MAX.
|
||||
* Add a new ticker by assigning it the current value of TICKER_ENUM_MAX
|
||||
* Add a string representation in TickersNameMap below.
|
||||
* And incrementing TICKER_ENUM_MAX.
|
||||
*/
|
||||
enum Tickers {
|
||||
@@ -39,21 +43,85 @@ enum Tickers {
|
||||
NO_FILE_CLOSES = 10,
|
||||
NO_FILE_OPENS = 11,
|
||||
NO_FILE_ERRORS = 12,
|
||||
TICKER_ENUM_MAX = 13,
|
||||
// Time system had to wait to do LO-L1 compactions
|
||||
STALL_L0_SLOWDOWN_MICROS = 13,
|
||||
// Time system had to wait to move memtable to L1.
|
||||
STALL_MEMTABLE_COMPACTION_MICROS = 14,
|
||||
// write throttle because of too many files in L0
|
||||
STALL_L0_NUM_FILES_MICROS = 15,
|
||||
RATE_LIMIT_DELAY_MILLIS = 16,
|
||||
|
||||
NO_ITERATORS = 17, // number of iterators currently open
|
||||
|
||||
// Number of MultiGet calls, keys read, and bytes read
|
||||
NUMBER_MULTIGET_CALLS = 18,
|
||||
NUMBER_MULTIGET_KEYS_READ = 19,
|
||||
NUMBER_MULTIGET_BYTES_READ = 20,
|
||||
|
||||
TICKER_ENUM_MAX = 21
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{ BLOCK_CACHE_MISS, "rocksdb.block.cache.miss" },
|
||||
{ BLOCK_CACHE_HIT, "rocksdb.block.cache.hit" },
|
||||
{ BLOOM_FILTER_USEFUL, "rocksdb.bloom.filter.useful" },
|
||||
{ COMPACTION_KEY_DROP_NEWER_ENTRY, "rocksdb.compaction.key.drop.new" },
|
||||
{ COMPACTION_KEY_DROP_OBSOLETE, "rocksdb.compaction.key.drop.obsolete" },
|
||||
{ COMPACTION_KEY_DROP_USER, "rocksdb.compaction.key.drop.user" },
|
||||
{ NUMBER_KEYS_WRITTEN, "rocksdb.number.keys.written" },
|
||||
{ NUMBER_KEYS_READ, "rocksdb.number.keys.read" },
|
||||
{ BYTES_WRITTEN, "rocksdb.bytes.written" },
|
||||
{ BYTES_READ, "rocksdb.bytes.read" },
|
||||
{ NO_FILE_CLOSES, "rocksdb.no.file.closes" },
|
||||
{ NO_FILE_OPENS, "rocksdb.no.file.opens" },
|
||||
{ NO_FILE_ERRORS, "rocksdb.no.file.errors" },
|
||||
{ STALL_L0_SLOWDOWN_MICROS, "rocksdb.l0.slowdown.micros" },
|
||||
{ STALL_MEMTABLE_COMPACTION_MICROS, "rocksdb.memtable.compaction.micros" },
|
||||
{ STALL_L0_NUM_FILES_MICROS, "rocksdb.l0.num.files.stall.micros" },
|
||||
{ RATE_LIMIT_DELAY_MILLIS, "rocksdb.rate.limit.dleay.millis" },
|
||||
{ NO_ITERATORS, "rocksdb.num.iterators" },
|
||||
{ NUMBER_MULTIGET_CALLS, "rocksdb.number.multiget.get" },
|
||||
{ NUMBER_MULTIGET_KEYS_READ, "rocksdb.number.multiget.keys.read" },
|
||||
{ NUMBER_MULTIGET_BYTES_READ, "rocksdb.number.multiget.bytes.read" }
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep adding histogram's here.
|
||||
* Any histogram whould have value less than HISTOGRAM_ENUM_MAX
|
||||
* Add a new Histogram by assigning it the current value of HISTOGRAM_ENUM_MAX
|
||||
* Add a string representation in HistogramsNameMap below
|
||||
* And increment HISTOGRAM_ENUM_MAX
|
||||
*/
|
||||
enum Histograms {
|
||||
DB_GET = 0,
|
||||
DB_WRITE = 1,
|
||||
COMPACTION_TIME = 2,
|
||||
HISTOGRAM_ENUM_MAX = 3,
|
||||
TABLE_SYNC_MICROS = 3,
|
||||
COMPACTION_OUTFILE_SYNC_MICROS = 4,
|
||||
WAL_FILE_SYNC_MICROS = 5,
|
||||
MANIFEST_FILE_SYNC_MICROS = 6,
|
||||
// TIME SPENT IN IO DURING TABLE OPEN
|
||||
TABLE_OPEN_IO_MICROS = 7,
|
||||
DB_MULTIGET = 8,
|
||||
READ_BLOCK_COMPACTION_MICROS = 9,
|
||||
READ_BLOCK_GET_MICROS = 10,
|
||||
WRITE_RAW_BLOCK_MICROS = 11,
|
||||
HISTOGRAM_ENUM_MAX = 12
|
||||
};
|
||||
|
||||
const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{ DB_GET, "rocksdb.db.get.micros" },
|
||||
{ DB_WRITE, "rocksdb.db.write.micros" },
|
||||
{ COMPACTION_TIME, "rocksdb.compaction.times.micros" },
|
||||
{ TABLE_SYNC_MICROS, "rocksdb.table.sync.micros" },
|
||||
{ COMPACTION_OUTFILE_SYNC_MICROS, "rocksdb.compaction.outfile.sync.micros" },
|
||||
{ WAL_FILE_SYNC_MICROS, "rocksdb.wal.file.sync.micros" },
|
||||
{ MANIFEST_FILE_SYNC_MICROS, "rocksdb.manifest.file.sync.micros" },
|
||||
{ TABLE_OPEN_IO_MICROS, "rocksdb.table.open.io.micros" },
|
||||
{ DB_MULTIGET, "rocksdb.db.multiget.micros" },
|
||||
{ READ_BLOCK_COMPACTION_MICROS, "rocksdb.read.block.compaction.micros" },
|
||||
{ READ_BLOCK_GET_MICROS, "rocksdb.read.block.get.micros" },
|
||||
{ WRITE_RAW_BLOCK_MICROS, "rocksdb.write.raw.block.micros" }
|
||||
};
|
||||
|
||||
struct HistogramData {
|
||||
@@ -72,7 +140,6 @@ class Histogram {
|
||||
virtual ~Histogram();
|
||||
// Add a value to be recorded in the histogram.
|
||||
virtual void Add(uint64_t value) = 0;
|
||||
virtual void Add(double value) = 0;
|
||||
|
||||
virtual std::string ToString() const = 0;
|
||||
|
||||
@@ -93,7 +160,7 @@ class Ticker {
|
||||
public:
|
||||
Ticker() : count_(0) { }
|
||||
|
||||
inline void recordTick() {
|
||||
inline void recordTick() {
|
||||
count_++;
|
||||
}
|
||||
|
||||
@@ -106,8 +173,7 @@ class Ticker {
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t count_;
|
||||
|
||||
std::atomic_uint_fast64_t count_;
|
||||
};
|
||||
|
||||
// Analyze the performance of a db
|
||||
@@ -116,18 +182,23 @@ class Statistics {
|
||||
|
||||
virtual long getTickerCount(Tickers tickerType) = 0;
|
||||
virtual void recordTick(Tickers tickerType, uint64_t count = 0) = 0;
|
||||
virtual void measureTime(Histograms histogramType, uint64_t count) = 0;
|
||||
virtual void measureTime(Histograms histogramType, double count) = 0;
|
||||
virtual void measureTime(Histograms histogramType, uint64_t time) = 0;
|
||||
|
||||
virtual void histogramData(Histograms type, HistogramData * const data) = 0;
|
||||
|
||||
// String representation of the statistic object.
|
||||
std::string ToString();
|
||||
};
|
||||
|
||||
// Create a concrete DBStatistics object
|
||||
std::shared_ptr<Statistics> CreateDBStatistics();
|
||||
|
||||
// Ease of Use functions
|
||||
inline void RecordTick(Statistics* const statistics,
|
||||
inline void RecordTick(std::shared_ptr<Statistics> statistics,
|
||||
Tickers ticker,
|
||||
uint64_t count = 1) {
|
||||
if (statistics != nullptr) {
|
||||
assert(HistogramsNameMap.size() == HISTOGRAM_ENUM_MAX);
|
||||
assert(TickersNameMap.size() == TICKER_ENUM_MAX);
|
||||
if (statistics) {
|
||||
statistics->recordTick(ticker, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ class Status {
|
||||
static Status IOError(const Slice& msg, const Slice& msg2 = Slice()) {
|
||||
return Status(kIOError, msg, msg2);
|
||||
}
|
||||
static Status MergeInProgress(const Slice& msg, const Slice& msg2 = Slice()) {
|
||||
return Status(kMergeInProgress, msg, msg2);
|
||||
}
|
||||
|
||||
// Returns true iff the status indicates success.
|
||||
bool ok() const { return (state_ == nullptr); }
|
||||
@@ -66,6 +69,9 @@ class Status {
|
||||
// Returns true iff the status indicates an IOError.
|
||||
bool IsIOError() const { return code() == kIOError; }
|
||||
|
||||
// Returns true iff the status indicates an MergeInProgress.
|
||||
bool IsMergeInProgress() const { return code() == kMergeInProgress; }
|
||||
|
||||
// Return a string representation of this status suitable for printing.
|
||||
// Returns the string "OK" for success.
|
||||
std::string ToString() const;
|
||||
@@ -84,7 +90,8 @@ class Status {
|
||||
kCorruption = 2,
|
||||
kNotSupported = 3,
|
||||
kInvalidArgument = 4,
|
||||
kIOError = 5
|
||||
kIOError = 5,
|
||||
kMergeInProgress = 6
|
||||
};
|
||||
|
||||
Code code() const {
|
||||
|
||||
@@ -36,6 +36,10 @@ class WriteBatch {
|
||||
// Store the mapping "key->value" in the database.
|
||||
void Put(const Slice& key, const Slice& value);
|
||||
|
||||
// Merge "value" with the existing value of "key" in the database.
|
||||
// "key->merge(existing, value)"
|
||||
void Merge(const Slice& key, const Slice& value);
|
||||
|
||||
// If the database contains a mapping for "key", erase it. Else do nothing.
|
||||
void Delete(const Slice& key);
|
||||
|
||||
@@ -47,6 +51,10 @@ class WriteBatch {
|
||||
public:
|
||||
virtual ~Handler();
|
||||
virtual void Put(const Slice& key, const Slice& value) = 0;
|
||||
// Merge is not pure virtual. Otherwise, we would break existing
|
||||
// clients of Handler on a source code level.
|
||||
// The default implementation simply throws a runtime exception.
|
||||
virtual void Merge(const Slice& key, const Slice& value);
|
||||
virtual void Delete(const Slice& key) = 0;
|
||||
};
|
||||
Status Iterate(Handler* handler) const;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef LEVELDB_INCLUDE_UTILITIES_UTILITY_DB_H_
|
||||
#define LEVELDB_INCLUDE_UTILITIES_UTILITY_DB_H_
|
||||
|
||||
#include "leveldb/db.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// This class contains APIs to open leveldb with specific support eg. TTL
|
||||
class UtilityDB {
|
||||
|
||||
public:
|
||||
// Open the database with TTL support.
|
||||
//
|
||||
// USE-CASES:
|
||||
// This API should be used to open the db when key-values inserted are
|
||||
// meant to be removed from the db in a non-strict 'ttl' amount of time
|
||||
// Therefore, this guarantees that key-values inserted will remain in the
|
||||
// db for >= ttl amount of time and the db will make efforts to remove the
|
||||
// key-values as soon as possible after ttl seconds of their insertion.
|
||||
//
|
||||
// BEHAVIOUR:
|
||||
// TTL is accepted in seconds
|
||||
// (int32_t)Timestamp(creation) is suffixed to values in Put internally
|
||||
// Expired TTL values deleted in compaction only:(Timestamp+ttl<time_now)
|
||||
// Get/Iterator may return expired entries(compaction not run on them yet)
|
||||
// Different TTL may be used during different Opens
|
||||
// Example: Open1 at t=0 with ttl=4 and insert k1,k2, close at t=2
|
||||
// Open2 at t=3 with ttl=5. Now k1,k2 should be deleted at t>=5
|
||||
// read_only=true opens in the usual read-only mode. Compactions will not be
|
||||
// triggered(neither manual nor automatic), so no expired entries removed
|
||||
//
|
||||
// CONSTRAINTS:
|
||||
// The caller must not specify any compaction-filter in options
|
||||
// Not specifying/passing or non-positive TTL behaves like TTL = infinity
|
||||
//
|
||||
// !!!WARNING!!!:
|
||||
// Calling DB::Open directly to re-open a db created by this API will get
|
||||
// corrupt values(timestamp suffixed) and no ttl effect will be there
|
||||
// during the second Open, so use this API consistently to open the db
|
||||
// Be careful when passing ttl with a small positive value because the
|
||||
// whole database may be deleted in a small amount of time
|
||||
static Status OpenTtlDB(const Options& options,
|
||||
const std::string& name,
|
||||
DB** dbptr,
|
||||
int32_t ttl = 0,
|
||||
bool read_only = false);
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // LEVELDB_INCLUDE_UTILITIES_UTILITY_DB_H_
|
||||
+18
-1
@@ -19,7 +19,24 @@ static void PthreadCall(const char* label, int result) {
|
||||
}
|
||||
}
|
||||
|
||||
Mutex::Mutex() { PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL)); }
|
||||
Mutex::Mutex(bool adaptive) {
|
||||
#ifdef OS_LINUX
|
||||
if (!adaptive) {
|
||||
PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL));
|
||||
} else {
|
||||
pthread_mutexattr_t mutex_attr;
|
||||
PthreadCall("init mutex attr", pthread_mutexattr_init(&mutex_attr));
|
||||
PthreadCall("set mutex attr",
|
||||
pthread_mutexattr_settype(&mutex_attr,
|
||||
PTHREAD_MUTEX_ADAPTIVE_NP));
|
||||
PthreadCall("init mutex", pthread_mutex_init(&mu_, &mutex_attr));
|
||||
PthreadCall("destroy mutex attr",
|
||||
pthread_mutexattr_destroy(&mutex_attr));
|
||||
}
|
||||
#else // ignore adaptive for non-linux platform
|
||||
PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL));
|
||||
#endif // OS_LINUX
|
||||
}
|
||||
|
||||
Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }
|
||||
|
||||
|
||||
+25
-17
@@ -82,7 +82,7 @@ class CondVar;
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
/* implicit */ Mutex(bool adaptive = false);
|
||||
~Mutex();
|
||||
|
||||
void Lock();
|
||||
@@ -192,17 +192,20 @@ inline bool Zlib_Compress(const CompressionOptions& opts, const char* input,
|
||||
_stream.avail_out = length;
|
||||
_stream.next_out = (Bytef *)&(*output)[0];
|
||||
|
||||
int old_sz =0, new_sz =0;
|
||||
while(_stream.next_in != NULL && _stream.avail_in != 0) {
|
||||
int old_sz =0, new_sz =0, new_sz_delta =0;
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
int st = deflate(&_stream, Z_FINISH);
|
||||
switch (st) {
|
||||
case Z_STREAM_END:
|
||||
done = true;
|
||||
break;
|
||||
case Z_OK:
|
||||
// No output space. Increase the output space by 20%.
|
||||
// (Should we fail the compression since it expands the size?)
|
||||
old_sz = output->size();
|
||||
new_sz = (int)(output->size() * 1.2);
|
||||
new_sz_delta = (int)(output->size() * 0.2);
|
||||
new_sz = output->size() + (new_sz_delta < 10 ? 10 : new_sz_delta);
|
||||
output->resize(new_sz);
|
||||
// Set more output.
|
||||
_stream.next_out = (Bytef *)&(*output)[old_sz];
|
||||
@@ -228,13 +231,13 @@ inline char* Zlib_Uncompress(const char* input_data, size_t input_length,
|
||||
z_stream _stream;
|
||||
memset(&_stream, 0, sizeof(z_stream));
|
||||
|
||||
// For raw inflate, the windowBits should be Ð8..Ð15.
|
||||
// For raw inflate, the windowBits should be -8..-15.
|
||||
// If windowBits is bigger than zero, it will use either zlib
|
||||
// header or gzip header. Adding 32 to it will do automatic detection.
|
||||
int st = inflateInit2(&_stream,
|
||||
windowBits > 0 ? windowBits + 32 : windowBits);
|
||||
if (st != Z_OK) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_stream.next_in = (Bytef *)input_data;
|
||||
@@ -248,17 +251,22 @@ inline char* Zlib_Uncompress(const char* input_data, size_t input_length,
|
||||
_stream.next_out = (Bytef *)output;
|
||||
_stream.avail_out = output_len;
|
||||
|
||||
char* tmp = NULL;
|
||||
char* tmp = nullptr;
|
||||
int output_len_delta;
|
||||
bool done = false;
|
||||
|
||||
while(_stream.next_in != NULL && _stream.avail_in != 0) {
|
||||
//while(_stream.next_in != nullptr && _stream.avail_in != 0) {
|
||||
while (!done) {
|
||||
int st = inflate(&_stream, Z_SYNC_FLUSH);
|
||||
switch (st) {
|
||||
case Z_STREAM_END:
|
||||
done = true;
|
||||
break;
|
||||
case Z_OK:
|
||||
// No output space. Increase the output space by 20%.
|
||||
old_sz = output_len;
|
||||
output_len = (int)(output_len * 1.2);
|
||||
output_len_delta = (int)(output_len * 0.2);
|
||||
output_len += output_len_delta < 10 ? 10 : output_len_delta;
|
||||
tmp = new char[output_len];
|
||||
memcpy(tmp, output, old_sz);
|
||||
delete[] output;
|
||||
@@ -272,7 +280,7 @@ inline char* Zlib_Uncompress(const char* input_data, size_t input_length,
|
||||
default:
|
||||
delete[] output;
|
||||
inflateEnd(&_stream);
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +289,7 @@ inline char* Zlib_Uncompress(const char* input_data, size_t input_length,
|
||||
return output;
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool BZip2_Compress(const CompressionOptions& opts, const char* input,
|
||||
@@ -311,7 +319,7 @@ inline bool BZip2_Compress(const CompressionOptions& opts, const char* input,
|
||||
_stream.avail_out = length;
|
||||
|
||||
int old_sz =0, new_sz =0;
|
||||
while(_stream.next_in != NULL && _stream.avail_in != 0) {
|
||||
while(_stream.next_in != nullptr && _stream.avail_in != 0) {
|
||||
int st = BZ2_bzCompress(&_stream, BZ_FINISH);
|
||||
switch (st) {
|
||||
case BZ_STREAM_END:
|
||||
@@ -349,7 +357,7 @@ inline char* BZip2_Uncompress(const char* input_data, size_t input_length,
|
||||
|
||||
int st = BZ2_bzDecompressInit(&_stream, 0, 0);
|
||||
if (st != BZ_OK) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_stream.next_in = (char *)input_data;
|
||||
@@ -363,9 +371,9 @@ inline char* BZip2_Uncompress(const char* input_data, size_t input_length,
|
||||
_stream.next_out = (char *)output;
|
||||
_stream.avail_out = output_len;
|
||||
|
||||
char* tmp = NULL;
|
||||
char* tmp = nullptr;
|
||||
|
||||
while(_stream.next_in != NULL && _stream.avail_in != 0) {
|
||||
while(_stream.next_in != nullptr && _stream.avail_in != 0) {
|
||||
int st = BZ2_bzDecompress(&_stream);
|
||||
switch (st) {
|
||||
case BZ_STREAM_END:
|
||||
@@ -387,7 +395,7 @@ inline char* BZip2_Uncompress(const char* input_data, size_t input_length,
|
||||
default:
|
||||
delete[] output;
|
||||
BZ2_bzDecompressEnd(&_stream);
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +403,7 @@ inline char* BZip2_Uncompress(const char* input_data, size_t input_length,
|
||||
BZ2_bzDecompressEnd(&_stream);
|
||||
return output;
|
||||
#endif
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool GetHeapProfile(void (*func)(void*, const char*, int), void* arg) {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "util/stack_trace.h"
|
||||
|
||||
#ifdef OS_LINUX
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const char* GetExecutableName()
|
||||
{
|
||||
static char name[1024];
|
||||
|
||||
char link[1024];
|
||||
snprintf(link, sizeof(link), "/proc/%d/exe", getpid());
|
||||
auto read = readlink(link, name, sizeof(name));
|
||||
if (-1 == read) {
|
||||
return nullptr;
|
||||
} else {
|
||||
name[read] = 0;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
static void StackTraceHandler(int sig) {
|
||||
// reset to default handler
|
||||
signal(sig, SIG_DFL);
|
||||
|
||||
fprintf(stderr, "Received signal %d (%s)\n", sig, strsignal(sig));
|
||||
|
||||
const int kMaxFrames = 100;
|
||||
void *frames[kMaxFrames];
|
||||
|
||||
auto num_frames = backtrace(frames, kMaxFrames);
|
||||
auto symbols = backtrace_symbols(frames, num_frames);
|
||||
|
||||
auto executable = GetExecutableName();
|
||||
|
||||
const int kSkip = 2; // skip the top two signal handler related frames
|
||||
|
||||
for (int i = kSkip; i < num_frames; ++i)
|
||||
{
|
||||
fprintf(stderr, "#%-2d %p ", i - kSkip, frames[i]);
|
||||
if (symbols) {
|
||||
fprintf(stderr, "%s ", symbols[i]);
|
||||
}
|
||||
if (executable) {
|
||||
// out source to addr2line, for the address translation
|
||||
const int kLineMax = 256;
|
||||
char cmd[kLineMax];
|
||||
sprintf(cmd,"addr2line %p -e %s 2>&1", frames[i] , executable);
|
||||
auto f = popen(cmd, "r");
|
||||
if (f) {
|
||||
char line[kLineMax];
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
fprintf(stderr, "%s", line);
|
||||
}
|
||||
pclose(f);
|
||||
} else {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// re-signal to default handler (so we still get core dump if needed...)
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
void InstallStackTraceHandler() {
|
||||
// just use the plain old signal as it's simple and sufficient
|
||||
// for this use case
|
||||
signal(SIGILL, StackTraceHandler);
|
||||
signal(SIGSEGV, StackTraceHandler);
|
||||
signal(SIGBUS, StackTraceHandler);
|
||||
signal(SIGABRT, StackTraceHandler);
|
||||
|
||||
printf("Installed stack trace handler for SIGILL SIGSEGV SIGBUS SIGABRT\n");
|
||||
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#else // no-op for non-linux system for now
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
void InstallStackTraceHandler() {}
|
||||
|
||||
}
|
||||
|
||||
#endif // OS_LINUX
|
||||
+2
-1
@@ -23,7 +23,8 @@ inline uint32_t Block::NumRestarts() const {
|
||||
Block::Block(const BlockContents& contents)
|
||||
: data_(contents.data.data()),
|
||||
size_(contents.data.size()),
|
||||
owned_(contents.heap_allocated) {
|
||||
owned_(contents.heap_allocated),
|
||||
cachable_(contents.cachable) {
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
size_ = 0; // Error marker
|
||||
} else {
|
||||
|
||||
@@ -22,6 +22,7 @@ class Block {
|
||||
~Block();
|
||||
|
||||
size_t size() const { return size_; }
|
||||
bool isCachable() const { return cachable_; }
|
||||
Iterator* NewIterator(const Comparator* comparator);
|
||||
|
||||
private:
|
||||
@@ -31,6 +32,7 @@ class Block {
|
||||
size_t size_;
|
||||
uint32_t restart_offset_; // Offset in data_ of restart array
|
||||
bool owned_; // Block owns data_[]
|
||||
bool cachable_;
|
||||
|
||||
// No copying allowed
|
||||
Block(const Block&);
|
||||
|
||||
@@ -60,6 +60,21 @@ size_t BlockBuilder::CurrentSizeEstimate() const {
|
||||
sizeof(uint32_t)); // Restart array length
|
||||
}
|
||||
|
||||
size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key, const Slice& value)
|
||||
const {
|
||||
size_t estimate = CurrentSizeEstimate();
|
||||
estimate += key.size() + value.size();
|
||||
if (counter_ >= options_->block_restart_interval) {
|
||||
estimate += sizeof(uint32_t); // a new restart entry.
|
||||
}
|
||||
|
||||
estimate += sizeof(int32_t); // varint for shared prefix length.
|
||||
estimate += VarintLength(key.size()); // varint for key length.
|
||||
estimate += VarintLength(value.size()); // varint for value length.
|
||||
|
||||
return estimate;
|
||||
}
|
||||
|
||||
Slice BlockBuilder::Finish() {
|
||||
// Append restart array
|
||||
for (size_t i = 0; i < restarts_.size(); i++) {
|
||||
|
||||
@@ -34,6 +34,9 @@ class BlockBuilder {
|
||||
// we are building.
|
||||
size_t CurrentSizeEstimate() const;
|
||||
|
||||
// Returns an estimated block size after appending key and value.
|
||||
size_t EstimateSizeAfterKV(const Slice& key, const Slice& value) const;
|
||||
|
||||
// Return true iff no entries have been added since the last Reset()
|
||||
bool empty() const {
|
||||
return buffer_.empty();
|
||||
|
||||
+5
-7
@@ -1,9 +1,6 @@
|
||||
// Copyright (c) 2012 Facebook.
|
||||
// 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 "leveldb/table.h"
|
||||
|
||||
#include <string>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/memtable.h"
|
||||
@@ -14,6 +11,7 @@
|
||||
#include "leveldb/table_builder.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"
|
||||
@@ -60,7 +58,7 @@ TEST(BlockTest, SimpleTest) {
|
||||
// read serialized contents of the block
|
||||
Slice rawblock = builder.Finish();
|
||||
|
||||
// create block reader
|
||||
// create block reader
|
||||
BlockContents contents;
|
||||
contents.data = rawblock;
|
||||
contents.cachable = false;
|
||||
@@ -71,7 +69,7 @@ TEST(BlockTest, SimpleTest) {
|
||||
int count = 0;
|
||||
Iterator* iter = reader.NewIterator(options.comparator);
|
||||
for (iter->SeekToFirst();iter->Valid(); count++, iter->Next()) {
|
||||
|
||||
|
||||
// read kv from block
|
||||
Slice k = iter->key();
|
||||
Slice v = iter->value();
|
||||
@@ -81,8 +79,8 @@ TEST(BlockTest, SimpleTest) {
|
||||
ASSERT_EQ(v.ToString().compare(values[count]), 0);
|
||||
}
|
||||
delete iter;
|
||||
|
||||
// read block contents randomly
|
||||
|
||||
// read block contents randomly
|
||||
iter = reader.NewIterator(options.comparator);
|
||||
for (int i = 0; i < num_records; i++) {
|
||||
|
||||
|
||||
+4
-4
@@ -66,10 +66,10 @@ Status Footer::DecodeFrom(Slice* input) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Status ReadBlock(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result) {
|
||||
Status ReadBlockContents(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result) {
|
||||
result->data = Slice();
|
||||
result->cachable = false;
|
||||
result->heap_allocated = false;
|
||||
|
||||
+4
-4
@@ -91,10 +91,10 @@ struct BlockContents {
|
||||
|
||||
// Read the block identified by "handle" from "file". On failure
|
||||
// return non-OK. On success fill *result and return OK.
|
||||
extern Status ReadBlock(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result);
|
||||
extern Status ReadBlockContents(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* result);
|
||||
|
||||
// Implementation details follow. Clients should ignore,
|
||||
|
||||
|
||||
+103
-46
@@ -2,7 +2,7 @@
|
||||
// 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 "leveldb/table.h"
|
||||
#include "table/table.h"
|
||||
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/comparator.h"
|
||||
@@ -10,11 +10,14 @@
|
||||
#include "leveldb/filter_policy.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/statistics.h"
|
||||
|
||||
#include "table/block.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -29,8 +32,12 @@ struct Table::Rep {
|
||||
delete [] filter_data;
|
||||
delete index_block;
|
||||
}
|
||||
Rep(const EnvOptions& storage_options) :
|
||||
soptions(storage_options) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
const EnvOptions& soptions;
|
||||
Status status;
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
char cache_key_prefix[kMaxCacheKeyPrefixSize];
|
||||
@@ -61,7 +68,34 @@ void Table::SetupCacheKeyPrefix(Rep* rep) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace { // anonymous namespace, not visible externally
|
||||
|
||||
// Read the block identified by "handle" from "file".
|
||||
// The only relevant option is options.verify_checksums for now.
|
||||
// Set *didIO to true if didIO is not null.
|
||||
// On failure return non-OK.
|
||||
// On success fill *result and return OK - caller owns *result
|
||||
Status ReadBlock(RandomAccessFile* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
Block** result,
|
||||
bool* didIO = nullptr) {
|
||||
BlockContents contents;
|
||||
Status s = ReadBlockContents(file, options, handle, &contents);
|
||||
if (s.ok()) {
|
||||
*result = new Block(contents);
|
||||
}
|
||||
|
||||
if (didIO) {
|
||||
*didIO = true;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // end of anonymous namespace
|
||||
|
||||
Status Table::Open(const Options& options,
|
||||
const EnvOptions& soptions,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t size,
|
||||
unique_ptr<Table>* table) {
|
||||
@@ -87,20 +121,14 @@ Status Table::Open(const Options& options,
|
||||
s = footer.DecodeFrom(&footer_input);
|
||||
if (!s.ok()) return s;
|
||||
|
||||
// Read the index block
|
||||
BlockContents contents;
|
||||
Block* index_block = nullptr;
|
||||
if (s.ok()) {
|
||||
s = ReadBlock(file.get(), ReadOptions(), footer.index_handle(), &contents);
|
||||
if (s.ok()) {
|
||||
index_block = new Block(contents);
|
||||
}
|
||||
}
|
||||
// TODO: we never really verify check sum for index block
|
||||
s = ReadBlock(file.get(), ReadOptions(), footer.index_handle(), &index_block);
|
||||
|
||||
if (s.ok()) {
|
||||
// We've successfully read the footer and the index block: we're
|
||||
// ready to serve requests.
|
||||
Rep* rep = new Table::Rep;
|
||||
Rep* rep = new Table::Rep(soptions);
|
||||
rep->options = options;
|
||||
rep->file = std::move(file);
|
||||
rep->metaindex_handle = footer.metaindex_handle();
|
||||
@@ -117,6 +145,25 @@ Status Table::Open(const Options& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
void Table::SetupForCompaction() {
|
||||
switch (rep_->options.access_hint_on_compaction_start) {
|
||||
case Options::NONE:
|
||||
break;
|
||||
case Options::NORMAL:
|
||||
rep_->file->Hint(RandomAccessFile::NORMAL);
|
||||
break;
|
||||
case Options::SEQUENTIAL:
|
||||
rep_->file->Hint(RandomAccessFile::SEQUENTIAL);
|
||||
break;
|
||||
case Options::WILLNEED:
|
||||
rep_->file->Hint(RandomAccessFile::WILLNEED);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
compaction_optimized_ = true;
|
||||
}
|
||||
|
||||
void Table::ReadMeta(const Footer& footer) {
|
||||
if (rep_->options.filter_policy == nullptr) {
|
||||
return; // Do not need any metadata
|
||||
@@ -124,14 +171,13 @@ void Table::ReadMeta(const Footer& footer) {
|
||||
|
||||
// TODO(sanjay): Skip this if footer.metaindex_handle() size indicates
|
||||
// it is an empty block.
|
||||
ReadOptions opt;
|
||||
BlockContents contents;
|
||||
if (!ReadBlock(rep_->file.get(), opt, footer.metaindex_handle(),
|
||||
&contents).ok()) {
|
||||
// TODO: we never really verify check sum for meta index block
|
||||
Block* meta = nullptr;
|
||||
if (!ReadBlock(rep_->file.get(), ReadOptions(), footer.metaindex_handle(),
|
||||
&meta).ok()) {
|
||||
// Do not propagate errors since meta info is not needed for operation
|
||||
return;
|
||||
}
|
||||
Block* meta = new Block(contents);
|
||||
|
||||
Iterator* iter = meta->NewIterator(BytewiseComparator());
|
||||
std::string key = "filter.";
|
||||
@@ -151,11 +197,11 @@ void Table::ReadFilter(const Slice& filter_handle_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We might want to unify with ReadBlock() if we start
|
||||
// TODO: We might want to unify with ReadBlock() if we start
|
||||
// requiring checksum verification in Table::Open.
|
||||
ReadOptions opt;
|
||||
BlockContents block;
|
||||
if (!ReadBlock(rep_->file.get(), opt, filter_handle, &block).ok()) {
|
||||
if (!ReadBlockContents(rep_->file.get(), opt, filter_handle, &block).ok()) {
|
||||
return;
|
||||
}
|
||||
if (block.heap_allocated) {
|
||||
@@ -188,10 +234,11 @@ static void ReleaseBlock(void* arg, void* h) {
|
||||
Iterator* Table::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value,
|
||||
bool* didIO) {
|
||||
bool* didIO,
|
||||
bool for_compaction) {
|
||||
Table* table = reinterpret_cast<Table*>(arg);
|
||||
Cache* block_cache = table->rep_->options.block_cache.get();
|
||||
Statistics* const statistics = table->rep_->options.statistics;
|
||||
std::shared_ptr<Statistics> statistics = table->rep_->options.statistics;
|
||||
Block* block = nullptr;
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
|
||||
@@ -202,7 +249,6 @@ Iterator* Table::BlockReader(void* arg,
|
||||
// can add more features in the future.
|
||||
|
||||
if (s.ok()) {
|
||||
BlockContents contents;
|
||||
if (block_cache != nullptr) {
|
||||
char cache_key[kMaxCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
const size_t cache_key_prefix_size = table->rep_->cache_key_prefix_size;
|
||||
@@ -219,28 +265,29 @@ Iterator* Table::BlockReader(void* arg,
|
||||
|
||||
RecordTick(statistics, BLOCK_CACHE_HIT);
|
||||
} else {
|
||||
s = ReadBlock(table->rep_->file.get(), options, handle, &contents);
|
||||
if (s.ok()) {
|
||||
block = new Block(contents);
|
||||
if (contents.cachable && options.fill_cache) {
|
||||
cache_handle = block_cache->Insert(
|
||||
key, block, block->size(), &DeleteCachedBlock);
|
||||
}
|
||||
Histograms histogram = for_compaction ?
|
||||
READ_BLOCK_COMPACTION_MICROS : READ_BLOCK_GET_MICROS;
|
||||
{ // block for stop watch
|
||||
StopWatch sw(table->rep_->options.env, statistics, histogram);
|
||||
s = ReadBlock(
|
||||
table->rep_->file.get(),
|
||||
options,
|
||||
handle,
|
||||
&block,
|
||||
didIO
|
||||
);
|
||||
}
|
||||
if (didIO != nullptr) {
|
||||
*didIO = true; // we did some io from storage
|
||||
if (s.ok()) {
|
||||
if (block->isCachable() && options.fill_cache) {
|
||||
cache_handle = block_cache->Insert(
|
||||
key, block, block->size(), &DeleteCachedBlock);
|
||||
}
|
||||
}
|
||||
|
||||
RecordTick(statistics, BLOCK_CACHE_MISS);
|
||||
}
|
||||
} else {
|
||||
s = ReadBlock(table->rep_->file.get(), options, handle, &contents);
|
||||
if (s.ok()) {
|
||||
block = new Block(contents);
|
||||
}
|
||||
if (didIO != nullptr) {
|
||||
*didIO = true; // we did some io from storage
|
||||
}
|
||||
s = ReadBlock(table->rep_->file.get(), options, handle, &block, didIO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,23 +307,26 @@ Iterator* Table::BlockReader(void* arg,
|
||||
|
||||
Iterator* Table::BlockReader(void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value) {
|
||||
return BlockReader(arg, options, index_value, nullptr);
|
||||
const EnvOptions& soptions,
|
||||
const Slice& index_value,
|
||||
bool for_compaction) {
|
||||
return BlockReader(arg, options, index_value, nullptr, for_compaction);
|
||||
}
|
||||
|
||||
Iterator* Table::NewIterator(const ReadOptions& options) const {
|
||||
return NewTwoLevelIterator(
|
||||
rep_->index_block->NewIterator(rep_->options.comparator),
|
||||
&Table::BlockReader, const_cast<Table*>(this), options);
|
||||
&Table::BlockReader, const_cast<Table*>(this), options, rep_->soptions);
|
||||
}
|
||||
|
||||
Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
void* arg,
|
||||
void (*saver)(void*, const Slice&, const Slice&, bool)) {
|
||||
bool (*saver)(void*, const Slice&, const Slice&,
|
||||
bool)) {
|
||||
Status s;
|
||||
Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
|
||||
iiter->Seek(k);
|
||||
if (iiter->Valid()) {
|
||||
bool done = false;
|
||||
for (iiter->Seek(k); iiter->Valid() && !done; iiter->Next()) {
|
||||
Slice handle_value = iiter->value();
|
||||
FilterBlockReader* filter = rep_->filter;
|
||||
BlockHandle handle;
|
||||
@@ -284,14 +334,20 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
handle.DecodeFrom(&handle_value).ok() &&
|
||||
!filter->KeyMayMatch(handle.offset(), k)) {
|
||||
// Not found
|
||||
// TODO: think about interaction with Merge. If a user key cannot
|
||||
// cross one data block, we should be fine.
|
||||
RecordTick(rep_->options.statistics, BLOOM_FILTER_USEFUL);
|
||||
break;
|
||||
} else {
|
||||
bool didIO = false;
|
||||
Iterator* block_iter = BlockReader(this, options, iiter->value(),
|
||||
&didIO);
|
||||
block_iter->Seek(k);
|
||||
if (block_iter->Valid()) {
|
||||
(*saver)(arg, block_iter->key(), block_iter->value(), didIO);
|
||||
|
||||
for (block_iter->Seek(k); block_iter->Valid(); block_iter->Next()) {
|
||||
if (!(*saver)(arg, block_iter->key(), block_iter->value(), didIO)) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s = block_iter->status();
|
||||
delete block_iter;
|
||||
@@ -304,8 +360,9 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
return s;
|
||||
}
|
||||
|
||||
void SaveDidIO(void* arg, const Slice& key, const Slice& value, bool didIO) {
|
||||
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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/env.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -39,6 +40,7 @@ class Table {
|
||||
//
|
||||
// *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);
|
||||
@@ -62,23 +64,29 @@ class Table {
|
||||
// REQUIRES: key is in this table.
|
||||
bool TEST_KeyInCache(const ReadOptions& options, const Slice& key);
|
||||
|
||||
// Set up the table for Compaction. Might change some parameters with
|
||||
// posix_fadvise
|
||||
void SetupForCompaction();
|
||||
|
||||
private:
|
||||
struct Rep;
|
||||
Rep* rep_;
|
||||
|
||||
explicit Table(Rep* rep) { rep_ = rep; }
|
||||
static Iterator* BlockReader(void*, const ReadOptions&, const Slice&);
|
||||
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);
|
||||
static Iterator* BlockReader(void*, const ReadOptions&, const Slice&,
|
||||
bool* didIO);
|
||||
bool* didIO, bool for_compaction = false);
|
||||
|
||||
// Calls (*handle_result)(arg, ...) with the entry found after a call
|
||||
// to Seek(key). May not make such a call if filter policy says
|
||||
// that key is not present.
|
||||
// Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found
|
||||
// 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,
|
||||
void (*handle_result)(void* arg, const Slice& k, const Slice& v, bool));
|
||||
bool (*handle_result)(void* arg, const Slice& k, const Slice& v, bool));
|
||||
|
||||
|
||||
void ReadMeta(const Footer& footer);
|
||||
+18
-5
@@ -14,6 +14,7 @@
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -98,6 +99,22 @@ void TableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
assert(r->options.comparator->Compare(key, Slice(r->last_key)) > 0);
|
||||
}
|
||||
|
||||
const size_t curr_size = r->data_block.CurrentSizeEstimate();
|
||||
const size_t estimated_size_after = r->data_block.EstimateSizeAfterKV(key,
|
||||
value);
|
||||
// Do flush if one of the below two conditions is true:
|
||||
// 1) if the current estimated size already exceeds the block size,
|
||||
// 2) block_size_deviation is set and the estimated size after appending
|
||||
// the kv will exceed the block size and the current size is under the
|
||||
// the deviation.
|
||||
if (curr_size >= r->options.block_size ||
|
||||
(estimated_size_after > r->options.block_size &&
|
||||
r->options.block_size_deviation > 0 &&
|
||||
(curr_size * 100) >
|
||||
r->options.block_size * (100 - r->options.block_size_deviation))) {
|
||||
Flush();
|
||||
}
|
||||
|
||||
if (r->pending_index_entry) {
|
||||
assert(r->data_block.empty());
|
||||
r->options.comparator->FindShortestSeparator(&r->last_key, key);
|
||||
@@ -114,11 +131,6 @@ void TableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
r->last_key.assign(key.data(), key.size());
|
||||
r->num_entries++;
|
||||
r->data_block.Add(key, value);
|
||||
|
||||
const size_t estimated_block_size = r->data_block.CurrentSizeEstimate();
|
||||
if (estimated_block_size >= r->options.block_size) {
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void TableBuilder::Flush() {
|
||||
@@ -220,6 +232,7 @@ void TableBuilder::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);
|
||||
handle->set_size(block_contents.size());
|
||||
r->status = r->file->Append(block_contents);
|
||||
|
||||
+4
-5
@@ -1,9 +1,6 @@
|
||||
// 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 "leveldb/table.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "db/dbformat.h"
|
||||
@@ -16,6 +13,7 @@
|
||||
#include "table/block.h"
|
||||
#include "table/block_builder.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -257,7 +255,7 @@ class TableConstructor: public Constructor {
|
||||
// Open the table
|
||||
uniq_id_ = cur_uniq_id_++;
|
||||
source_.reset(new StringSource(sink_->contents(), uniq_id_));
|
||||
return Table::Open(options, std::move(source_),
|
||||
return Table::Open(options, soptions, std::move(source_),
|
||||
sink_->contents().size(), &table_);
|
||||
}
|
||||
|
||||
@@ -271,7 +269,7 @@ class TableConstructor: public Constructor {
|
||||
|
||||
virtual Status Reopen(const Options& options) {
|
||||
source_.reset(new StringSource(sink_->contents(), uniq_id_));
|
||||
return Table::Open(options, std::move(source_),
|
||||
return Table::Open(options, soptions, std::move(source_),
|
||||
sink_->contents().size(), &table_);
|
||||
}
|
||||
|
||||
@@ -295,6 +293,7 @@ class TableConstructor: public Constructor {
|
||||
TableConstructor();
|
||||
|
||||
static uint64_t cur_uniq_id_;
|
||||
const EnvOptions soptions;
|
||||
};
|
||||
uint64_t TableConstructor::cur_uniq_id_ = 1;
|
||||
|
||||
|
||||
@@ -4,16 +4,18 @@
|
||||
|
||||
#include "table/two_level_iterator.h"
|
||||
|
||||
#include "leveldb/table.h"
|
||||
#include "table/block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/table.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef Iterator* (*BlockFunction)(void*, const ReadOptions&, const Slice&);
|
||||
typedef Iterator* (*BlockFunction)(void*, const ReadOptions&,
|
||||
const EnvOptions& soptions, const Slice&,
|
||||
bool for_compaction);
|
||||
|
||||
class TwoLevelIterator: public Iterator {
|
||||
public:
|
||||
@@ -21,7 +23,9 @@ class TwoLevelIterator: public Iterator {
|
||||
Iterator* index_iter,
|
||||
BlockFunction block_function,
|
||||
void* arg,
|
||||
const ReadOptions& options);
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
bool for_compaction);
|
||||
|
||||
virtual ~TwoLevelIterator();
|
||||
|
||||
@@ -65,24 +69,30 @@ class TwoLevelIterator: public Iterator {
|
||||
BlockFunction block_function_;
|
||||
void* arg_;
|
||||
const ReadOptions options_;
|
||||
const EnvOptions& soptions_;
|
||||
Status status_;
|
||||
IteratorWrapper index_iter_;
|
||||
IteratorWrapper data_iter_; // May be nullptr
|
||||
// If data_iter_ is non-nullptr, then "data_block_handle_" holds the
|
||||
// "index_value" passed to block_function_ to create the data_iter_.
|
||||
std::string data_block_handle_;
|
||||
bool for_compaction_;
|
||||
};
|
||||
|
||||
TwoLevelIterator::TwoLevelIterator(
|
||||
Iterator* index_iter,
|
||||
BlockFunction block_function,
|
||||
void* arg,
|
||||
const ReadOptions& options)
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
bool for_compaction)
|
||||
: block_function_(block_function),
|
||||
arg_(arg),
|
||||
options_(options),
|
||||
soptions_(soptions),
|
||||
index_iter_(index_iter),
|
||||
data_iter_(nullptr) {
|
||||
data_iter_(nullptr),
|
||||
for_compaction_(for_compaction) {
|
||||
}
|
||||
|
||||
TwoLevelIterator::~TwoLevelIterator() {
|
||||
@@ -163,7 +173,8 @@ void TwoLevelIterator::InitDataBlock() {
|
||||
// data_iter_ is already constructed with this iterator, so
|
||||
// no need to change anything
|
||||
} else {
|
||||
Iterator* iter = (*block_function_)(arg_, options_, handle);
|
||||
Iterator* iter = (*block_function_)(arg_, options_, soptions_, handle,
|
||||
for_compaction_);
|
||||
data_block_handle_.assign(handle.data(), handle.size());
|
||||
SetDataIterator(iter);
|
||||
}
|
||||
@@ -176,8 +187,11 @@ Iterator* NewTwoLevelIterator(
|
||||
Iterator* index_iter,
|
||||
BlockFunction block_function,
|
||||
void* arg,
|
||||
const ReadOptions& options) {
|
||||
return new TwoLevelIterator(index_iter, block_function, arg, options);
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
bool for_compaction) {
|
||||
return new TwoLevelIterator(index_iter, block_function, arg,
|
||||
options, soptions, for_compaction);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -6,6 +6,7 @@
|
||||
#define STORAGE_LEVELDB_TABLE_TWO_LEVEL_ITERATOR_H_
|
||||
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/env.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -25,9 +26,13 @@ extern Iterator* NewTwoLevelIterator(
|
||||
Iterator* (*block_function)(
|
||||
void* arg,
|
||||
const ReadOptions& options,
|
||||
const Slice& index_value),
|
||||
const EnvOptions& soptions,
|
||||
const Slice& index_value,
|
||||
bool for_compaction),
|
||||
void* arg,
|
||||
const ReadOptions& options);
|
||||
const ReadOptions& options,
|
||||
const EnvOptions& soptions,
|
||||
bool for_compaction = false);
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
#! /usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import getopt
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
# This python script runs and kills db_stress multiple times with
|
||||
# test-batches-snapshot ON,
|
||||
# total operations much less than the total keys, and
|
||||
# a high read percentage.
|
||||
# This checks consistency in case of unsafe crashes in Rocksdb
|
||||
|
||||
def main(argv):
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "hd:t:i:o:b:")
|
||||
except getopt.GetoptError:
|
||||
print("db_crashtest.py -d <duration_test> -t <#threads> "
|
||||
"-i <interval for one run> -o <ops_per_thread>\n")
|
||||
sys.exit(2)
|
||||
|
||||
# default values, will be overridden by cmdline args
|
||||
interval = 120 # time for one db_stress instance to run
|
||||
duration = 6000 # total time for this script to test db_stress
|
||||
threads = 32
|
||||
# since we will be killing anyway, use large value for ops_per_thread
|
||||
ops_per_thread = 100000000
|
||||
write_buf_size = 4 * 1024 * 1024
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print("db_crashtest.py -d <duration_test>"
|
||||
" -t <#threads> -i <interval for one run>"
|
||||
" -o <ops_per_thread> -b <write_buffer_size>\n")
|
||||
sys.exit()
|
||||
elif opt == ("-d"):
|
||||
duration = int(arg)
|
||||
elif opt == ("-t"):
|
||||
threads = int(arg)
|
||||
elif opt == ("-i"):
|
||||
interval = int(arg)
|
||||
elif opt == ("-o"):
|
||||
ops_per_thread = int(arg)
|
||||
elif opt == ("-b"):
|
||||
write_buf_size = int(arg)
|
||||
else:
|
||||
print("db_crashtest.py -d <duration_test>"
|
||||
" -t <#threads> -i <interval for one run>"
|
||||
" -o <ops_per_thread> -b <write_buffer_size>\n")
|
||||
sys.exit(2)
|
||||
|
||||
exit_time = time.time() + duration
|
||||
|
||||
dirpath = tempfile.mkdtemp()
|
||||
|
||||
print("Running blackbox-crash-test with \ninterval_between_crash="
|
||||
+ str(interval) + "\ntotal-duration=" + str(duration)
|
||||
+ "\nthreads=" + str(threads) + "\nops_per_thread="
|
||||
+ str(ops_per_thread) + "\nwrite_buffer_size="
|
||||
+ str(write_buf_size) + "\n")
|
||||
|
||||
while time.time() < exit_time:
|
||||
run_had_errors = False
|
||||
additional_opts = ' --disable_seek_compaction=' + \
|
||||
str(random.randint(0, 1)) + \
|
||||
' --mmap_read=' + str(random.randint(0, 1)) + \
|
||||
' --block_size=16384 ' + \
|
||||
' --cache_size=1048576 ' + \
|
||||
' --open_files=500000 ' + \
|
||||
' --verify_checksum=1 ' + \
|
||||
' --sync=' + str(random.randint(0, 1)) + \
|
||||
' --disable_wal=0 ' + \
|
||||
' --disable_data_sync=' + \
|
||||
str(random.randint(0, 1)) + \
|
||||
' --target_file_size_base=2097152 ' + \
|
||||
' --target_file_size_multiplier=2 ' + \
|
||||
' --max_write_buffer_number=3 ' + \
|
||||
' --max_background_compactions=20 ' + \
|
||||
' --max_bytes_for_level_base=10485760'
|
||||
killtime = time.time() + interval
|
||||
child = subprocess.Popen(['./db_stress \
|
||||
--test_batches_snapshots=1 \
|
||||
--ops_per_thread=0' + str(ops_per_thread) + ' \
|
||||
--threads=0' + str(threads) + ' \
|
||||
--write_buffer_size=' + str(write_buf_size) + '\
|
||||
--destroy_db_initially=0 \
|
||||
--reopen=0 \
|
||||
--readpercent=50 \
|
||||
--db=' + dirpath + '\
|
||||
--max_key=100000000 ' + additional_opts],
|
||||
stderr=subprocess.PIPE, shell=True)
|
||||
print("Running db_stress with pid=%d and additional options=\n"
|
||||
% child.pid + additional_opts + "\n")
|
||||
time.sleep(interval)
|
||||
while True:
|
||||
if time.time() > killtime:
|
||||
if child.poll() is not None:
|
||||
print("WARNING: db_stress ended before kill\n")
|
||||
else:
|
||||
child.kill()
|
||||
print("KILLED %d\n" % child.pid)
|
||||
time.sleep(1) # time to stabilize after a kill
|
||||
|
||||
while True:
|
||||
line = child.stderr.readline().strip()
|
||||
if line != '':
|
||||
run_had_errors = True
|
||||
print('***' + line + '^')
|
||||
else:
|
||||
break
|
||||
if run_had_errors:
|
||||
sys.exit(2)
|
||||
break
|
||||
|
||||
time.sleep(1) # time to stabilize before the next run
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -0,0 +1,148 @@
|
||||
#! /usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import getopt
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
# This python script runs db_stress multiple times with kill_random_test
|
||||
# that causes leveldb to crash at various points in code.
|
||||
# It also has test-batches-snapshot ON so that basic atomic/consistency
|
||||
# checks can be performed.
|
||||
#
|
||||
def main(argv):
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "hd:t:k:o:b:")
|
||||
except getopt.GetoptError:
|
||||
print str(getopt.GetoptError)
|
||||
print "db_crashtest2.py -d <duration_test> -t <#threads> " \
|
||||
"-k <kills with prob 1/k> -o <ops_per_thread> "\
|
||||
"-b <write_buffer_size>\n"
|
||||
sys.exit(2)
|
||||
|
||||
# default values, will be overridden by cmdline args
|
||||
kill_random_test = 97 # kill with probability 1/97 by default
|
||||
duration = 6000 # total time for this script to test db_stress
|
||||
threads = 32
|
||||
ops_per_thread = 200000
|
||||
write_buf_size = 4 * 1024 * 1024
|
||||
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print "db_crashtest2.py -d <duration_test> -t <#threads> " \
|
||||
"-k <kills with prob 1/k> -o <ops_per_thread> "\
|
||||
"-b <write_buffer_size>\n"
|
||||
sys.exit()
|
||||
elif opt == ("-d"):
|
||||
duration = int(arg)
|
||||
elif opt == ("-t"):
|
||||
threads = int(arg)
|
||||
elif opt == ("-k"):
|
||||
kill_random_test = int(arg)
|
||||
elif opt == ("-i"):
|
||||
interval = int(arg)
|
||||
elif opt == ("-o"):
|
||||
ops_per_thread = int(arg)
|
||||
elif opt == ("-b"):
|
||||
write_buf_size = int(arg)
|
||||
else:
|
||||
print "unrecognized option " + str(opt) + "\n"
|
||||
print "db_crashtest2.py -d <duration_test> -t <#threads> " \
|
||||
"-k <kills with prob 1/k> -o <ops_per_thread> " \
|
||||
"-b <write_buffer_size>\n"
|
||||
sys.exit(2)
|
||||
|
||||
exit_time = time.time() + duration
|
||||
|
||||
dirpath = tempfile.mkdtemp()
|
||||
|
||||
print("Running whitebox-crash-test with \ntotal-duration=" + str(duration)
|
||||
+ "\nthreads=" + str(threads) + "\nops_per_thread="
|
||||
+ str(ops_per_thread) + "\nwrite_buffer_size="
|
||||
+ str(write_buf_size) + "\n")
|
||||
|
||||
# kill in every alternate run. toggle tracks which run we are doing.
|
||||
toggle = True
|
||||
|
||||
while time.time() < exit_time:
|
||||
run_had_errors = False
|
||||
additional_opts = ' --disable_seek_compaction=' + \
|
||||
str(random.randint(0, 1)) + \
|
||||
' --mmap_read=' + str(random.randint(0, 1)) + \
|
||||
' --block_size=16384 ' + \
|
||||
' --cache_size=1048576 ' + \
|
||||
' --open_files=500000 ' + \
|
||||
' --verify_checksum=1 ' + \
|
||||
' --sync=' + str(random.randint(0, 1)) + \
|
||||
' --disable_wal=0 ' + \
|
||||
' --disable_data_sync=' + \
|
||||
str(random.randint(0, 1)) + \
|
||||
' --target_file_size_base=2097152 ' + \
|
||||
' --target_file_size_multiplier=2 ' + \
|
||||
' --max_write_buffer_number=3 ' + \
|
||||
' --max_background_compactions=20 ' + \
|
||||
' --max_bytes_for_level_base=10485760'
|
||||
print ("Running db_stress with additional options=\n"
|
||||
+ additional_opts + "\n")
|
||||
|
||||
if toggle:
|
||||
# since we are going to kill anyway, use more ops per thread
|
||||
new_ops_per_thread = 100 * ops_per_thread
|
||||
killoption = '--kill_random_test=' + str(kill_random_test)
|
||||
else:
|
||||
new_ops_per_thread = ops_per_thread
|
||||
killoption = ''
|
||||
|
||||
toggle = not toggle
|
||||
|
||||
cmd = ['./db_stress \
|
||||
--test_batches_snapshots=1 \
|
||||
--ops_per_thread=0' + str(new_ops_per_thread) + ' \
|
||||
--threads=0' + str(threads) + ' \
|
||||
--write_buffer_size=' + str(write_buf_size) + ' \
|
||||
--destroy_db_initially=0 ' + killoption + ' \
|
||||
--reopen=0 \
|
||||
--readpercent=50 \
|
||||
--db=' + dirpath + ' \
|
||||
--max_key=100000000 ' + additional_opts]
|
||||
|
||||
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
shell=True)
|
||||
stdoutdata, stderrdata = popen.communicate()
|
||||
retncode = popen.returncode
|
||||
msg = ("kill option = {0}, exitcode = {1}".format(
|
||||
killoption, retncode))
|
||||
print msg
|
||||
print stdoutdata
|
||||
|
||||
expected = False
|
||||
if (killoption == '') and (retncode == 0):
|
||||
# we expect zero retncode if no kill option
|
||||
expected = True
|
||||
elif killoption != '' and retncode < 0:
|
||||
# we expect negative retncode if kill option was given
|
||||
expected = True
|
||||
|
||||
if not expected:
|
||||
print "TEST FAILED. See kill option and exit code above!!!\n"
|
||||
sys.exit(1)
|
||||
|
||||
stdoutdata = stdoutdata.lower()
|
||||
errorcount = (stdoutdata.count('error') -
|
||||
stdoutdata.count('got errors 0 times'))
|
||||
print "#times error occured in output is " + str(errorcount) + "\n"
|
||||
|
||||
if (errorcount > 0):
|
||||
print "TEST FAILED. Output has 'error'!!!\n"
|
||||
sys.exit(2)
|
||||
if (stdoutdata.find('fail') >= 0):
|
||||
print "TEST FAILED. Output has 'fail'!!!\n"
|
||||
sys.exit(2)
|
||||
time.sleep(1) # time to stabilize after a kill
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
+87
-30
@@ -21,7 +21,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/db_statistics.h"
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/db.h"
|
||||
#include "utilities/utility_db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "leveldb/statistics.h"
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/testutil.h"
|
||||
#include "util/logging.h"
|
||||
#include "utilities/ttl/db_ttl.h"
|
||||
#include "hdfs/env_hdfs.h"
|
||||
|
||||
static const long KB = 1024;
|
||||
@@ -54,6 +56,11 @@ static bool FLAGS_test_batches_snapshots = false;
|
||||
// Number of concurrent threads to run.
|
||||
static int FLAGS_threads = 32;
|
||||
|
||||
// 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;
|
||||
|
||||
// Size of each value will be this number times rand_int(1,3) bytes
|
||||
static int FLAGS_value_size_mult = 8;
|
||||
|
||||
@@ -62,6 +69,9 @@ static bool FLAGS_verify_before_write = false;
|
||||
// Print histogram of operation timings
|
||||
static bool FLAGS_histogram = false;
|
||||
|
||||
// Destroys the database dir before start if this is true
|
||||
static bool FLAGS_destroy_db_initially = true;
|
||||
|
||||
static bool FLAGS_verbose = false;
|
||||
|
||||
// Number of bytes to buffer in memtable before compacting
|
||||
@@ -100,8 +110,11 @@ static const char* FLAGS_db = nullptr;
|
||||
// Verify checksum for every block read from storage
|
||||
static bool FLAGS_verify_checksum = false;
|
||||
|
||||
// Allow reads to occur via mmap-ing files
|
||||
static bool FLAGS_use_mmap_reads = leveldb::EnvOptions().use_mmap_reads;
|
||||
|
||||
// Database statistics
|
||||
static class leveldb::DBStatistics* dbstats;
|
||||
static std::shared_ptr<leveldb::Statistics> dbstats;
|
||||
|
||||
// Sync all writes to disk
|
||||
static bool FLAGS_sync = false;
|
||||
@@ -112,6 +125,10 @@ static bool FLAGS_disable_data_sync = false;
|
||||
// If true, issue fsync instead of fdatasync
|
||||
static bool FLAGS_use_fsync = false;
|
||||
|
||||
// If non-zero, kill at various points in source code with probability 1/this
|
||||
static int FLAGS_kill_random_test = 0;
|
||||
extern int leveldb_kill_odds;
|
||||
|
||||
// If true, do not write WAL for write.
|
||||
static bool FLAGS_disable_wal = false;
|
||||
|
||||
@@ -163,9 +180,8 @@ static uint32_t FLAGS_log2_keys_per_lock = 2; // implies 2^2 keys per lock
|
||||
// Percentage of times we want to purge redundant keys in memory before flushing
|
||||
static uint32_t FLAGS_purge_redundant_percent = 50;
|
||||
|
||||
extern bool useOsBuffer;
|
||||
extern bool useFsReadAhead;
|
||||
extern bool useMmapRead;
|
||||
// Level0 compaction start trigger
|
||||
static int FLAGS_level0_file_num_compaction_trigger = 0;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -232,8 +248,7 @@ class Stats {
|
||||
double micros = now - last_op_finish_;
|
||||
hist_.Add(micros);
|
||||
if (micros > 20000) {
|
||||
fprintf(stderr, "long op: %.1f micros%30s\r", micros, "");
|
||||
fflush(stderr);
|
||||
fprintf(stdout, "long op: %.1f micros%30s\r", micros, "");
|
||||
}
|
||||
last_op_finish_ = now;
|
||||
}
|
||||
@@ -247,8 +262,7 @@ class Stats {
|
||||
else if (next_report_ < 100000) next_report_ += 10000;
|
||||
else if (next_report_ < 500000) next_report_ += 50000;
|
||||
else next_report_ += 100000;
|
||||
fprintf(stderr, "... finished %ld ops%30s\r", done_, "");
|
||||
fflush(stderr);
|
||||
fprintf(stdout, "... finished %ld ops%30s\r", done_, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,14 +488,16 @@ class StressTest {
|
||||
: nullptr),
|
||||
db_(nullptr),
|
||||
num_times_reopened_(0) {
|
||||
std::vector<std::string> files;
|
||||
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]);
|
||||
if (FLAGS_destroy_db_initially) {
|
||||
std::vector<std::string> files;
|
||||
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]);
|
||||
}
|
||||
}
|
||||
DestroyDB(FLAGS_db, Options());
|
||||
}
|
||||
DestroyDB(FLAGS_db, Options());
|
||||
}
|
||||
|
||||
~StressTest() {
|
||||
@@ -688,7 +704,7 @@ class StressTest {
|
||||
char expected_prefix = (keys[i])[0];
|
||||
char actual_prefix = (values[i])[0];
|
||||
if (actual_prefix != expected_prefix) {
|
||||
fprintf(stderr, "expected prefix = %c actual = %c\n",
|
||||
fprintf(stderr, "error expected prefix = %c actual = %c\n",
|
||||
expected_prefix, actual_prefix);
|
||||
}
|
||||
(values[i])[0] = ' '; // blank out the differing character
|
||||
@@ -700,7 +716,7 @@ class StressTest {
|
||||
// Now that we retrieved all values, check that they all match
|
||||
for (int i = 1; i < 10; i++) {
|
||||
if (values[i] != values[0]) {
|
||||
fprintf(stderr, "inconsistent values for key %s: %s, %s\n",
|
||||
fprintf(stderr, "error : inconsistent values for key %s: %s, %s\n",
|
||||
key.ToString().c_str(), values[0].c_str(),
|
||||
values[i].c_str());
|
||||
// we continue after error rather than exiting so that we can
|
||||
@@ -859,6 +875,7 @@ class StressTest {
|
||||
for (size_t i=sizeof(uint32_t); i < value_sz; i++) {
|
||||
v[i] = (char)(rand ^ i);
|
||||
}
|
||||
v[value_sz] = '\0';
|
||||
return value_sz; // the size of the value set.
|
||||
}
|
||||
|
||||
@@ -867,7 +884,13 @@ class StressTest {
|
||||
kMajorVersion, kMinorVersion);
|
||||
fprintf(stdout, "Number of threads : %d\n", FLAGS_threads);
|
||||
fprintf(stdout, "Ops per thread : %d\n", FLAGS_ops_per_thread);
|
||||
std::string ttl_state("unused");
|
||||
if (FLAGS_ttl > 0) {
|
||||
ttl_state = NumberToString(FLAGS_ttl);
|
||||
}
|
||||
fprintf(stdout, "Time to live(sec) : %s\n", ttl_state.c_str());
|
||||
fprintf(stdout, "Read percentage : %d\n", FLAGS_readpercent);
|
||||
fprintf(stdout, "Write-buffer-size : %d\n", FLAGS_write_buffer_size);
|
||||
fprintf(stdout, "Delete percentage : %d\n", FLAGS_delpercent);
|
||||
fprintf(stdout, "Max key : %ld\n", FLAGS_max_key);
|
||||
fprintf(stdout, "Ratio #ops/#keys : %ld\n",
|
||||
@@ -914,6 +937,8 @@ 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;
|
||||
leveldb_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;
|
||||
options.max_bytes_for_level_base = FLAGS_max_bytes_for_level_base;
|
||||
@@ -922,6 +947,8 @@ class StressTest {
|
||||
options.level0_stop_writes_trigger = FLAGS_level0_stop_writes_trigger;
|
||||
options.level0_slowdown_writes_trigger =
|
||||
FLAGS_level0_slowdown_writes_trigger;
|
||||
options.level0_file_num_compaction_trigger =
|
||||
FLAGS_level0_file_num_compaction_trigger;
|
||||
options.compression = FLAGS_compression_type;
|
||||
options.create_if_missing = true;
|
||||
options.disable_seek_compaction = FLAGS_disable_seek_compaction;
|
||||
@@ -932,7 +959,15 @@ class StressTest {
|
||||
if (purge_percent.Uniform(100) < FLAGS_purge_redundant_percent - 1) {
|
||||
options.purge_redundant_kvs_while_flush = false;
|
||||
}
|
||||
Status s = DB::Open(options, FLAGS_db, &db_);
|
||||
|
||||
fprintf(stdout, "DB path: [%s]\n", FLAGS_db);
|
||||
|
||||
Status s;
|
||||
if (FLAGS_ttl == -1) {
|
||||
s = DB::Open(options, FLAGS_db, &db_);
|
||||
} else {
|
||||
s = UtilityDB::OpenTtlDB(options, FLAGS_db, &db_, FLAGS_ttl);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "open error: %s\n", s.ToString().c_str());
|
||||
exit(1);
|
||||
@@ -942,7 +977,11 @@ class StressTest {
|
||||
void Reopen() {
|
||||
// do not close the db. Just delete the lock file. This
|
||||
// simulates a crash-recovery kind of situation.
|
||||
((DBImpl*) db_)->TEST_Destroy_DBImpl();
|
||||
if (FLAGS_ttl != -1) {
|
||||
((DBWithTTL*) db_)->TEST_Destroy_DBWithTtl();
|
||||
} else {
|
||||
((DBImpl*) db_)->TEST_Destroy_DBImpl();
|
||||
}
|
||||
db_ = nullptr;
|
||||
|
||||
num_times_reopened_++;
|
||||
@@ -977,6 +1016,12 @@ int main(int argc, char** argv) {
|
||||
FLAGS_open_files = leveldb::Options().max_open_files;
|
||||
FLAGS_max_background_compactions =
|
||||
leveldb::Options().max_background_compactions;
|
||||
FLAGS_level0_file_num_compaction_trigger =
|
||||
leveldb::Options().level0_file_num_compaction_trigger;
|
||||
FLAGS_level0_slowdown_writes_trigger =
|
||||
leveldb::Options().level0_slowdown_writes_trigger;
|
||||
FLAGS_level0_stop_writes_trigger =
|
||||
leveldb::Options().level0_stop_writes_trigger;
|
||||
// Compression test code above refers to FLAGS_block_size
|
||||
FLAGS_block_size = leveldb::Options().block_size;
|
||||
std::string default_db_path;
|
||||
@@ -1002,6 +1047,9 @@ int main(int argc, char** argv) {
|
||||
} 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;
|
||||
@@ -1010,6 +1058,8 @@ int main(int argc, char** argv) {
|
||||
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) {
|
||||
@@ -1033,19 +1083,13 @@ int main(int argc, char** argv) {
|
||||
} 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], "--bufferedio=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
useOsBuffer = n;
|
||||
} else if (sscanf(argv[i], "--mmap_read=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
useMmapRead = n;
|
||||
} else if (sscanf(argv[i], "--readhead=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
useFsReadAhead = n;
|
||||
FLAGS_use_mmap_reads = n;
|
||||
} else if (sscanf(argv[i], "--statistics=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
if (n == 1) {
|
||||
dbstats = new leveldb::DBStatistics();
|
||||
dbstats = leveldb::CreateDBStatistics();
|
||||
}
|
||||
} else if (sscanf(argv[i], "--sync=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
@@ -1062,6 +1106,9 @@ int main(int argc, char** argv) {
|
||||
} 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;
|
||||
@@ -1085,6 +1132,9 @@ int main(int argc, char** argv) {
|
||||
} 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"))
|
||||
@@ -1121,6 +1171,16 @@ int main(int argc, char** argv) {
|
||||
fprintf(stderr, "Error: Read + Delete percents > 100!\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_disable_wal == 1 && FLAGS_reopen > 0) {
|
||||
fprintf(stderr, "Error: Db cannot reopen safely with disable_wal set!\n");
|
||||
exit(1);
|
||||
}
|
||||
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,
|
||||
FLAGS_ops_per_thread);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db == nullptr) {
|
||||
@@ -1131,8 +1191,5 @@ int main(int argc, char** argv) {
|
||||
|
||||
leveldb::StressTest stress;
|
||||
stress.Run();
|
||||
if (dbstats) {
|
||||
delete dbstats;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+4
-87
@@ -2,93 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "util/ldb_cmd.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class LDBCommandRunner {
|
||||
public:
|
||||
|
||||
static void PrintHelp(const char* exec_name) {
|
||||
string ret;
|
||||
|
||||
ret.append("ldb - LevelDB Tool");
|
||||
ret.append("\n\n");
|
||||
ret.append("All commands MUST specify --" + LDBCommand::ARG_DB +
|
||||
"=<full_path_to_db_directory>\n");
|
||||
ret.append("\n");
|
||||
ret.append("The following optional parameters control if keys/values are "
|
||||
"input/output as hex or as plain strings:\n");
|
||||
ret.append(" --" + LDBCommand::ARG_KEY_HEX +
|
||||
" : Keys are input/output as hex\n");
|
||||
ret.append(" --" + LDBCommand::ARG_VALUE_HEX +
|
||||
" : Values are input/output as hex\n");
|
||||
ret.append(" --" + LDBCommand::ARG_HEX +
|
||||
" : Both keys and values are input/output as hex\n");
|
||||
ret.append("\n");
|
||||
|
||||
ret.append("The following optional parameters control the database "
|
||||
"internals:\n");
|
||||
ret.append(" --" + LDBCommand::ARG_BLOOM_BITS + "=<int,e.g.:14>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_COMPRESSION_TYPE +
|
||||
"=<no|snappy|zlib|bzip2>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_BLOCK_SIZE +
|
||||
"=<block_size_in_bytes>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_AUTO_COMPACTION + "=<true|false>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_WRITE_BUFFER_SIZE +
|
||||
"=<int,e.g.:4194304>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_FILE_SIZE + "=<int,e.g.:2097152>\n");
|
||||
|
||||
ret.append("\n\n");
|
||||
ret.append("Data Access Commands:\n");
|
||||
PutCommand::Help(ret);
|
||||
GetCommand::Help(ret);
|
||||
BatchPutCommand::Help(ret);
|
||||
ScanCommand::Help(ret);
|
||||
DeleteCommand::Help(ret);
|
||||
DBQuerierCommand::Help(ret);
|
||||
ApproxSizeCommand::Help(ret);
|
||||
|
||||
ret.append("\n\n");
|
||||
ret.append("Admin Commands:\n");
|
||||
WALDumperCommand::Help(ret);
|
||||
CompactorCommand::Help(ret);
|
||||
ReduceDBLevelsCommand::Help(ret);
|
||||
DBDumperCommand::Help(ret);
|
||||
DBLoaderCommand::Help(ret);
|
||||
|
||||
fprintf(stderr, "%s\n", ret.c_str());
|
||||
}
|
||||
|
||||
static void RunCommand(int argc, char** argv) {
|
||||
if (argc <= 2) {
|
||||
PrintHelp(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
LDBCommand* cmdObj = LDBCommand::InitFromCmdLineArgs(argc, argv);
|
||||
if (cmdObj == NULL) {
|
||||
fprintf(stderr, "Unknown command\n");
|
||||
PrintHelp(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!cmdObj->ValidateCmdLineOptions()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cmdObj->Run();
|
||||
LDBCommandExecuteResult ret = cmdObj->GetExecuteState();
|
||||
fprintf(stderr, "%s\n", ret.ToString().c_str());
|
||||
delete cmdObj;
|
||||
|
||||
exit(ret.IsFailed());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#include "leveldb/ldb_tool.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
leveldb::LDBCommandRunner::RunCommand(argc, argv);
|
||||
leveldb::LDBTool tool;
|
||||
tool.Run(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
+30
-5
@@ -43,7 +43,7 @@ class LDBTestCase(unittest.TestCase):
|
||||
def dbParam(self, dbName):
|
||||
return "--db=%s" % os.path.join(self.TMP_DIR, dbName)
|
||||
|
||||
def assertRunOKFull(self, params, expectedOutput):
|
||||
def assertRunOKFull(self, params, expectedOutput, unexpected=False):
|
||||
"""
|
||||
All command-line params must be specified.
|
||||
Allows full flexibility in testing; for example: missing db param.
|
||||
@@ -52,7 +52,10 @@ class LDBTestCase(unittest.TestCase):
|
||||
|
||||
output = my_check_output("./ldb %s |grep -v \"Created bg thread\"" %
|
||||
params, shell=True)
|
||||
self.assertEquals(output.strip(), expectedOutput.strip());
|
||||
if not unexpected:
|
||||
self.assertEqual(output.strip(), expectedOutput.strip())
|
||||
else:
|
||||
self.assertNotEqual(output.strip(), expectedOutput.strip())
|
||||
|
||||
def assertRunFAILFull(self, params):
|
||||
"""
|
||||
@@ -70,13 +73,13 @@ class LDBTestCase(unittest.TestCase):
|
||||
"Exception should have been raised for command with params: %s" %
|
||||
params)
|
||||
|
||||
def assertRunOK(self, params, expectedOutput):
|
||||
def assertRunOK(self, params, expectedOutput, unexpected=False):
|
||||
"""
|
||||
Uses the default test db.
|
||||
|
||||
"""
|
||||
self.assertRunOKFull("%s %s" % (self.dbParam(self.DB_NAME), params),
|
||||
expectedOutput)
|
||||
expectedOutput, unexpected)
|
||||
|
||||
def assertRunFAIL(self, params):
|
||||
"""
|
||||
@@ -123,7 +126,7 @@ class LDBTestCase(unittest.TestCase):
|
||||
self.assertRunOK("scan", "x2 : y2\nx3 : y3")
|
||||
|
||||
self.assertRunOK("delete NonExistentKey", "OK")
|
||||
# It is wierd that GET and SCAN raise exception for
|
||||
# It is weird that GET and SCAN raise exception for
|
||||
# non-existent key, while delete does not
|
||||
|
||||
def dumpDb(self, params, dumpFile):
|
||||
@@ -142,6 +145,16 @@ class LDBTestCase(unittest.TestCase):
|
||||
self.assertRunFAIL("batchput k1")
|
||||
self.assertRunFAIL("batchput k1 v1 k2")
|
||||
|
||||
def testInvalidCmdLines(self):
|
||||
print "Running testInvalidCmdLines..."
|
||||
# db not specified
|
||||
self.assertRunFAILFull("put 0x6133 0x6233 --hex --create_if_missing")
|
||||
# No param called he
|
||||
self.assertRunFAIL("put 0x6133 0x6233 --he --create_if_missing")
|
||||
# max_keys is not applicable for put
|
||||
self.assertRunFAIL("put 0x6133 0x6233 --max_keys=1 --create_if_missing")
|
||||
# hex has invalid boolean value
|
||||
|
||||
def testHexPutGet(self):
|
||||
print "Running testHexPutGet..."
|
||||
self.assertRunOK("put a1 b1 --create_if_missing", "OK")
|
||||
@@ -171,6 +184,18 @@ class LDBTestCase(unittest.TestCase):
|
||||
self.assertRunOK("delete --hex 0x6133", "OK")
|
||||
self.assertRunOK("scan", "a1 : b1\na2 : b2\na4 : b4")
|
||||
|
||||
def testTtlPutGet(self):
|
||||
print "Running testTtlPutGet..."
|
||||
self.assertRunOK("put a1 b1 --ttl --create_if_missing", "OK")
|
||||
self.assertRunOK("scan ", "a1 : b1", True)
|
||||
self.assertRunOK("dump --ttl ", "a1 ==> b1", True)
|
||||
self.assertRunOK("scan --hex --ttl", "0x6131 : 0x6231")
|
||||
self.assertRunOK("get a1", "b1", True)
|
||||
self.assertRunOK("get --ttl a1", "b1")
|
||||
self.assertRunOK("put a3 b3 --create_if_missing", "OK")
|
||||
# fails because timstamp's length is greater than value's
|
||||
self.assertRunFAIL("get --ttl a3")
|
||||
|
||||
def testInvalidCmdLines(self):
|
||||
print "Running testInvalidCmdLines..."
|
||||
# db not specified
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/version_set.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include "db/filename.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/table_builder.h"
|
||||
#include "table/merger.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
static int verbose = 0;
|
||||
static int output_hex = 0;
|
||||
|
||||
using namespace leveldb;
|
||||
|
||||
//
|
||||
// Takes a manifest file and dumps out all metedata
|
||||
//
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
// parse command line options
|
||||
size_t n;
|
||||
char junk;
|
||||
int foundfile = 0;
|
||||
std::string manifestfile;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string param(argv[i]);
|
||||
if ((n = param.find("--file=")) != std::string::npos) {
|
||||
manifestfile = param.substr(strlen("--file="));
|
||||
foundfile = 1;
|
||||
} else if (sscanf(argv[i], "--verbose=%ld%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
verbose = n;
|
||||
} else if (param == "--output_hex") {
|
||||
output_hex = n;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown or badly-formatted option: %s\n",
|
||||
argv[i]);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (!foundfile) {
|
||||
fprintf(stderr,
|
||||
"%s [--verbose=0|1] [--output_hex] [--file=pathname of manifest file]\n",
|
||||
argv[0]);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
printf("Processing Manifest file %s\n", manifestfile.c_str());
|
||||
}
|
||||
|
||||
Options options;
|
||||
std::string file(manifestfile);
|
||||
std::string dbname("dummy");
|
||||
TableCache* tc = new TableCache(dbname, &options, 10);
|
||||
const InternalKeyComparator* cmp = new InternalKeyComparator(options.comparator);
|
||||
|
||||
VersionSet* versions = new VersionSet(dbname, &options,
|
||||
tc, cmp);
|
||||
Status s = versions->DumpManifest(options, file, verbose, output_hex);
|
||||
if (!s.ok()) {
|
||||
printf("Error in processing file %s %s\n", manifestfile.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
if (verbose) {
|
||||
printf("Processing Manifest file %s done\n", manifestfile.c_str());
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
ReduceLevelTest() {
|
||||
dbname_ = test::TmpDir() + "/db_reduce_levels_test";
|
||||
DestroyDB(dbname_, Options());
|
||||
db_ = NULL;
|
||||
db_ = nullptr;
|
||||
}
|
||||
|
||||
Status OpenDB(bool create_if_missing, int levels,
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
}
|
||||
|
||||
Status CompactMemTable() {
|
||||
if (db_ == NULL) {
|
||||
if (db_ == nullptr) {
|
||||
return Status::InvalidArgument("DB not opened.");
|
||||
}
|
||||
DBImpl* db_impl = reinterpret_cast<DBImpl*>(db_);
|
||||
@@ -49,9 +49,9 @@ public:
|
||||
}
|
||||
|
||||
void CloseDB() {
|
||||
if (db_ != NULL) {
|
||||
if (db_ != nullptr) {
|
||||
delete db_;
|
||||
db_ = NULL;
|
||||
db_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -1,4 +1,4 @@
|
||||
#include "leveldb/table.h"
|
||||
#include "table/table.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -22,7 +22,7 @@ namespace leveldb {
|
||||
|
||||
class SstFileReader {
|
||||
public:
|
||||
SstFileReader(std::string file_name,
|
||||
explicit SstFileReader(std::string file_name,
|
||||
bool verify_checksum = false,
|
||||
bool output_hex = false);
|
||||
Status ReadSequential(bool print_kv, uint64_t read_num = -1);
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
uint64_t read_num_;
|
||||
bool verify_checksum_;
|
||||
bool output_hex_;
|
||||
EnvOptions soptions_;
|
||||
};
|
||||
|
||||
SstFileReader::SstFileReader(std::string file_path,
|
||||
@@ -48,13 +49,14 @@ Status SstFileReader::ReadSequential(bool print_kv, uint64_t read_num)
|
||||
unique_ptr<Table> table;
|
||||
Options table_options;
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
Status s = table_options.env->NewRandomAccessFile(file_name_, &file);
|
||||
Status s = table_options.env->NewRandomAccessFile(file_name_, &file,
|
||||
soptions_);
|
||||
if(!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
uint64_t file_size;
|
||||
table_options.env->GetFileSize(file_name_, &file_size);
|
||||
s = Table::Open(table_options, std::move(file), file_size, &table);
|
||||
s = Table::Open(table_options, soptions_, std::move(file), file_size, &table);
|
||||
if(!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -93,7 +95,7 @@ static void print_help() {
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
const char* dir_or_file = NULL;
|
||||
const char* dir_or_file = nullptr;
|
||||
uint64_t read_num = -1;
|
||||
std::string command;
|
||||
|
||||
@@ -119,7 +121,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if(dir_or_file == NULL) {
|
||||
if(dir_or_file == nullptr) {
|
||||
print_help();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "util/auto_roll_logger.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -35,17 +36,26 @@ void AutoRollLogger::RollLogFile() {
|
||||
void AutoRollLogger::Logv(const char* format, va_list ap) {
|
||||
assert(GetStatus().ok());
|
||||
|
||||
if (kLogFileTimeToRoll > 0 && LogExpired()) {
|
||||
RollLogFile();
|
||||
ResetLogger();
|
||||
std::shared_ptr<Logger> logger;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
if ((kLogFileTimeToRoll > 0 && LogExpired()) ||
|
||||
(kMaxLogFileSize > 0 && logger_->GetLogFileSize() >= kMaxLogFileSize)) {
|
||||
RollLogFile();
|
||||
ResetLogger();
|
||||
}
|
||||
|
||||
// pin down the current logger_ instance before releasing the mutex.
|
||||
logger = logger_;
|
||||
}
|
||||
|
||||
logger_->Logv(format, ap);
|
||||
|
||||
if (kMaxLogFileSize > 0 && logger_->GetLogFileSize() > kMaxLogFileSize) {
|
||||
RollLogFile();
|
||||
ResetLogger();
|
||||
}
|
||||
// Another thread could have put a new Logger instance into logger_ by now.
|
||||
// However, since logger is still hanging on to the previous instance
|
||||
// (reference count is not zero), we don't have to worry about it being
|
||||
// deleted while we are accessing it.
|
||||
// Note that logv itself is not mutex protected to allow maximum concurrency,
|
||||
// as thread safety should have been handled by the underlying logger.
|
||||
logger->Logv(format, ap);
|
||||
}
|
||||
|
||||
bool AutoRollLogger::LogExpired() {
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#ifndef STORAGE_LEVELDB_UTIL_AUTO_ROLL_LOGGER_H
|
||||
#define STORAGE_LEVELDB_UTIL_AUTO_ROLL_LOGGER_H
|
||||
|
||||
#include "util/posix_logger.h"
|
||||
#include "db/filename.h"
|
||||
#include "port/port.h"
|
||||
#include "util/posix_logger.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -29,7 +30,8 @@ class AutoRollLogger : public Logger {
|
||||
cached_now(static_cast<uint64_t>(env_->NowMicros() * 1e-6)),
|
||||
ctime_(cached_now),
|
||||
cached_now_access_count(0),
|
||||
call_NowMicros_every_N_records_(100) {
|
||||
call_NowMicros_every_N_records_(100),
|
||||
mutex_() {
|
||||
env->GetAbsolutePath(dbname, &db_absolute_path_);
|
||||
log_fname_ = InfoLogFileName(dbname_, db_absolute_path_, db_log_dir_);
|
||||
RollLogFile();
|
||||
@@ -75,6 +77,7 @@ class AutoRollLogger : public Logger {
|
||||
uint64_t ctime_;
|
||||
uint64_t cached_now_access_count;
|
||||
uint64_t call_NowMicros_every_N_records_;
|
||||
port::Mutex mutex_;
|
||||
};
|
||||
|
||||
// Facade to craete logger automatically
|
||||
|
||||
@@ -78,7 +78,11 @@ void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
|
||||
// Now the log file will be rolled
|
||||
LogMessage(logger, log_message.c_str());
|
||||
ASSERT_TRUE(0 == logger->GetLogFileSize());
|
||||
// Since rotation is checked before actual logging, we need to
|
||||
// trigger the rotation by logging another message.
|
||||
LogMessage(logger, log_message.c_str());
|
||||
|
||||
ASSERT_TRUE(message_size == logger->GetLogFileSize());
|
||||
}
|
||||
|
||||
uint64_t AutoRollLoggerTest::RollLogFileByTimeTest(
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2009 Facebook
|
||||
* @author Tudor Bosman (tudorb@facebook.com)
|
||||
*/
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_BIT_SET_H_
|
||||
#define STORAGE_LEVELDB_UTIL_BIT_SET_H_
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class BitSet {
|
||||
public:
|
||||
/**
|
||||
* Create a bit set of numBits, with the bits set to either true or false.
|
||||
*/
|
||||
explicit BitSet(size_t numBits, bool initial=false)
|
||||
: numBits_(numBits),
|
||||
data_(numWords(), initial ? ~0UL : 0UL) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bit b to 1.
|
||||
*/
|
||||
void set(size_t b) {
|
||||
assert(b >= 0 && b < numBits_);
|
||||
data_[word(b)] |= wordOffsetMask(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bit b to 0;
|
||||
*/
|
||||
void reset(size_t b) {
|
||||
assert(b >= 0 && b < numBits_);
|
||||
data_[word(b)] &= ~wordOffsetMask(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bit.
|
||||
*/
|
||||
bool test(int b) const {
|
||||
return data_[word(b)] & wordOffsetMask(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the BitSet, in bits.
|
||||
*/
|
||||
size_t size() const {
|
||||
return numBits_;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
inline size_t numWords() const {
|
||||
if (numBits_ == 0) return 0;
|
||||
return 1 + (numBits_-1) / (8*sizeof(unsigned long));
|
||||
}
|
||||
inline static size_t word(int b) {
|
||||
return b / (8*sizeof(unsigned long));
|
||||
}
|
||||
inline static int wordOffset(int b) {
|
||||
return b % (8*sizeof(unsigned long));
|
||||
}
|
||||
inline static unsigned long wordOffsetMask(int b) {
|
||||
return 1UL << wordOffset(b);
|
||||
}
|
||||
|
||||
size_t numBits_;
|
||||
std::vector<unsigned long> data_;
|
||||
};
|
||||
|
||||
} // namespace facebook
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_BIT_SET_H_
|
||||
+5
-3
@@ -189,7 +189,7 @@ void LRUCache::Unref(LRUHandle* e) {
|
||||
assert(e->refs > 0);
|
||||
e->refs--;
|
||||
if (e->refs <= 0) {
|
||||
usage_ -= e->charge;
|
||||
|
||||
(*e->deleter)(e->key(), e->value);
|
||||
free(e);
|
||||
}
|
||||
@@ -198,6 +198,7 @@ void LRUCache::Unref(LRUHandle* e) {
|
||||
void LRUCache::LRU_Remove(LRUHandle* e) {
|
||||
e->next->prev = e->prev;
|
||||
e->prev->next = e->next;
|
||||
usage_ -= e->charge;
|
||||
}
|
||||
|
||||
void LRUCache::LRU_Append(LRUHandle* e) {
|
||||
@@ -206,6 +207,7 @@ void LRUCache::LRU_Append(LRUHandle* e) {
|
||||
e->prev = lru_.prev;
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
usage_ += e->charge;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) {
|
||||
@@ -239,7 +241,6 @@ Cache::Handle* LRUCache::Insert(
|
||||
e->refs = 2; // One from LRUCache, one for the returned handle
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
LRU_Append(e);
|
||||
usage_ += charge;
|
||||
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
if (old != nullptr) {
|
||||
@@ -281,7 +282,8 @@ class ShardedLRUCache : public Cache {
|
||||
}
|
||||
|
||||
uint32_t Shard(uint32_t hash) {
|
||||
return hash >> (32 - numShardBits);
|
||||
// Note, hash >> 32 yields hash in gcc, not the zero we expect!
|
||||
return (numShardBits > 0) ? (hash >> (32 - numShardBits)) : 0;
|
||||
}
|
||||
|
||||
void init(size_t capacity, int numbits) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "leveldb/cache.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "util/coding.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
@@ -178,6 +180,51 @@ TEST(CacheTest, NewId) {
|
||||
ASSERT_NE(a, b);
|
||||
}
|
||||
|
||||
|
||||
class Value {
|
||||
private:
|
||||
int v_;
|
||||
public:
|
||||
Value(int v) : v_(v) { }
|
||||
|
||||
~Value() { std::cout << v_ << " is destructed\n"; }
|
||||
};
|
||||
|
||||
void deleter(const Slice& key, void* value) {
|
||||
delete (Value *)value;
|
||||
}
|
||||
|
||||
|
||||
TEST(CacheTest, BadEviction) {
|
||||
int n = 10;
|
||||
|
||||
// a LRUCache with n entries and one shard only
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(n, 0);
|
||||
|
||||
std::vector<Cache::Handle*> handles(n+1);
|
||||
|
||||
// Insert n+1 entries, but not releasing.
|
||||
for (int i = 0; i < n+1; i++) {
|
||||
std::string key = std::to_string(i+1);
|
||||
handles[i] = cache->Insert(key, new Value(i+1), 1, &deleter);
|
||||
}
|
||||
|
||||
// Guess what's in the cache now?
|
||||
for (int i = 0; i < n+1; i++) {
|
||||
std::string key = std::to_string(i+1);
|
||||
auto h = cache->Lookup(key);
|
||||
std::cout << key << (h?" found\n":" not found\n");
|
||||
// Only the first entry should be missing
|
||||
ASSERT_TRUE(h || i == 0);
|
||||
if (h) cache->Release(h);
|
||||
}
|
||||
|
||||
for (int i = 0; i < n+1; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
std::cout << "Poor entries\n";
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+27
-2
@@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/options.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -46,7 +47,8 @@ static Status DoWriteStringToFile(Env* env, const Slice& data,
|
||||
const std::string& fname,
|
||||
bool should_sync) {
|
||||
unique_ptr<WritableFile> file;
|
||||
Status s = env->NewWritableFile(fname, &file);
|
||||
EnvOptions soptions;
|
||||
Status s = env->NewWritableFile(fname, &file, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -71,9 +73,10 @@ Status WriteStringToFileSync(Env* env, const Slice& data,
|
||||
}
|
||||
|
||||
Status ReadFileToString(Env* env, const std::string& fname, std::string* data) {
|
||||
EnvOptions soptions;
|
||||
data->clear();
|
||||
unique_ptr<SequentialFile> file;
|
||||
Status s = env->NewSequentialFile(fname, &file);
|
||||
Status s = env->NewSequentialFile(fname, &file, soptions);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -97,4 +100,26 @@ Status ReadFileToString(Env* env, const std::string& fname, std::string* data) {
|
||||
EnvWrapper::~EnvWrapper() {
|
||||
}
|
||||
|
||||
namespace { // anonymous namespace
|
||||
|
||||
void AssignEnvOptions(EnvOptions* env_options, const Options& options) {
|
||||
env_options->use_os_buffer = options.allow_os_buffer;
|
||||
env_options->use_mmap_reads = options.allow_mmap_reads;
|
||||
env_options->use_mmap_writes = options.allow_mmap_writes;
|
||||
env_options->set_fd_cloexec = options.is_fd_close_on_exec;
|
||||
env_options->bytes_per_sync = options.bytes_per_sync;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EnvOptions::EnvOptions(const Options& options) {
|
||||
AssignEnvOptions(this, options);
|
||||
}
|
||||
|
||||
EnvOptions::EnvOptions() {
|
||||
Options options;
|
||||
AssignEnvOptions(this, options);
|
||||
}
|
||||
|
||||
|
||||
} // namespace leveldb
|
||||
+2
-1
@@ -516,7 +516,8 @@ Status HdfsEnv::NewLogger(const std::string& fname,
|
||||
#include "hdfs/env_hdfs.h"
|
||||
namespace leveldb {
|
||||
Status HdfsEnv::NewSequentialFile(const std::string& fname,
|
||||
unique_ptr<SequentialFile>* result) {
|
||||
unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) {
|
||||
return Status::NotSupported("Not compiled with hdfs support");
|
||||
}
|
||||
}
|
||||
|
||||
+235
-61
@@ -22,6 +22,7 @@
|
||||
#include <unistd.h>
|
||||
#if defined(OS_LINUX)
|
||||
#include <linux/fs.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#if defined(LEVELDB_PLATFORM_ANDROID)
|
||||
#include <sys/stat.h>
|
||||
@@ -32,6 +33,8 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/posix_logger.h"
|
||||
#include "util/random.h"
|
||||
#include <signal.h>
|
||||
|
||||
#if !defined(TMPFS_MAGIC)
|
||||
#define TMPFS_MAGIC 0x01021994
|
||||
@@ -43,13 +46,13 @@
|
||||
#define EXT4_SUPER_MAGIC 0xEF53
|
||||
#endif
|
||||
|
||||
bool useOsBuffer = 1; // cache data in OS buffers
|
||||
bool useFsReadAhead = 1; // allow filesystem to do readaheads
|
||||
bool useMmapRead = 0; // do not use mmaps for reading files
|
||||
bool useMmapWrite = 1; // use mmaps for appending to files
|
||||
// This is only set from db_stress.cc and for testing only.
|
||||
// If non-zero, kill at various points in source code with probability 1/this
|
||||
int leveldb_kill_odds = 0;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// list of pathnames that are locked
|
||||
@@ -60,14 +63,52 @@ static Status IOError(const std::string& context, int err_number) {
|
||||
return Status::IOError(context, strerror(err_number));
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
// empty in release build
|
||||
#define TEST_KILL_RANDOM(leveldb_kill_odds)
|
||||
#else
|
||||
|
||||
// Kill the process with probablity 1/odds for testing.
|
||||
static void TestKillRandom(int odds, const std::string& srcfile,
|
||||
int srcline) {
|
||||
time_t curtime = time(nullptr);
|
||||
Random r((uint32_t)curtime);
|
||||
|
||||
assert(odds > 0);
|
||||
bool crash = r.OneIn(odds);
|
||||
if (crash) {
|
||||
fprintf(stdout, "Crashing at %s:%d\n", srcfile.c_str(), srcline);
|
||||
fflush(stdout);
|
||||
kill(getpid(), SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid crashing always at some frequently executed codepaths (during
|
||||
// kill random test), use this factor to reduce odds
|
||||
#define REDUCE_ODDS 2
|
||||
#define REDUCE_ODDS2 4
|
||||
|
||||
#define TEST_KILL_RANDOM(leveldb_kill_odds) { \
|
||||
if (leveldb_kill_odds > 0) { \
|
||||
TestKillRandom(leveldb_kill_odds, __FILE__, __LINE__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
class PosixSequentialFile: public SequentialFile {
|
||||
private:
|
||||
std::string filename_;
|
||||
FILE* file_;
|
||||
int fd_;
|
||||
bool use_os_buffer_;
|
||||
|
||||
public:
|
||||
PosixSequentialFile(const std::string& fname, FILE* f)
|
||||
: filename_(fname), file_(f) { }
|
||||
PosixSequentialFile(const std::string& fname, FILE* f,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname), file_(f), fd_(fileno(f)),
|
||||
use_os_buffer_(options.use_os_buffer) {
|
||||
}
|
||||
virtual ~PosixSequentialFile() { fclose(file_); }
|
||||
|
||||
virtual Status Read(size_t n, Slice* result, char* scratch) {
|
||||
@@ -82,6 +123,11 @@ class PosixSequentialFile: public SequentialFile {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
if (!use_os_buffer_) {
|
||||
// we need to fadvise away the entire range of pages because
|
||||
// we do not want readahead pages to be cached.
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_DONTNEED); // free OS pages
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -98,14 +144,13 @@ class PosixRandomAccessFile: public RandomAccessFile {
|
||||
private:
|
||||
std::string filename_;
|
||||
int fd_;
|
||||
bool use_os_buffer_;
|
||||
|
||||
public:
|
||||
PosixRandomAccessFile(const std::string& fname, int fd)
|
||||
: filename_(fname), fd_(fd) {
|
||||
if (!useFsReadAhead) {
|
||||
// disable read-aheads
|
||||
posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM);
|
||||
}
|
||||
PosixRandomAccessFile(const std::string& fname, int fd,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname), fd_(fd), use_os_buffer_(options.use_os_buffer) {
|
||||
assert(!options.use_mmap_reads);
|
||||
}
|
||||
virtual ~PosixRandomAccessFile() { close(fd_); }
|
||||
|
||||
@@ -118,7 +163,7 @@ class PosixRandomAccessFile: public RandomAccessFile {
|
||||
// An error: return a non-ok status
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
if (!useOsBuffer) {
|
||||
if (!use_os_buffer_) {
|
||||
// we need to fadvise away the entire range of pages because
|
||||
// we do not want readahead pages to be cached.
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_DONTNEED); // free OS pages
|
||||
@@ -154,6 +199,30 @@ class PosixRandomAccessFile: public RandomAccessFile {
|
||||
return static_cast<size_t>(rid-id);
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual void Hint(AccessPattern pattern) {
|
||||
switch(pattern) {
|
||||
case NORMAL:
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_NORMAL);
|
||||
break;
|
||||
case RANDOM:
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_RANDOM);
|
||||
break;
|
||||
case SEQUENTIAL:
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL);
|
||||
break;
|
||||
case WILLNEED:
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_WILLNEED);
|
||||
break;
|
||||
case DONTNEED:
|
||||
posix_fadvise(fd_, 0, 0, POSIX_FADV_DONTNEED);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// mmap() based random-access
|
||||
@@ -165,8 +234,12 @@ class PosixMmapReadableFile: public RandomAccessFile {
|
||||
|
||||
public:
|
||||
// base[0,length-1] contains the mmapped contents of the file.
|
||||
PosixMmapReadableFile(const std::string& fname, void* base, size_t length)
|
||||
: filename_(fname), mmapped_region_(base), length_(length) { }
|
||||
PosixMmapReadableFile(const std::string& fname, void* base, size_t length,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname), mmapped_region_(base), length_(length) {
|
||||
assert(options.use_mmap_reads);
|
||||
assert(options.use_os_buffer);
|
||||
}
|
||||
virtual ~PosixMmapReadableFile() { munmap(mmapped_region_, length_); }
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
@@ -214,6 +287,7 @@ class PosixMmapFile : public WritableFile {
|
||||
|
||||
bool UnmapCurrentRegion() {
|
||||
bool result = true;
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (base_ != nullptr) {
|
||||
if (last_sync_ < limit_) {
|
||||
// Defer syncing this data until next Sync() call, if any
|
||||
@@ -239,18 +313,22 @@ class PosixMmapFile : public WritableFile {
|
||||
Status MapNewRegion() {
|
||||
assert(base_ == nullptr);
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
int alloc_status = posix_fallocate(fd_, file_offset_, map_size_);
|
||||
if (alloc_status != 0) {
|
||||
return Status::IOError("Error allocating space to file : " + filename_ +
|
||||
"Error : " + strerror(alloc_status));
|
||||
}
|
||||
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
void* ptr = mmap(nullptr, map_size_, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
fd_, file_offset_);
|
||||
if (ptr == MAP_FAILED) {
|
||||
return Status::IOError("MMap failed on " + filename_);
|
||||
}
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
|
||||
base_ = reinterpret_cast<char*>(ptr);
|
||||
limit_ = base_ + map_size_;
|
||||
dst_ = base_;
|
||||
@@ -259,7 +337,8 @@ class PosixMmapFile : public WritableFile {
|
||||
}
|
||||
|
||||
public:
|
||||
PosixMmapFile(const std::string& fname, int fd, size_t page_size)
|
||||
PosixMmapFile(const std::string& fname, int fd, size_t page_size,
|
||||
const EnvOptions& options)
|
||||
: filename_(fname),
|
||||
fd_(fd),
|
||||
page_size_(page_size),
|
||||
@@ -271,6 +350,7 @@ class PosixMmapFile : public WritableFile {
|
||||
file_offset_(0),
|
||||
pending_sync_(false) {
|
||||
assert((page_size & (page_size - 1)) == 0);
|
||||
assert(options.use_mmap_writes);
|
||||
}
|
||||
|
||||
|
||||
@@ -283,6 +363,7 @@ class PosixMmapFile : public WritableFile {
|
||||
virtual Status Append(const Slice& data) {
|
||||
const char* src = data.data();
|
||||
size_t left = data.size();
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds * REDUCE_ODDS);
|
||||
PrepareWrite(GetFileSize(), left);
|
||||
while (left > 0) {
|
||||
assert(base_ <= dst_);
|
||||
@@ -294,6 +375,7 @@ class PosixMmapFile : public WritableFile {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,12 +385,16 @@ class PosixMmapFile : public WritableFile {
|
||||
src += n;
|
||||
left -= n;
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status Close() {
|
||||
Status s;
|
||||
size_t unused = limit_ - dst_;
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
|
||||
if (!UnmapCurrentRegion()) {
|
||||
s = IOError(filename_, errno);
|
||||
} else if (unused > 0) {
|
||||
@@ -318,6 +404,8 @@ class PosixMmapFile : public WritableFile {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
|
||||
if (close(fd_) < 0) {
|
||||
if (s.ok()) {
|
||||
s = IOError(filename_, errno);
|
||||
@@ -331,6 +419,7 @@ class PosixMmapFile : public WritableFile {
|
||||
}
|
||||
|
||||
virtual Status Flush() {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -339,10 +428,12 @@ class PosixMmapFile : public WritableFile {
|
||||
|
||||
if (pending_sync_) {
|
||||
// Some unmapped data was not synced
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
pending_sync_ = false;
|
||||
if (fdatasync(fd_) < 0) {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds * REDUCE_ODDS);
|
||||
}
|
||||
|
||||
if (dst_ > last_sync_) {
|
||||
@@ -351,9 +442,11 @@ class PosixMmapFile : public WritableFile {
|
||||
size_t p1 = TruncateToPageBoundary(last_sync_ - base_);
|
||||
size_t p2 = TruncateToPageBoundary(dst_ - base_ - 1);
|
||||
last_sync_ = dst_;
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (msync(base_ + p1, p2 - p1 + page_size_, MS_SYNC) < 0) {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -365,10 +458,12 @@ class PosixMmapFile : public WritableFile {
|
||||
virtual Status Fsync() {
|
||||
if (pending_sync_) {
|
||||
// Some unmapped data was not synced
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
pending_sync_ = false;
|
||||
if (fsync(fd_) < 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
}
|
||||
// This invocation to Sync will not issue the call to
|
||||
// fdatasync because pending_sync_ has already been cleared.
|
||||
@@ -387,6 +482,7 @@ class PosixMmapFile : public WritableFile {
|
||||
|
||||
#ifdef OS_LINUX
|
||||
virtual Status Allocate(off_t offset, off_t len) {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
@@ -403,13 +499,16 @@ class PosixWritableFile : public WritableFile {
|
||||
int fd_;
|
||||
size_t cursize_; // current size of cached data in buf_
|
||||
size_t capacity_; // max size of buf_
|
||||
char* buf_; // a buffer to cache writes
|
||||
unique_ptr<char[]> buf_; // a buffer to cache writes
|
||||
uint64_t filesize_;
|
||||
bool pending_sync_;
|
||||
bool pending_fsync_;
|
||||
uint64_t last_sync_size_;
|
||||
uint64_t bytes_per_sync_;
|
||||
|
||||
public:
|
||||
PosixWritableFile(const std::string& fname, int fd, size_t capacity) :
|
||||
PosixWritableFile(const std::string& fname, int fd, size_t capacity,
|
||||
const EnvOptions& options) :
|
||||
filename_(fname),
|
||||
fd_(fd),
|
||||
cursize_(0),
|
||||
@@ -417,15 +516,16 @@ class PosixWritableFile : public WritableFile {
|
||||
buf_(new char[capacity]),
|
||||
filesize_(0),
|
||||
pending_sync_(false),
|
||||
pending_fsync_(false) {
|
||||
pending_fsync_(false),
|
||||
last_sync_size_(0),
|
||||
bytes_per_sync_(options.bytes_per_sync) {
|
||||
assert(!options.use_mmap_writes);
|
||||
}
|
||||
|
||||
~PosixWritableFile() {
|
||||
if (fd_ >= 0) {
|
||||
PosixWritableFile::Close();
|
||||
}
|
||||
delete buf_;
|
||||
buf_ = 0;
|
||||
}
|
||||
|
||||
virtual Status Append(const Slice& data) {
|
||||
@@ -435,6 +535,8 @@ class PosixWritableFile : public WritableFile {
|
||||
pending_sync_ = true;
|
||||
pending_fsync_ = true;
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds * REDUCE_ODDS2);
|
||||
|
||||
PrepareWrite(GetFileSize(), left);
|
||||
// if there is no space in the cache, then flush
|
||||
if (cursize_ + left > capacity_) {
|
||||
@@ -444,9 +546,8 @@ class PosixWritableFile : public WritableFile {
|
||||
}
|
||||
// Increase the buffer size, but capped at 1MB
|
||||
if (capacity_ < (1<<20)) {
|
||||
delete buf_;
|
||||
capacity_ *= 2;
|
||||
buf_ = new char[capacity_];
|
||||
buf_.reset(new char[capacity_]);
|
||||
}
|
||||
assert(cursize_ == 0);
|
||||
}
|
||||
@@ -454,7 +555,7 @@ class PosixWritableFile : public WritableFile {
|
||||
// if the write fits into the cache, then write to cache
|
||||
// otherwise do a write() syscall to write to OS buffers.
|
||||
if (cursize_ + left <= capacity_) {
|
||||
memcpy(buf_+cursize_, src, left);
|
||||
memcpy(buf_.get()+cursize_, src, left);
|
||||
cursize_ += left;
|
||||
} else {
|
||||
while (left != 0) {
|
||||
@@ -462,6 +563,8 @@ class PosixWritableFile : public WritableFile {
|
||||
if (done < 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
|
||||
left -= done;
|
||||
src += done;
|
||||
}
|
||||
@@ -475,6 +578,9 @@ class PosixWritableFile : public WritableFile {
|
||||
s = Flush(); // flush cache to OS
|
||||
if (!s.ok()) {
|
||||
}
|
||||
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
|
||||
if (close(fd_) < 0) {
|
||||
if (s.ok()) {
|
||||
s = IOError(filename_, errno);
|
||||
@@ -486,32 +592,49 @@ class PosixWritableFile : public WritableFile {
|
||||
|
||||
// write out the cached data to the OS cache
|
||||
virtual Status Flush() {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds * REDUCE_ODDS2);
|
||||
size_t left = cursize_;
|
||||
char* src = buf_;
|
||||
char* src = buf_.get();
|
||||
while (left != 0) {
|
||||
ssize_t done = write(fd_, src, left);
|
||||
if (done < 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds * REDUCE_ODDS2);
|
||||
left -= done;
|
||||
src += done;
|
||||
}
|
||||
cursize_ = 0;
|
||||
|
||||
// sync OS cache to disk for every bytes_per_sync_
|
||||
// TODO: give log file and sst file different options (log
|
||||
// files could be potentially cached in OS for their whole
|
||||
// life time, thus we might not want to flush at all).
|
||||
if (bytes_per_sync_ &&
|
||||
filesize_ - last_sync_size_ >= bytes_per_sync_) {
|
||||
RangeSync(last_sync_size_, filesize_ - last_sync_size_);
|
||||
last_sync_size_ = filesize_;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status Sync() {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (pending_sync_ && fdatasync(fd_) < 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
pending_sync_ = false;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status Fsync() {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (pending_fsync_ && fsync(fd_) < 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
pending_fsync_ = false;
|
||||
pending_sync_ = false;
|
||||
return Status::OK();
|
||||
@@ -523,12 +646,21 @@ class PosixWritableFile : public WritableFile {
|
||||
|
||||
#ifdef OS_LINUX
|
||||
virtual Status Allocate(off_t offset, off_t len) {
|
||||
TEST_KILL_RANDOM(leveldb_kill_odds);
|
||||
if (!fallocate(fd_, FALLOC_FL_KEEP_SIZE, offset, len)) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status RangeSync(off64_t offset, off64_t nbytes) {
|
||||
if (sync_file_range(fd_, offset, nbytes, SYNC_FILE_RANGE_WRITE) == 0) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -579,32 +711,43 @@ class PosixFileLock : public FileLock {
|
||||
class PosixEnv : public Env {
|
||||
public:
|
||||
PosixEnv();
|
||||
virtual ~PosixEnv() {
|
||||
fprintf(stderr, "Destroying Env::Default()\n");
|
||||
exit(1);
|
||||
|
||||
virtual ~PosixEnv(){
|
||||
WaitForBGThreads();
|
||||
}
|
||||
|
||||
void SetFD_CLOEXEC(int fd, const EnvOptions* options) {
|
||||
if ((options == nullptr || options->set_fd_cloexec) && fd > 0) {
|
||||
fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
unique_ptr<SequentialFile>* result) {
|
||||
unique_ptr<SequentialFile>* result,
|
||||
const EnvOptions& options) {
|
||||
result->reset();
|
||||
FILE* f = fopen(fname.c_str(), "r");
|
||||
if (f == nullptr) {
|
||||
*result = nullptr;
|
||||
return IOError(fname, errno);
|
||||
} else {
|
||||
result->reset(new PosixSequentialFile(fname, f));
|
||||
int fd = fileno(f);
|
||||
SetFD_CLOEXEC(fd, &options);
|
||||
result->reset(new PosixSequentialFile(fname, f, options));
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status NewRandomAccessFile(const std::string& fname,
|
||||
unique_ptr<RandomAccessFile>* result) {
|
||||
unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) {
|
||||
result->reset();
|
||||
Status s;
|
||||
int fd = open(fname.c_str(), O_RDONLY);
|
||||
SetFD_CLOEXEC(fd, &options);
|
||||
if (fd < 0) {
|
||||
s = IOError(fname, errno);
|
||||
} else if (useMmapRead && sizeof(void*) >= 8) {
|
||||
} else if (options.use_mmap_reads && sizeof(void*) >= 8) {
|
||||
// Use of mmap for random reads has been removed because it
|
||||
// kills performance when storage is fast.
|
||||
// Use mmap when virtual address-space is plentiful.
|
||||
@@ -613,39 +756,42 @@ class PosixEnv : public Env {
|
||||
if (s.ok()) {
|
||||
void* base = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (base != MAP_FAILED) {
|
||||
result->reset(new PosixMmapReadableFile(fname, base, size));
|
||||
result->reset(new PosixMmapReadableFile(fname, base, size, options));
|
||||
} else {
|
||||
s = IOError(fname, errno);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
} else {
|
||||
result->reset(new PosixRandomAccessFile(fname, fd));
|
||||
result->reset(new PosixRandomAccessFile(fname, fd, options));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result) {
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) {
|
||||
result->reset();
|
||||
Status s;
|
||||
const int fd = open(fname.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||
if (fd < 0) {
|
||||
s = IOError(fname, errno);
|
||||
} else {
|
||||
if (!checkedDiskForMmap_) {
|
||||
// this will be executed once in the program's lifetime.
|
||||
if (useMmapWrite) {
|
||||
SetFD_CLOEXEC(fd, &options);
|
||||
if (options.use_mmap_writes) {
|
||||
if (!checkedDiskForMmap_) {
|
||||
// this will be executed once in the program's lifetime.
|
||||
// do not use mmapWrite on non ext-3/xfs/tmpfs systems.
|
||||
useMmapWrite = SupportsFastAllocate(fname);
|
||||
if (!SupportsFastAllocate(fname)) {
|
||||
forceMmapOff = true;
|
||||
}
|
||||
checkedDiskForMmap_ = true;
|
||||
}
|
||||
checkedDiskForMmap_ = true;
|
||||
}
|
||||
|
||||
if (useMmapWrite) {
|
||||
result->reset(new PosixMmapFile(fname, fd, page_size_));
|
||||
if (options.use_mmap_writes && !forceMmapOff) {
|
||||
result->reset(new PosixMmapFile(fname, fd, page_size_, options));
|
||||
} else {
|
||||
result->reset(new PosixWritableFile(fname, fd, 65536));
|
||||
result->reset(new PosixWritableFile(fname, fd, 65536, options));
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -747,6 +893,7 @@ class PosixEnv : public Env {
|
||||
result = IOError("lock " + fname, errno);
|
||||
close(fd);
|
||||
} else {
|
||||
SetFD_CLOEXEC(fd, nullptr);
|
||||
PosixFileLock* my_lock = new PosixFileLock;
|
||||
my_lock->fd_ = fd;
|
||||
my_lock->filename = fname;
|
||||
@@ -768,6 +915,8 @@ class PosixEnv : public Env {
|
||||
|
||||
virtual void Schedule(void (*function)(void*), void* arg);
|
||||
|
||||
virtual void WaitForBGThreads();
|
||||
|
||||
virtual void StartThread(void (*function)(void* arg), void* arg);
|
||||
|
||||
virtual Status GetTestDirectory(std::string* result) {
|
||||
@@ -798,6 +947,8 @@ class PosixEnv : public Env {
|
||||
result->reset();
|
||||
return IOError(fname, errno);
|
||||
} else {
|
||||
int fd = fileno(f);
|
||||
SetFD_CLOEXEC(fd, nullptr);
|
||||
result->reset(new PosixLogger(f, &PosixEnv::gettid));
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -852,10 +1003,12 @@ class PosixEnv : public Env {
|
||||
|
||||
// Allow increasing the number of worker threads.
|
||||
virtual void SetBackgroundThreads(int num) {
|
||||
PthreadCall("lock", pthread_mutex_lock(&mu_));
|
||||
if (num > num_threads_) {
|
||||
num_threads_ = num;
|
||||
bgthread_.resize(num_threads_);
|
||||
}
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
}
|
||||
|
||||
virtual std::string TimeToString(uint64_t secondsSince1970) {
|
||||
@@ -879,7 +1032,8 @@ class PosixEnv : public Env {
|
||||
}
|
||||
|
||||
private:
|
||||
bool checkedDiskForMmap_ = false;
|
||||
bool checkedDiskForMmap_;
|
||||
bool forceMmapOff; // do we override Env options?
|
||||
|
||||
void PthreadCall(const char* label, int result) {
|
||||
if (result != 0) {
|
||||
@@ -933,21 +1087,42 @@ class PosixEnv : public Env {
|
||||
struct BGItem { void* arg; void (*function)(void*); };
|
||||
typedef std::deque<BGItem> BGQueue;
|
||||
int queue_size_; // number of items in BGQueue
|
||||
bool exit_all_threads_;
|
||||
BGQueue queue_;
|
||||
std::vector<pthread_t> threads_to_join_;
|
||||
};
|
||||
|
||||
PosixEnv::PosixEnv() : page_size_(getpagesize()),
|
||||
PosixEnv::PosixEnv() : checkedDiskForMmap_(false),
|
||||
forceMmapOff(false),
|
||||
page_size_(getpagesize()),
|
||||
started_bgthread_(0),
|
||||
num_threads_(1),
|
||||
queue_size_(0) {
|
||||
queue_size_(0),
|
||||
exit_all_threads_(false) {
|
||||
PthreadCall("mutex_init", pthread_mutex_init(&mu_, nullptr));
|
||||
PthreadCall("cvar_init", pthread_cond_init(&bgsignal_, nullptr));
|
||||
bgthread_.resize(num_threads_);
|
||||
}
|
||||
|
||||
// Signal and Join all background threads started by calls to Schedule
|
||||
void PosixEnv::WaitForBGThreads() {
|
||||
PthreadCall("lock", pthread_mutex_lock(&mu_));
|
||||
assert(! exit_all_threads_);
|
||||
exit_all_threads_ = true;
|
||||
PthreadCall("signalall", pthread_cond_broadcast(&bgsignal_));
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
for (unsigned int i = 0; i < threads_to_join_.size(); i++) {
|
||||
pthread_join(threads_to_join_[i], nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PosixEnv::Schedule(void (*function)(void*), void* arg) {
|
||||
PthreadCall("lock", pthread_mutex_lock(&mu_));
|
||||
|
||||
if (exit_all_threads_) {
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
return;
|
||||
}
|
||||
// Start background thread if necessary
|
||||
for (; started_bgthread_ < num_threads_; started_bgthread_++) {
|
||||
PthreadCall(
|
||||
@@ -956,17 +1131,17 @@ void PosixEnv::Schedule(void (*function)(void*), void* arg) {
|
||||
nullptr,
|
||||
&PosixEnv::BGThreadWrapper,
|
||||
this));
|
||||
threads_to_join_.push_back(bgthread_[started_bgthread_]);
|
||||
fprintf(stdout, "Created bg thread 0x%lx\n", bgthread_[started_bgthread_]);
|
||||
}
|
||||
|
||||
// always wake up at least one waiting thread.
|
||||
PthreadCall("signal", pthread_cond_signal(&bgsignal_));
|
||||
|
||||
// Add to priority queue
|
||||
queue_.push_back(BGItem());
|
||||
queue_.back().function = function;
|
||||
queue_.back().arg = arg;
|
||||
queue_size_++;
|
||||
|
||||
// always wake up at least one waiting thread.
|
||||
PthreadCall("signal", pthread_cond_signal(&bgsignal_));
|
||||
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
}
|
||||
@@ -975,14 +1150,16 @@ void PosixEnv::BGThread() {
|
||||
while (true) {
|
||||
// Wait until there is an item that is ready to run
|
||||
PthreadCall("lock", pthread_mutex_lock(&mu_));
|
||||
while (queue_.empty()) {
|
||||
while (queue_.empty() && !exit_all_threads_) {
|
||||
PthreadCall("wait", pthread_cond_wait(&bgsignal_, &mu_));
|
||||
}
|
||||
|
||||
if (exit_all_threads_) { // mechanism to let BG threads exit safely
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
break;
|
||||
}
|
||||
void (*function)(void*) = queue_.front().function;
|
||||
void* arg = queue_.front().arg;
|
||||
queue_.pop_front();
|
||||
queue_size_--;
|
||||
|
||||
PthreadCall("unlock", pthread_mutex_unlock(&mu_));
|
||||
(*function)(arg);
|
||||
@@ -1009,17 +1186,14 @@ void PosixEnv::StartThread(void (*function)(void* arg), void* arg) {
|
||||
state->arg = arg;
|
||||
PthreadCall("start thread",
|
||||
pthread_create(&t, nullptr, &StartThreadWrapper, state));
|
||||
threads_to_join_.push_back(t);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
static Env* default_env;
|
||||
static void InitDefaultEnv() { default_env = new PosixEnv; }
|
||||
|
||||
Env* Env::Default() {
|
||||
pthread_once(&once, InitDefaultEnv);
|
||||
return default_env;
|
||||
static PosixEnv default_env;
|
||||
return &default_env;
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
+11
-8
@@ -119,21 +119,22 @@ char temp_id[MAX_ID_SIZE];
|
||||
|
||||
TEST(EnvPosixTest, RandomAccessUniqueID) {
|
||||
// Create file.
|
||||
const EnvOptions soptions;
|
||||
std::string fname = test::TmpDir() + "/" + "testfile";
|
||||
unique_ptr<WritableFile> wfile;
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &wfile));
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions));
|
||||
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
|
||||
// Get Unique ID
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE);
|
||||
ASSERT_TRUE(id_size > 0);
|
||||
std::string unique_id1(temp_id, id_size);
|
||||
ASSERT_TRUE(IsUniqueIDValid(unique_id1));
|
||||
|
||||
// Get Unique ID again
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE);
|
||||
ASSERT_TRUE(id_size > 0);
|
||||
std::string unique_id2(temp_id, id_size);
|
||||
@@ -141,7 +142,7 @@ TEST(EnvPosixTest, RandomAccessUniqueID) {
|
||||
|
||||
// Get Unique ID again after waiting some time.
|
||||
env_->SleepForMicroseconds(1000000);
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE);
|
||||
ASSERT_TRUE(id_size > 0);
|
||||
std::string unique_id3(temp_id, id_size);
|
||||
@@ -172,6 +173,7 @@ bool HasPrefix(const std::unordered_set<std::string>& ss) {
|
||||
|
||||
TEST(EnvPosixTest, RandomAccessUniqueIDConcurrent) {
|
||||
// Check whether a bunch of concurrently existing files have unique IDs.
|
||||
const EnvOptions soptions;
|
||||
|
||||
// Create the files
|
||||
std::vector<std::string> fnames;
|
||||
@@ -180,7 +182,7 @@ TEST(EnvPosixTest, RandomAccessUniqueIDConcurrent) {
|
||||
|
||||
// Create file.
|
||||
unique_ptr<WritableFile> wfile;
|
||||
ASSERT_OK(env_->NewWritableFile(fnames[i], &wfile));
|
||||
ASSERT_OK(env_->NewWritableFile(fnames[i], &wfile, soptions));
|
||||
}
|
||||
|
||||
// Collect and check whether the IDs are unique.
|
||||
@@ -188,7 +190,7 @@ TEST(EnvPosixTest, RandomAccessUniqueIDConcurrent) {
|
||||
for (const std::string fname: fnames) {
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
std::string unique_id;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE);
|
||||
ASSERT_TRUE(id_size > 0);
|
||||
unique_id = std::string(temp_id, id_size);
|
||||
@@ -207,6 +209,7 @@ TEST(EnvPosixTest, RandomAccessUniqueIDConcurrent) {
|
||||
}
|
||||
|
||||
TEST(EnvPosixTest, RandomAccessUniqueIDDeletes) {
|
||||
const EnvOptions soptions;
|
||||
std::string fname = test::TmpDir() + "/" + "testfile";
|
||||
|
||||
// Check that after file is deleted we don't get same ID again in a new file.
|
||||
@@ -215,14 +218,14 @@ TEST(EnvPosixTest, RandomAccessUniqueIDDeletes) {
|
||||
// Create file.
|
||||
{
|
||||
unique_ptr<WritableFile> wfile;
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &wfile));
|
||||
ASSERT_OK(env_->NewWritableFile(fname, &wfile, soptions));
|
||||
}
|
||||
|
||||
// Get Unique ID
|
||||
std::string unique_id;
|
||||
{
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file));
|
||||
ASSERT_OK(env_->NewRandomAccessFile(fname, &file, soptions));
|
||||
size_t id_size = file->GetUniqueId(temp_id, MAX_ID_SIZE);
|
||||
ASSERT_TRUE(id_size > 0);
|
||||
unique_id = std::string(temp_id, id_size);
|
||||
|
||||
+7
-9
@@ -85,17 +85,13 @@ void HistogramImpl::Add(uint64_t value) {
|
||||
sum_squares_ += (value * value);
|
||||
}
|
||||
|
||||
void HistogramImpl::Add(double value) {
|
||||
Add(static_cast<uint64_t>(value));
|
||||
}
|
||||
|
||||
void HistogramImpl::Merge(const HistogramImpl& other) {
|
||||
if (other.min_ < min_) min_ = other.min_;
|
||||
if (other.max_ > max_) max_ = other.max_;
|
||||
num_ += other.num_;
|
||||
sum_ += other.sum_;
|
||||
sum_squares_ += other.sum_squares_;
|
||||
for (int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
for (unsigned int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
buckets_[b] += other.buckets_[b];
|
||||
}
|
||||
}
|
||||
@@ -107,7 +103,7 @@ double HistogramImpl::Median() const {
|
||||
double HistogramImpl::Percentile(double p) const {
|
||||
double threshold = num_ * (p / 100.0);
|
||||
double sum = 0;
|
||||
for (int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
for (unsigned int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
sum += buckets_[b];
|
||||
if (sum >= threshold) {
|
||||
// Scale linearly within this bucket
|
||||
@@ -152,13 +148,15 @@ std::string HistogramImpl::ToString() const {
|
||||
(num_ == 0.0 ? 0.0 : min_), Median(), max_);
|
||||
r.append(buf);
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Percentiles: P50: %.2f P75: %.2f P99: %.2f P99.9: %.2f\n",
|
||||
Percentile(50), Percentile(75), Percentile(99), Percentile(99.9));
|
||||
"Percentiles: "
|
||||
"P50: %.2f P75: %.2f P99: %.2f P99.9: %.2f P99.99: %.2f\n",
|
||||
Percentile(50), Percentile(75), Percentile(99), Percentile(99.9),
|
||||
Percentile(99.99));
|
||||
r.append(buf);
|
||||
r.append("------------------------------------------------------\n");
|
||||
const double mult = 100.0 / num_;
|
||||
double sum = 0;
|
||||
for (int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
for (unsigned int b = 0; b < bucketMapper.BucketCount(); b++) {
|
||||
if (buckets_[b] <= 0.0) continue;
|
||||
sum += buckets_[b];
|
||||
snprintf(buf, sizeof(buf),
|
||||
|
||||
@@ -53,7 +53,6 @@ class HistogramImpl {
|
||||
virtual ~HistogramImpl() {}
|
||||
virtual void Clear();
|
||||
virtual void Add(uint64_t value);
|
||||
virtual void Add(double value);
|
||||
void Merge(const HistogramImpl& other);
|
||||
|
||||
virtual std::string ToString() const;
|
||||
|
||||
+386
-119
@@ -4,20 +4,31 @@
|
||||
|
||||
#include "util/ldb_cmd.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <dirent.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
using namespace std;
|
||||
|
||||
const string LDBCommand::ARG_DB = "db";
|
||||
const string LDBCommand::ARG_HEX = "hex";
|
||||
const string LDBCommand::ARG_KEY_HEX = "key_hex";
|
||||
const string LDBCommand::ARG_VALUE_HEX = "value_hex";
|
||||
const string LDBCommand::ARG_TTL = "ttl";
|
||||
const string LDBCommand::ARG_TTL_START = "start_time";
|
||||
const string LDBCommand::ARG_TTL_END = "end_time";
|
||||
const string LDBCommand::ARG_TIMESTAMP = "timestamp";
|
||||
const string LDBCommand::ARG_FROM = "from";
|
||||
const string LDBCommand::ARG_TO = "to";
|
||||
const string LDBCommand::ARG_MAX_KEYS = "max_keys";
|
||||
@@ -31,12 +42,16 @@ const string LDBCommand::ARG_CREATE_IF_MISSING = "create_if_missing";
|
||||
|
||||
const char* LDBCommand::DELIM = " ==> ";
|
||||
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(int argc, char** argv) {
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
int argc,
|
||||
char** argv,
|
||||
Options options
|
||||
) {
|
||||
vector<string> args;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
return InitFromCmdLineArgs(args);
|
||||
return InitFromCmdLineArgs(args, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,14 +64,17 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(int argc, char** argv) {
|
||||
* Command name is not included in args.
|
||||
* Returns nullptr if the command-line cannot be parsed.
|
||||
*/
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(const vector<string>& args) {
|
||||
LDBCommand* LDBCommand::InitFromCmdLineArgs(
|
||||
const vector<string>& args,
|
||||
Options options
|
||||
) {
|
||||
// --x=y command line arguments are added as x->y map entries.
|
||||
map<string, string> options;
|
||||
map<string, string> option_map;
|
||||
|
||||
// Command-line arguments of the form --hex end up in this array as hex
|
||||
vector<string> flags;
|
||||
|
||||
// Everything other than options and flags. Represents commands
|
||||
// Everything other than option_map and flags. Represents commands
|
||||
// and their parameters. For eg: put key1 value1 go into this vector.
|
||||
vector<string> cmdTokens;
|
||||
|
||||
@@ -65,12 +83,11 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(const vector<string>& args) {
|
||||
for (vector<string>::const_iterator itr = args.begin();
|
||||
itr != args.end(); itr++) {
|
||||
string arg = *itr;
|
||||
if (boost::starts_with(arg, OPTION_PREFIX)){
|
||||
vector<string> splits;
|
||||
boost::split(splits, arg, boost::is_any_of("="));
|
||||
if (arg[0] == '-' && arg[1] == '-'){
|
||||
vector<string> splits = stringSplit(arg, '=');
|
||||
if (splits.size() == 2) {
|
||||
string optionKey = splits[0].substr(OPTION_PREFIX.size());
|
||||
options[optionKey] = splits[1];
|
||||
option_map[optionKey] = splits[1];
|
||||
} else {
|
||||
string optionKey = splits[0].substr(OPTION_PREFIX.size());
|
||||
flags.push_back(optionKey);
|
||||
@@ -87,36 +104,57 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(const vector<string>& args) {
|
||||
|
||||
string cmd = cmdTokens[0];
|
||||
vector<string> cmdParams(cmdTokens.begin()+1, cmdTokens.end());
|
||||
LDBCommand* command = LDBCommand::SelectCommand(
|
||||
cmd,
|
||||
cmdParams,
|
||||
option_map,
|
||||
flags
|
||||
);
|
||||
|
||||
if (command) {
|
||||
command->SetOptions(options);
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
LDBCommand* LDBCommand::SelectCommand(
|
||||
const std::string& cmd,
|
||||
vector<string>& cmdParams,
|
||||
map<string, string>& option_map,
|
||||
vector<string>& flags
|
||||
) {
|
||||
|
||||
if (cmd == GetCommand::Name()) {
|
||||
return new GetCommand(cmdParams, options, flags);
|
||||
return new GetCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == PutCommand::Name()) {
|
||||
return new PutCommand(cmdParams, options, flags);
|
||||
return new PutCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == BatchPutCommand::Name()) {
|
||||
return new BatchPutCommand(cmdParams, options, flags);
|
||||
return new BatchPutCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == ScanCommand::Name()) {
|
||||
return new ScanCommand(cmdParams, options, flags);
|
||||
return new ScanCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == DeleteCommand::Name()) {
|
||||
return new DeleteCommand(cmdParams, options, flags);
|
||||
return new DeleteCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == ApproxSizeCommand::Name()) {
|
||||
return new ApproxSizeCommand(cmdParams, options, flags);
|
||||
return new ApproxSizeCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == DBQuerierCommand::Name()) {
|
||||
return new DBQuerierCommand(cmdParams, options, flags);
|
||||
return new DBQuerierCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == CompactorCommand::Name()) {
|
||||
return new CompactorCommand(cmdParams, options, flags);
|
||||
return new CompactorCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == WALDumperCommand::Name()) {
|
||||
return new WALDumperCommand(cmdParams, options, flags);
|
||||
return new WALDumperCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == ReduceDBLevelsCommand::Name()) {
|
||||
return new ReduceDBLevelsCommand(cmdParams, options, flags);
|
||||
return new ReduceDBLevelsCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == DBDumperCommand::Name()) {
|
||||
return new DBDumperCommand(cmdParams, options, flags);
|
||||
return new DBDumperCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == DBLoaderCommand::Name()) {
|
||||
return new DBLoaderCommand(cmdParams, options, flags);
|
||||
return new DBLoaderCommand(cmdParams, option_map, flags);
|
||||
} else if (cmd == ManifestDumpCommand::Name()) {
|
||||
return new ManifestDumpCommand(cmdParams, option_map, flags);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses the specific integer option and fills in the value.
|
||||
* Returns true if the option is found.
|
||||
@@ -127,30 +165,33 @@ LDBCommand* LDBCommand::InitFromCmdLineArgs(const vector<string>& args) {
|
||||
bool LDBCommand::ParseIntOption(const map<string, string>& options,
|
||||
string option, int& value, LDBCommandExecuteResult& exec_state) {
|
||||
|
||||
map<string, string>::const_iterator itr = options_.find(option);
|
||||
if (itr != options_.end()) {
|
||||
map<string, string>::const_iterator itr = option_map_.find(option);
|
||||
if (itr != option_map_.end()) {
|
||||
try {
|
||||
value = boost::lexical_cast<int>(itr->second);
|
||||
value = stoi(itr->second);
|
||||
return true;
|
||||
} catch( const boost::bad_lexical_cast & ) {
|
||||
} catch(const invalid_argument&) {
|
||||
exec_state = LDBCommandExecuteResult::FAILED(option +
|
||||
" has an invalid value.");
|
||||
} catch(const out_of_range&) {
|
||||
exec_state = LDBCommandExecuteResult::FAILED(option +
|
||||
" has a value out-of-range.");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
|
||||
leveldb::Options opt;
|
||||
Options opt = options_;
|
||||
opt.create_if_missing = false;
|
||||
|
||||
map<string, string>::const_iterator itr;
|
||||
|
||||
int bits;
|
||||
if (ParseIntOption(options_, ARG_BLOOM_BITS, bits, exec_state_)) {
|
||||
if (ParseIntOption(option_map_, ARG_BLOOM_BITS, bits, exec_state_)) {
|
||||
if (bits > 0) {
|
||||
opt.filter_policy = leveldb::NewBloomFilterPolicy(bits);
|
||||
opt.filter_policy = NewBloomFilterPolicy(bits);
|
||||
} else {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(ARG_BLOOM_BITS +
|
||||
" must be > 0.");
|
||||
@@ -158,7 +199,7 @@ leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
}
|
||||
|
||||
int block_size;
|
||||
if (ParseIntOption(options_, ARG_BLOCK_SIZE, block_size, exec_state_)) {
|
||||
if (ParseIntOption(option_map_, ARG_BLOCK_SIZE, block_size, exec_state_)) {
|
||||
if (block_size > 0) {
|
||||
opt.block_size = block_size;
|
||||
} else {
|
||||
@@ -167,22 +208,22 @@ leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
}
|
||||
}
|
||||
|
||||
itr = options_.find(ARG_AUTO_COMPACTION);
|
||||
if (itr != options_.end()) {
|
||||
itr = option_map_.find(ARG_AUTO_COMPACTION);
|
||||
if (itr != option_map_.end()) {
|
||||
opt.disable_auto_compactions = ! StringToBool(itr->second);
|
||||
}
|
||||
|
||||
itr = options_.find(ARG_COMPRESSION_TYPE);
|
||||
if (itr != options_.end()) {
|
||||
itr = option_map_.find(ARG_COMPRESSION_TYPE);
|
||||
if (itr != option_map_.end()) {
|
||||
string comp = itr->second;
|
||||
if (comp == "no") {
|
||||
opt.compression = leveldb::kNoCompression;
|
||||
opt.compression = kNoCompression;
|
||||
} else if (comp == "snappy") {
|
||||
opt.compression = leveldb::kSnappyCompression;
|
||||
opt.compression = kSnappyCompression;
|
||||
} else if (comp == "zlib") {
|
||||
opt.compression = leveldb::kZlibCompression;
|
||||
opt.compression = kZlibCompression;
|
||||
} else if (comp == "bzip2") {
|
||||
opt.compression = leveldb::kBZip2Compression;
|
||||
opt.compression = kBZip2Compression;
|
||||
} else {
|
||||
// Unknown compression.
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(
|
||||
@@ -191,7 +232,7 @@ leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
}
|
||||
|
||||
int write_buffer_size;
|
||||
if (ParseIntOption(options_, ARG_WRITE_BUFFER_SIZE, write_buffer_size,
|
||||
if (ParseIntOption(option_map_, ARG_WRITE_BUFFER_SIZE, write_buffer_size,
|
||||
exec_state_)) {
|
||||
if (write_buffer_size > 0) {
|
||||
opt.write_buffer_size = write_buffer_size;
|
||||
@@ -202,7 +243,7 @@ leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
}
|
||||
|
||||
int file_size;
|
||||
if (ParseIntOption(options_, ARG_FILE_SIZE, file_size, exec_state_)) {
|
||||
if (ParseIntOption(option_map_, ARG_FILE_SIZE, file_size, exec_state_)) {
|
||||
if (file_size > 0) {
|
||||
opt.target_file_size_base = file_size;
|
||||
} else {
|
||||
@@ -217,7 +258,7 @@ leveldb::Options LDBCommand::PrepareOptionsForOpenDB() {
|
||||
bool LDBCommand::ParseKeyValue(const string& line, string* key, string* value,
|
||||
bool is_key_hex, bool is_value_hex) {
|
||||
size_t pos = line.find(DELIM);
|
||||
if (pos != std::string::npos) {
|
||||
if (pos != string::npos) {
|
||||
*key = line.substr(0, pos);
|
||||
*value = line.substr(pos + strlen(DELIM));
|
||||
if (is_key_hex) {
|
||||
@@ -241,9 +282,9 @@ bool LDBCommand::ParseKeyValue(const string& line, string* key, string* value,
|
||||
*/
|
||||
bool LDBCommand::ValidateCmdLineOptions() {
|
||||
|
||||
for (map<string, string>::const_iterator itr = options_.begin();
|
||||
itr != options_.end(); itr++) {
|
||||
if (std::find(valid_cmd_line_options_.begin(),
|
||||
for (map<string, string>::const_iterator itr = option_map_.begin();
|
||||
itr != option_map_.end(); itr++) {
|
||||
if (find(valid_cmd_line_options_.begin(),
|
||||
valid_cmd_line_options_.end(), itr->first) ==
|
||||
valid_cmd_line_options_.end()) {
|
||||
fprintf(stderr, "Invalid command-line option %s\n", itr->first.c_str());
|
||||
@@ -253,7 +294,7 @@ bool LDBCommand::ValidateCmdLineOptions() {
|
||||
|
||||
for (vector<string>::const_iterator itr = flags_.begin();
|
||||
itr != flags_.end(); itr++) {
|
||||
if (std::find(valid_cmd_line_options_.begin(),
|
||||
if (find(valid_cmd_line_options_.begin(),
|
||||
valid_cmd_line_options_.end(), *itr) ==
|
||||
valid_cmd_line_options_.end()) {
|
||||
fprintf(stderr, "Invalid command-line flag %s\n", itr->c_str());
|
||||
@@ -261,7 +302,7 @@ bool LDBCommand::ValidateCmdLineOptions() {
|
||||
}
|
||||
}
|
||||
|
||||
if (options_.find(ARG_DB) == options_.end()) {
|
||||
if (!NoDBOpen() && option_map_.find(ARG_DB) == option_map_.end()) {
|
||||
fprintf(stderr, "%s must be specified\n", ARG_DB.c_str());
|
||||
return false;
|
||||
}
|
||||
@@ -307,13 +348,13 @@ void CompactorCommand::Help(string& ret) {
|
||||
|
||||
void CompactorCommand::DoCommand() {
|
||||
|
||||
leveldb::Slice* begin = nullptr;
|
||||
leveldb::Slice* end = nullptr;
|
||||
Slice* begin = nullptr;
|
||||
Slice* end = nullptr;
|
||||
if (!null_from_) {
|
||||
begin = new leveldb::Slice(from_);
|
||||
begin = new Slice(from_);
|
||||
}
|
||||
if (!null_to_) {
|
||||
end = new leveldb::Slice(to_);
|
||||
end = new Slice(to_);
|
||||
}
|
||||
|
||||
db_->CompactRange(begin, end);
|
||||
@@ -353,8 +394,8 @@ void DBLoaderCommand::Help(string& ret) {
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
leveldb::Options DBLoaderCommand::PrepareOptionsForOpenDB() {
|
||||
leveldb::Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
Options DBLoaderCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = create_if_missing_;
|
||||
if (bulk_load_) {
|
||||
opt.PrepareForBulkLoad();
|
||||
@@ -374,7 +415,7 @@ void DBLoaderCommand::DoCommand() {
|
||||
|
||||
int bad_lines = 0;
|
||||
string line;
|
||||
while (std::getline(std::cin, line, '\n')) {
|
||||
while (getline(cin, line, '\n')) {
|
||||
string key;
|
||||
string value;
|
||||
if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) {
|
||||
@@ -389,22 +430,147 @@ void DBLoaderCommand::DoCommand() {
|
||||
}
|
||||
|
||||
if (bad_lines > 0) {
|
||||
std::cout << "Warning: " << bad_lines << " bad lines ignored." << std::endl;
|
||||
cout << "Warning: " << bad_lines << " bad lines ignored." << endl;
|
||||
}
|
||||
if (compact_) {
|
||||
db_->CompactRange(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const string ManifestDumpCommand::ARG_VERBOSE = "verbose";
|
||||
const string ManifestDumpCommand::ARG_PATH = "path";
|
||||
|
||||
void ManifestDumpCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(ManifestDumpCommand::Name());
|
||||
ret.append(" [--" + ARG_VERBOSE + "]");
|
||||
ret.append(" [--" + ARG_PATH + "=<path_to_manifest_file>]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
ManifestDumpCommand::ManifestDumpCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, false,
|
||||
BuildCmdLineOptions({ARG_VERBOSE,ARG_PATH})),
|
||||
verbose_(false),
|
||||
path_("")
|
||||
{
|
||||
verbose_ = IsFlagPresent(flags, ARG_VERBOSE);
|
||||
|
||||
map<string, string>::const_iterator itr = options.find(ARG_PATH);
|
||||
if (itr != options.end()) {
|
||||
path_ = itr->second;
|
||||
if (path_.empty()) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("--path: missing pathname");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManifestDumpCommand::DoCommand() {
|
||||
|
||||
std::string manifestfile;
|
||||
|
||||
if (!path_.empty()) {
|
||||
manifestfile = path_;
|
||||
} else {
|
||||
bool found = false;
|
||||
// We need to find the manifest file by searching the directory
|
||||
// containing the db for files of the form MANIFEST_[0-9]+
|
||||
DIR* d = opendir(db_path_.c_str());
|
||||
if (d == nullptr) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(
|
||||
db_path_ + " is not a directory");
|
||||
return;
|
||||
}
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(d)) != nullptr) {
|
||||
unsigned int match;
|
||||
unsigned long long num;
|
||||
if (sscanf(entry->d_name, "MANIFEST-%llu%n", &num, &match)
|
||||
&& match == strlen(entry->d_name)) {
|
||||
if (!found) {
|
||||
manifestfile = db_path_ + "/" + std::string(entry->d_name);
|
||||
found = true;
|
||||
} else {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(
|
||||
"Multiple MANIFEST files found; use --path to select one");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
if (verbose_) {
|
||||
printf("Processing Manifest file %s\n", manifestfile.c_str());
|
||||
}
|
||||
|
||||
Options options;
|
||||
EnvOptions sopt;
|
||||
std::string file(manifestfile);
|
||||
std::string dbname("dummy");
|
||||
TableCache* tc = new TableCache(dbname, &options, sopt, 10);
|
||||
const InternalKeyComparator* cmp =
|
||||
new InternalKeyComparator(options.comparator);
|
||||
|
||||
VersionSet* versions = new VersionSet(dbname, &options, sopt, tc, cmp);
|
||||
Status s = versions->DumpManifest(options, file, verbose_, is_key_hex_);
|
||||
if (!s.ok()) {
|
||||
printf("Error in processing file %s %s\n", manifestfile.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
if (verbose_) {
|
||||
printf("Processing Manifest file %s done\n", manifestfile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
string ReadableTime(int unixtime) {
|
||||
char time_buffer [80];
|
||||
time_t rawtime = unixtime;
|
||||
struct tm * timeinfo = localtime(&rawtime);
|
||||
strftime(time_buffer, 80, "%c", timeinfo);
|
||||
return string(time_buffer);
|
||||
}
|
||||
|
||||
// This function only called when it's the sane case of >1 buckets in time-range
|
||||
// Also called only when timekv falls between ttl_start and ttl_end provided
|
||||
void IncBucketCounts(vector<uint64_t>& bucket_counts, int ttl_start,
|
||||
int time_range, int bucket_size, int timekv, int num_buckets) {
|
||||
assert(time_range > 0 && timekv >= ttl_start && bucket_size > 0 &&
|
||||
timekv < (ttl_start + time_range) && num_buckets > 1);
|
||||
int bucket = (timekv - ttl_start) / bucket_size;
|
||||
bucket_counts[bucket]++;
|
||||
}
|
||||
|
||||
void PrintBucketCounts(const vector<uint64_t>& bucket_counts, int ttl_start,
|
||||
int ttl_end, int bucket_size, int num_buckets) {
|
||||
int time_point = ttl_start;
|
||||
for(int i = 0; i < num_buckets - 1; i++, time_point += bucket_size) {
|
||||
fprintf(stdout, "Keys in range %s to %s : %lu\n",
|
||||
ReadableTime(time_point).c_str(),
|
||||
ReadableTime(time_point + bucket_size).c_str(), bucket_counts[i]);
|
||||
}
|
||||
fprintf(stdout, "Keys in range %s to %s : %lu\n",
|
||||
ReadableTime(time_point).c_str(),
|
||||
ReadableTime(ttl_end).c_str(), bucket_counts[num_buckets - 1]);
|
||||
}
|
||||
|
||||
const string DBDumperCommand::ARG_COUNT_ONLY = "count_only";
|
||||
const string DBDumperCommand::ARG_STATS = "stats";
|
||||
const string DBDumperCommand::ARG_TTL_BUCKET = "bucket";
|
||||
|
||||
DBDumperCommand::DBDumperCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_FROM, ARG_TO, ARG_MAX_KEYS,
|
||||
ARG_COUNT_ONLY, ARG_STATS})),
|
||||
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})),
|
||||
null_from_(true),
|
||||
null_to_(true),
|
||||
max_keys_(-1),
|
||||
@@ -426,10 +592,13 @@ DBDumperCommand::DBDumperCommand(const vector<string>& params,
|
||||
itr = options.find(ARG_MAX_KEYS);
|
||||
if (itr != options.end()) {
|
||||
try {
|
||||
max_keys_ = boost::lexical_cast<int>(itr->second);
|
||||
} catch( const boost::bad_lexical_cast & ) {
|
||||
max_keys_ = stoi(itr->second);
|
||||
} catch(const invalid_argument&) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(ARG_MAX_KEYS +
|
||||
" has an invalid value");
|
||||
} catch(const out_of_range&) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(ARG_MAX_KEYS +
|
||||
" has a value out-of-range");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,9 +619,14 @@ void DBDumperCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(DBDumperCommand::Name());
|
||||
ret.append(HelpRangeCmdArgs());
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append(" [--" + ARG_MAX_KEYS + "=<N>]");
|
||||
ret.append(" [--" + ARG_TIMESTAMP + "]");
|
||||
ret.append(" [--" + ARG_COUNT_ONLY + "]");
|
||||
ret.append(" [--" + ARG_STATS + "]");
|
||||
ret.append(" [--" + ARG_TTL_BUCKET + "=<N>]");
|
||||
ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
|
||||
ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
@@ -470,8 +644,8 @@ void DBDumperCommand::DoCommand() {
|
||||
}
|
||||
|
||||
// Setup key iterator
|
||||
leveldb::Iterator* iter = db_->NewIterator(leveldb::ReadOptions());
|
||||
leveldb::Status st = iter->status();
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
Status st = iter->status();
|
||||
if (!st.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("Iterator error."
|
||||
+ st.ToString());
|
||||
@@ -484,25 +658,73 @@ void DBDumperCommand::DoCommand() {
|
||||
}
|
||||
|
||||
int max_keys = max_keys_;
|
||||
int ttl_start;
|
||||
if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) {
|
||||
ttl_start = DBWithTTL::kMinTimestamp; // TTL introduction time
|
||||
}
|
||||
int ttl_end;
|
||||
if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) {
|
||||
ttl_end = DBWithTTL::kMaxTimestamp; // Max time allowed by TTL feature
|
||||
}
|
||||
if (ttl_end < ttl_start) {
|
||||
fprintf(stderr, "Error: End time can't be less than start time\n");
|
||||
delete iter;
|
||||
return;
|
||||
}
|
||||
int time_range = ttl_end - ttl_start;
|
||||
int bucket_size;
|
||||
if (!ParseIntOption(option_map_, ARG_TTL_BUCKET, bucket_size, exec_state_) ||
|
||||
bucket_size <= 0) {
|
||||
bucket_size = time_range; // Will have just 1 bucket by default
|
||||
}
|
||||
// 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_) {
|
||||
fprintf(stdout, "Dumping key-values from %s to %s\n",
|
||||
ReadableTime(ttl_start).c_str(), ReadableTime(ttl_end).c_str());
|
||||
}
|
||||
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
int rawtime = 0;
|
||||
// If end marker was specified, we stop before it
|
||||
if (!null_to_ && (iter->key().ToString() >= to_))
|
||||
break;
|
||||
// Terminate if maximum number of keys have been dumped
|
||||
if (max_keys == 0)
|
||||
break;
|
||||
if (is_db_ttl_) {
|
||||
TtlIterator* it_ttl = (TtlIterator*)iter;
|
||||
rawtime = it_ttl->timestamp();
|
||||
if (rawtime < ttl_start || rawtime >= ttl_end) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (max_keys > 0) {
|
||||
--max_keys;
|
||||
}
|
||||
if (is_db_ttl_ && num_buckets > 1) {
|
||||
IncBucketCounts(bucket_counts, ttl_start, time_range, bucket_size,
|
||||
rawtime, num_buckets);
|
||||
}
|
||||
++count;
|
||||
if (!count_only_) {
|
||||
if (is_db_ttl_ && timestamp_) {
|
||||
fprintf(stdout, "%s ", ReadableTime(rawtime).c_str());
|
||||
}
|
||||
string str = PrintKeyValue(iter->key().ToString(),
|
||||
iter->value().ToString(),
|
||||
is_key_hex_, is_value_hex_);
|
||||
iter->value().ToString(), is_key_hex_,
|
||||
is_value_hex_);
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
}
|
||||
fprintf(stdout, "Keys in range: %lld\n", (long long) count);
|
||||
if (num_buckets > 1 && is_db_ttl_) {
|
||||
PrintBucketCounts(bucket_counts, ttl_start, ttl_end, bucket_size,
|
||||
num_buckets);
|
||||
} else {
|
||||
fprintf(stdout, "Keys in range: %lld\n", (long long) count);
|
||||
}
|
||||
// Clean up
|
||||
delete iter;
|
||||
}
|
||||
@@ -519,7 +741,7 @@ ReduceDBLevelsCommand::ReduceDBLevelsCommand(const vector<string>& params,
|
||||
print_old_levels_(false) {
|
||||
|
||||
|
||||
ParseIntOption(options_, ARG_NEW_LEVELS, new_levels_, exec_state_);
|
||||
ParseIntOption(option_map_, ARG_NEW_LEVELS, new_levels_, exec_state_);
|
||||
print_old_levels_ = IsFlagPresent(flags, ARG_PRINT_OLD_LEVELS);
|
||||
|
||||
if(new_levels_ <= 0) {
|
||||
@@ -533,7 +755,7 @@ vector<string> ReduceDBLevelsCommand::PrepareArgs(const string& db_path,
|
||||
vector<string> ret;
|
||||
ret.push_back("reduce_levels");
|
||||
ret.push_back("--" + ARG_DB + "=" + db_path);
|
||||
ret.push_back("--" + ARG_NEW_LEVELS + "=" + std::to_string(new_levels));
|
||||
ret.push_back("--" + ARG_NEW_LEVELS + "=" + to_string(new_levels));
|
||||
if(print_old_level) {
|
||||
ret.push_back("--" + ARG_PRINT_OLD_LEVELS);
|
||||
}
|
||||
@@ -548,9 +770,10 @@ void ReduceDBLevelsCommand::Help(string& ret) {
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
leveldb::Options ReduceDBLevelsCommand::PrepareOptionsForOpenDB() {
|
||||
leveldb::Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
Options ReduceDBLevelsCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.num_levels = old_levels_;
|
||||
opt.max_bytes_for_level_multiplier_additional.resize(opt.num_levels, 1);
|
||||
// Disable size compaction
|
||||
opt.max_bytes_for_level_base = 1UL << 50;
|
||||
opt.max_bytes_for_level_multiplier = 1;
|
||||
@@ -558,11 +781,12 @@ leveldb::Options ReduceDBLevelsCommand::PrepareOptionsForOpenDB() {
|
||||
return opt;
|
||||
}
|
||||
|
||||
Status ReduceDBLevelsCommand::GetOldNumOfLevels(leveldb::Options& opt,
|
||||
Status ReduceDBLevelsCommand::GetOldNumOfLevels(Options& opt,
|
||||
int* levels) {
|
||||
TableCache tc(db_path_, &opt, 10);
|
||||
EnvOptions soptions;
|
||||
TableCache tc(db_path_, &opt, soptions, 10);
|
||||
const InternalKeyComparator cmp(opt.comparator);
|
||||
VersionSet versions(db_path_, &opt, &tc, &cmp);
|
||||
VersionSet versions(db_path_, &opt, soptions, &tc, &cmp);
|
||||
// We rely the VersionSet::Recover to tell us the internal data structures
|
||||
// in the db. And the Recover() should never do any change
|
||||
// (like LogAndApply) to the manifest file.
|
||||
@@ -588,8 +812,8 @@ void ReduceDBLevelsCommand::DoCommand() {
|
||||
return;
|
||||
}
|
||||
|
||||
leveldb::Status st;
|
||||
leveldb::Options opt = PrepareOptionsForOpenDB();
|
||||
Status st;
|
||||
Options opt = PrepareOptionsForOpenDB();
|
||||
int old_level_num = -1;
|
||||
st = GetOldNumOfLevels(opt, &old_level_num);
|
||||
if (!st.ok()) {
|
||||
@@ -616,9 +840,10 @@ void ReduceDBLevelsCommand::DoCommand() {
|
||||
db_->CompactRange(nullptr, nullptr);
|
||||
CloseDB();
|
||||
|
||||
TableCache tc(db_path_, &opt, 10);
|
||||
EnvOptions soptions;
|
||||
TableCache tc(db_path_, &opt, soptions, 10);
|
||||
const InternalKeyComparator cmp(opt.comparator);
|
||||
VersionSet versions(db_path_, &opt, &tc, &cmp);
|
||||
VersionSet versions(db_path_, &opt, soptions, &tc, &cmp);
|
||||
// We rely the VersionSet::Recover to tell us the internal data structures
|
||||
// in the db. And the Recover() should never do any change (like LogAndApply)
|
||||
// to the manifest file.
|
||||
@@ -658,8 +883,8 @@ class InMemoryHandler : public WriteBatch::Handler {
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<string, string> putMap_;
|
||||
std::vector<string> deleteList_;
|
||||
map<string, string> putMap_;
|
||||
vector<string> deleteList_;
|
||||
};
|
||||
|
||||
const string WALDumperCommand::ARG_WAL_FILE = "walfile";
|
||||
@@ -701,29 +926,30 @@ void WALDumperCommand::Help(string& ret) {
|
||||
void WALDumperCommand::DoCommand() {
|
||||
struct StdErrReporter : public log::Reader::Reporter {
|
||||
virtual void Corruption(size_t bytes, const Status& s) {
|
||||
std::cerr<<"Corruption detected in log file "<<s.ToString()<<"\n";
|
||||
cerr<<"Corruption detected in log file "<<s.ToString()<<"\n";
|
||||
}
|
||||
};
|
||||
|
||||
unique_ptr<SequentialFile> file;
|
||||
Env* env_ = Env::Default();
|
||||
Status status = env_->NewSequentialFile(wal_file_, &file);
|
||||
EnvOptions soptions;
|
||||
Status status = env_->NewSequentialFile(wal_file_, &file, soptions);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED("Failed to open WAL file " +
|
||||
status.ToString());
|
||||
} else {
|
||||
StdErrReporter reporter;
|
||||
log::Reader reader(std::move(file), &reporter, true, 0);
|
||||
log::Reader reader(move(file), &reporter, true, 0);
|
||||
string scratch;
|
||||
WriteBatch batch;
|
||||
Slice record;
|
||||
std::stringstream row;
|
||||
stringstream row;
|
||||
if (print_header_) {
|
||||
std::cout<<"Sequence,Count,ByteSize,Physical Offset,Key(s)";
|
||||
cout<<"Sequence,Count,ByteSize,Physical Offset,Key(s)";
|
||||
if (print_values_) {
|
||||
std::cout << " : value ";
|
||||
cout << " : value ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
cout << "\n";
|
||||
}
|
||||
while(reader.ReadRecord(&record, &scratch)) {
|
||||
row.str("");
|
||||
@@ -741,8 +967,8 @@ void WALDumperCommand::DoCommand() {
|
||||
row << "PUT : ";
|
||||
if (print_values_) {
|
||||
for (auto& kv : handler.PutMap()) {
|
||||
std::string k = StringToHex(kv.first);
|
||||
std::string v = StringToHex(kv.second);
|
||||
string k = StringToHex(kv.first);
|
||||
string v = StringToHex(kv.second);
|
||||
row << k << " : ";
|
||||
row << v << " ";
|
||||
}
|
||||
@@ -758,7 +984,7 @@ void WALDumperCommand::DoCommand() {
|
||||
}
|
||||
row<<"\n";
|
||||
}
|
||||
std::cout<<row.str();
|
||||
cout<<row.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -766,8 +992,9 @@ void WALDumperCommand::DoCommand() {
|
||||
|
||||
GetCommand::GetCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
|
||||
LDBCommand(options, flags, true, BuildCmdLineOptions({ARG_TTL, ARG_HEX,
|
||||
ARG_KEY_HEX,
|
||||
ARG_VALUE_HEX})) {
|
||||
|
||||
if (params.size() != 1) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(
|
||||
@@ -785,12 +1012,13 @@ void GetCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(GetCommand::Name());
|
||||
ret.append(" <key>");
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
void GetCommand::DoCommand() {
|
||||
string value;
|
||||
leveldb::Status st = db_->Get(leveldb::ReadOptions(), key_, &value);
|
||||
Status st = db_->Get(ReadOptions(), key_, &value);
|
||||
if (st.ok()) {
|
||||
fprintf(stdout, "%s\n",
|
||||
(is_value_hex_ ? StringToHex(value) : value).c_str());
|
||||
@@ -837,8 +1065,8 @@ void ApproxSizeCommand::Help(string& ret) {
|
||||
|
||||
void ApproxSizeCommand::DoCommand() {
|
||||
|
||||
leveldb::Range ranges[1];
|
||||
ranges[0] = leveldb::Range(start_key_, end_key_);
|
||||
Range ranges[1];
|
||||
ranges[0] = Range(start_key_, end_key_);
|
||||
uint64_t sizes[1];
|
||||
db_->GetApproximateSizes(ranges, 1, sizes);
|
||||
fprintf(stdout, "%ld\n", sizes[0]);
|
||||
@@ -854,7 +1082,7 @@ void ApproxSizeCommand::DoCommand() {
|
||||
BatchPutCommand::BatchPutCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, false,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_CREATE_IF_MISSING})) {
|
||||
|
||||
if (params.size() < 2) {
|
||||
@@ -867,7 +1095,7 @@ BatchPutCommand::BatchPutCommand(const vector<string>& params,
|
||||
for (size_t i = 0; i < params.size(); i += 2) {
|
||||
string key = params.at(i);
|
||||
string value = params.at(i+1);
|
||||
key_values_.push_back(std::pair<string, string>(
|
||||
key_values_.push_back(pair<string, string>(
|
||||
is_key_hex_ ? HexToString(key) : key,
|
||||
is_value_hex_ ? HexToString(value) : value));
|
||||
}
|
||||
@@ -878,17 +1106,18 @@ void BatchPutCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(BatchPutCommand::Name());
|
||||
ret.append(" <key> <value> [<key> <value>] [..]");
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
void BatchPutCommand::DoCommand() {
|
||||
leveldb::WriteBatch batch;
|
||||
WriteBatch batch;
|
||||
|
||||
for (vector<std::pair<string, string>>::const_iterator itr
|
||||
for (vector<pair<string, string>>::const_iterator itr
|
||||
= key_values_.begin(); itr != key_values_.end(); itr++) {
|
||||
batch.Put(itr->first, itr->second);
|
||||
}
|
||||
leveldb::Status st = db_->Write(leveldb::WriteOptions(), &batch);
|
||||
Status st = db_->Write(WriteOptions(), &batch);
|
||||
if (st.ok()) {
|
||||
fprintf(stdout, "OK\n");
|
||||
} else {
|
||||
@@ -896,8 +1125,8 @@ void BatchPutCommand::DoCommand() {
|
||||
}
|
||||
}
|
||||
|
||||
leveldb::Options BatchPutCommand::PrepareOptionsForOpenDB() {
|
||||
leveldb::Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
Options BatchPutCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING);
|
||||
return opt;
|
||||
}
|
||||
@@ -906,8 +1135,9 @@ leveldb::Options BatchPutCommand::PrepareOptionsForOpenDB() {
|
||||
ScanCommand::ScanCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, true,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_FROM, ARG_TO, ARG_MAX_KEYS})),
|
||||
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_TO,
|
||||
ARG_VALUE_HEX, ARG_FROM, ARG_TIMESTAMP,
|
||||
ARG_MAX_KEYS, ARG_TTL_START, ARG_TTL_END})),
|
||||
start_key_specified_(false),
|
||||
end_key_specified_(false),
|
||||
max_keys_scanned_(-1) {
|
||||
@@ -932,10 +1162,13 @@ ScanCommand::ScanCommand(const vector<string>& params,
|
||||
itr = options.find(ARG_MAX_KEYS);
|
||||
if (itr != options.end()) {
|
||||
try {
|
||||
max_keys_scanned_ = boost::lexical_cast< int >(itr->second);
|
||||
} catch( const boost::bad_lexical_cast & ) {
|
||||
max_keys_scanned_ = stoi(itr->second);
|
||||
} catch(const invalid_argument&) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(ARG_MAX_KEYS +
|
||||
" has an invalid value");
|
||||
} catch(const out_of_range&) {
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(ARG_MAX_KEYS +
|
||||
" has a value out-of-range");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -944,23 +1177,54 @@ void ScanCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(ScanCommand::Name());
|
||||
ret.append(HelpRangeCmdArgs());
|
||||
ret.append("--" + ARG_MAX_KEYS + "=N] ");
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append(" [--" + ARG_TIMESTAMP + "]");
|
||||
ret.append(" [--" + ARG_MAX_KEYS + "=<N>q] ");
|
||||
ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]");
|
||||
ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
void ScanCommand::DoCommand() {
|
||||
|
||||
int num_keys_scanned = 0;
|
||||
Iterator* it = db_->NewIterator(leveldb::ReadOptions());
|
||||
Iterator* it = db_->NewIterator(ReadOptions());
|
||||
if (start_key_specified_) {
|
||||
it->Seek(start_key_);
|
||||
} else {
|
||||
it->SeekToFirst();
|
||||
}
|
||||
int ttl_start;
|
||||
if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) {
|
||||
ttl_start = DBWithTTL::kMinTimestamp; // TTL introduction time
|
||||
}
|
||||
int ttl_end;
|
||||
if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) {
|
||||
ttl_end = DBWithTTL::kMaxTimestamp; // Max time allowed by TTL feature
|
||||
}
|
||||
if (ttl_end < ttl_start) {
|
||||
fprintf(stderr, "Error: End time can't be less than start time\n");
|
||||
delete it;
|
||||
return;
|
||||
}
|
||||
if (is_db_ttl_ && timestamp_) {
|
||||
fprintf(stdout, "Scanning key-values from %s to %s\n",
|
||||
ReadableTime(ttl_start).c_str(), ReadableTime(ttl_end).c_str());
|
||||
}
|
||||
for ( ;
|
||||
it->Valid() && (!end_key_specified_ || it->key().ToString() < end_key_);
|
||||
it->Next()) {
|
||||
string key = it->key().ToString();
|
||||
if (is_db_ttl_) {
|
||||
TtlIterator* it_ttl = (TtlIterator*)it;
|
||||
int rawtime = it_ttl->timestamp();
|
||||
if (rawtime < ttl_start || rawtime >= ttl_end) {
|
||||
continue;
|
||||
}
|
||||
if (timestamp_) {
|
||||
fprintf(stdout, "%s ", ReadableTime(rawtime).c_str());
|
||||
}
|
||||
}
|
||||
string value = it->value().ToString();
|
||||
fprintf(stdout, "%s : %s\n",
|
||||
(is_key_hex_ ? StringToHex(key) : key).c_str(),
|
||||
@@ -1001,7 +1265,7 @@ void DeleteCommand::Help(string& ret) {
|
||||
}
|
||||
|
||||
void DeleteCommand::DoCommand() {
|
||||
leveldb::Status st = db_->Delete(leveldb::WriteOptions(), key_);
|
||||
Status st = db_->Delete(WriteOptions(), key_);
|
||||
if (st.ok()) {
|
||||
fprintf(stdout, "OK\n");
|
||||
} else {
|
||||
@@ -1013,7 +1277,7 @@ void DeleteCommand::DoCommand() {
|
||||
PutCommand::PutCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, false,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX,
|
||||
ARG_CREATE_IF_MISSING})) {
|
||||
|
||||
if (params.size() != 2) {
|
||||
@@ -1037,11 +1301,12 @@ void PutCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(PutCommand::Name());
|
||||
ret.append(" <key> <value> ");
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
void PutCommand::DoCommand() {
|
||||
leveldb::Status st = db_->Put(leveldb::WriteOptions(), key_, value_);
|
||||
Status st = db_->Put(WriteOptions(), key_, value_);
|
||||
if (st.ok()) {
|
||||
fprintf(stdout, "OK\n");
|
||||
} else {
|
||||
@@ -1049,8 +1314,8 @@ void PutCommand::DoCommand() {
|
||||
}
|
||||
}
|
||||
|
||||
leveldb::Options PutCommand::PrepareOptionsForOpenDB() {
|
||||
leveldb::Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
Options PutCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING);
|
||||
return opt;
|
||||
}
|
||||
@@ -1064,13 +1329,15 @@ const char* DBQuerierCommand::DELETE_CMD = "delete";
|
||||
DBQuerierCommand::DBQuerierCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags) :
|
||||
LDBCommand(options, flags, false,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) {
|
||||
BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX,
|
||||
ARG_VALUE_HEX})) {
|
||||
|
||||
}
|
||||
|
||||
void DBQuerierCommand::Help(string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(DBQuerierCommand::Name());
|
||||
ret.append(" [--" + ARG_TTL + "]");
|
||||
ret.append("\n");
|
||||
ret.append(" Starts a REPL shell. Type help for list of available "
|
||||
"commands.");
|
||||
@@ -1082,13 +1349,13 @@ void DBQuerierCommand::DoCommand() {
|
||||
return;
|
||||
}
|
||||
|
||||
leveldb::ReadOptions read_options;
|
||||
leveldb::WriteOptions write_options;
|
||||
ReadOptions read_options;
|
||||
WriteOptions write_options;
|
||||
|
||||
string line;
|
||||
string key;
|
||||
string value;
|
||||
while (getline(std::cin, line, '\n')) {
|
||||
while (getline(cin, line, '\n')) {
|
||||
|
||||
// Parse line into vector<string>
|
||||
vector<string> tokens;
|
||||
|
||||
+93
-29
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef LEVELDB_UTIL_LDB_CMD_H_
|
||||
#define LEVELDB_UTIL_LDB_CMD_H_
|
||||
#ifndef STORAGE_LEVELDB_UTIL_LDB_CMD_H_
|
||||
#define STORAGE_LEVELDB_UTIL_LDB_CMD_H_
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
@@ -12,17 +12,16 @@
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "db/version_set.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "db/version_set.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/ldb_cmd_execute_result.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/utility_db.h"
|
||||
#include "utilities/ttl/db_ttl.h"
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
@@ -39,6 +38,10 @@ public:
|
||||
static const string ARG_HEX;
|
||||
static const string ARG_KEY_HEX;
|
||||
static const string ARG_VALUE_HEX;
|
||||
static const string ARG_TTL;
|
||||
static const string ARG_TTL_START;
|
||||
static const string ARG_TTL_END;
|
||||
static const string ARG_TIMESTAMP;
|
||||
static const string ARG_FROM;
|
||||
static const string ARG_TO;
|
||||
static const string ARG_MAX_KEYS;
|
||||
@@ -50,11 +53,24 @@ public:
|
||||
static const string ARG_FILE_SIZE;
|
||||
static const string ARG_CREATE_IF_MISSING;
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(const vector<string>& args);
|
||||
static LDBCommand* InitFromCmdLineArgs(int argc, char** argv);
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
const vector<string>& args,
|
||||
Options options = Options()
|
||||
);
|
||||
|
||||
static LDBCommand* InitFromCmdLineArgs(
|
||||
int argc,
|
||||
char** argv,
|
||||
Options options = Options()
|
||||
);
|
||||
|
||||
bool ValidateCmdLineOptions();
|
||||
|
||||
virtual leveldb::Options PrepareOptionsForOpenDB();
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
virtual void SetOptions(Options options) {
|
||||
options_ = options;
|
||||
}
|
||||
|
||||
virtual bool NoDBOpen() {
|
||||
return false;
|
||||
@@ -102,7 +118,7 @@ public:
|
||||
|
||||
static string HexToString(const string& str) {
|
||||
string parsed;
|
||||
if (!boost::starts_with(str, "0x")) {
|
||||
if (str[0] != '0' || str[1] != 'x') {
|
||||
fprintf(stderr, "Invalid hex input %s. Must start with 0x\n",
|
||||
str.c_str());
|
||||
throw "Invalid hex input";
|
||||
@@ -132,8 +148,8 @@ public:
|
||||
protected:
|
||||
|
||||
LDBCommandExecuteResult exec_state_;
|
||||
std::string db_path_;
|
||||
leveldb::DB* db_;
|
||||
string db_path_;
|
||||
DB* db_;
|
||||
|
||||
/**
|
||||
* true implies that this command can work if the db is opened in read-only
|
||||
@@ -147,10 +163,16 @@ protected:
|
||||
/** If true, the value is input/output as hex in get/put/scan/delete etc. */
|
||||
bool is_value_hex_;
|
||||
|
||||
/** If true, the value is treated as timestamp suffixed */
|
||||
bool is_db_ttl_;
|
||||
|
||||
// If true, the kvs are output with their insert/modify timestamp in a ttl db
|
||||
bool timestamp_;
|
||||
|
||||
/**
|
||||
* Map of options passed on the command-line.
|
||||
*/
|
||||
const map<string, string> options_;
|
||||
const map<string, string> option_map_;
|
||||
|
||||
/**
|
||||
* Flags passed on the command-line.
|
||||
@@ -169,7 +191,9 @@ protected:
|
||||
is_read_only_(is_read_only),
|
||||
is_key_hex_(false),
|
||||
is_value_hex_(false),
|
||||
options_(options),
|
||||
is_db_ttl_(false),
|
||||
timestamp_(false),
|
||||
option_map_(options),
|
||||
flags_(flags),
|
||||
valid_cmd_line_options_(valid_cmd_line_options) {
|
||||
|
||||
@@ -180,22 +204,30 @@ protected:
|
||||
|
||||
is_key_hex_ = IsKeyHex(options, flags);
|
||||
is_value_hex_ = IsValueHex(options, flags);
|
||||
is_db_ttl_ = IsFlagPresent(flags, ARG_TTL);
|
||||
timestamp_ = IsFlagPresent(flags, ARG_TIMESTAMP);
|
||||
}
|
||||
|
||||
void OpenDB() {
|
||||
leveldb::Options opt = PrepareOptionsForOpenDB();
|
||||
Options opt = PrepareOptionsForOpenDB();
|
||||
if (!exec_state_.IsNotStarted()) {
|
||||
return;
|
||||
}
|
||||
// Open the DB.
|
||||
leveldb::Status st;
|
||||
if (is_read_only_) {
|
||||
st = leveldb::DB::OpenForReadOnly(opt, db_path_, &db_);
|
||||
Status st;
|
||||
if (is_db_ttl_) {
|
||||
if (is_read_only_) {
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &db_, 0, true);
|
||||
} else {
|
||||
st = UtilityDB::OpenTtlDB(opt, db_path_, &db_);
|
||||
}
|
||||
} else if (is_read_only_) {
|
||||
st = DB::OpenForReadOnly(opt, db_path_, &db_);
|
||||
} else {
|
||||
st = leveldb::DB::Open(opt, db_path_, &db_);
|
||||
st = DB::Open(opt, db_path_, &db_);
|
||||
}
|
||||
if (!st.ok()) {
|
||||
std::string msg = st.ToString();
|
||||
string msg = st.ToString();
|
||||
exec_state_ = LDBCommandExecuteResult::FAILED(msg);
|
||||
}
|
||||
}
|
||||
@@ -254,6 +286,8 @@ protected:
|
||||
|
||||
private:
|
||||
|
||||
Options options_;
|
||||
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the key
|
||||
* should be input/output in hex.
|
||||
@@ -301,7 +335,7 @@ private:
|
||||
* Otherwise an exception is thrown.
|
||||
*/
|
||||
bool StringToBool(string val) {
|
||||
boost::algorithm::to_lower(val);
|
||||
std::transform(val.begin(), val.end(), val.begin(), ::tolower);
|
||||
if (val == "true") {
|
||||
return true;
|
||||
} else if (val == "false") {
|
||||
@@ -311,6 +345,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static LDBCommand* SelectCommand(
|
||||
const string& cmd,
|
||||
vector<string>& cmdParams,
|
||||
map<string, string>& option_map,
|
||||
vector<string>& flags
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
class CompactorCommand: public LDBCommand {
|
||||
@@ -353,6 +394,7 @@ private:
|
||||
|
||||
static const string ARG_COUNT_ONLY;
|
||||
static const string ARG_STATS;
|
||||
static const string ARG_TTL_BUCKET;
|
||||
};
|
||||
|
||||
class DBLoaderCommand: public LDBCommand {
|
||||
@@ -367,7 +409,7 @@ public:
|
||||
static void Help(string& ret);
|
||||
virtual void DoCommand();
|
||||
|
||||
virtual leveldb::Options PrepareOptionsForOpenDB();
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
private:
|
||||
bool create_if_missing_;
|
||||
@@ -380,6 +422,28 @@ private:
|
||||
static const string ARG_COMPACT;
|
||||
};
|
||||
|
||||
class ManifestDumpCommand: public LDBCommand {
|
||||
public:
|
||||
static string Name() { return "manifest_dump"; }
|
||||
|
||||
ManifestDumpCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags);
|
||||
|
||||
static void Help(string& ret);
|
||||
virtual void DoCommand();
|
||||
|
||||
virtual bool NoDBOpen() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool verbose_;
|
||||
string path_;
|
||||
|
||||
static const string ARG_VERBOSE;
|
||||
static const string ARG_PATH;
|
||||
};
|
||||
|
||||
class ReduceDBLevelsCommand : public LDBCommand {
|
||||
public:
|
||||
static string Name() { return "reduce_levels"; }
|
||||
@@ -387,7 +451,7 @@ public:
|
||||
ReduceDBLevelsCommand(const vector<string>& params,
|
||||
const map<string, string>& options, const vector<string>& flags);
|
||||
|
||||
virtual leveldb::Options PrepareOptionsForOpenDB();
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
virtual void DoCommand();
|
||||
|
||||
@@ -408,7 +472,7 @@ private:
|
||||
static const string ARG_NEW_LEVELS;
|
||||
static const string ARG_PRINT_OLD_LEVELS;
|
||||
|
||||
Status GetOldNumOfLevels(leveldb::Options& opt, int* levels);
|
||||
Status GetOldNumOfLevels(Options& opt, int* levels);
|
||||
};
|
||||
|
||||
class WALDumperCommand : public LDBCommand {
|
||||
@@ -478,7 +542,7 @@ public:
|
||||
|
||||
static void Help(string& ret);
|
||||
|
||||
virtual leveldb::Options PrepareOptionsForOpenDB();
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -532,7 +596,7 @@ public:
|
||||
|
||||
static void Help(string& ret);
|
||||
|
||||
virtual leveldb::Options PrepareOptionsForOpenDB();
|
||||
virtual Options PrepareOptionsForOpenDB();
|
||||
|
||||
private:
|
||||
string key_;
|
||||
@@ -561,5 +625,5 @@ private:
|
||||
static const char* DELETE_CMD;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
} // namespace leveldb
|
||||
#endif // STORAGE_LEVELDB_UTIL_LDB_CMD_H_
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2008-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "leveldb/ldb_tool.h"
|
||||
#include "util/ldb_cmd.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class LDBCommandRunner {
|
||||
public:
|
||||
|
||||
static void PrintHelp(const char* exec_name) {
|
||||
string ret;
|
||||
|
||||
ret.append("ldb - LevelDB Tool");
|
||||
ret.append("\n\n");
|
||||
ret.append("commands MUST specify --" + LDBCommand::ARG_DB +
|
||||
"=<full_path_to_db_directory> when necessary\n");
|
||||
ret.append("\n");
|
||||
ret.append("The following optional parameters control if keys/values are "
|
||||
"input/output as hex or as plain strings:\n");
|
||||
ret.append(" --" + LDBCommand::ARG_KEY_HEX +
|
||||
" : Keys are input/output as hex\n");
|
||||
ret.append(" --" + LDBCommand::ARG_VALUE_HEX +
|
||||
" : Values are input/output as hex\n");
|
||||
ret.append(" --" + LDBCommand::ARG_HEX +
|
||||
" : Both keys and values are input/output as hex\n");
|
||||
ret.append("\n");
|
||||
|
||||
ret.append("The following optional parameters control the database "
|
||||
"internals:\n");
|
||||
ret.append(" --" + LDBCommand::ARG_TTL +
|
||||
" with 'put','get','scan','dump','query','batchput'"
|
||||
" : DB supports ttl and value is internally timestamp-suffixed\n");
|
||||
ret.append(" --" + LDBCommand::ARG_BLOOM_BITS + "=<int,e.g.:14>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_COMPRESSION_TYPE +
|
||||
"=<no|snappy|zlib|bzip2>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_BLOCK_SIZE +
|
||||
"=<block_size_in_bytes>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_AUTO_COMPACTION + "=<true|false>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_WRITE_BUFFER_SIZE +
|
||||
"=<int,e.g.:4194304>\n");
|
||||
ret.append(" --" + LDBCommand::ARG_FILE_SIZE + "=<int,e.g.:2097152>\n");
|
||||
|
||||
ret.append("\n\n");
|
||||
ret.append("Data Access Commands:\n");
|
||||
PutCommand::Help(ret);
|
||||
GetCommand::Help(ret);
|
||||
BatchPutCommand::Help(ret);
|
||||
ScanCommand::Help(ret);
|
||||
DeleteCommand::Help(ret);
|
||||
DBQuerierCommand::Help(ret);
|
||||
ApproxSizeCommand::Help(ret);
|
||||
|
||||
ret.append("\n\n");
|
||||
ret.append("Admin Commands:\n");
|
||||
WALDumperCommand::Help(ret);
|
||||
CompactorCommand::Help(ret);
|
||||
ReduceDBLevelsCommand::Help(ret);
|
||||
DBDumperCommand::Help(ret);
|
||||
DBLoaderCommand::Help(ret);
|
||||
ManifestDumpCommand::Help(ret);
|
||||
|
||||
fprintf(stderr, "%s\n", ret.c_str());
|
||||
}
|
||||
|
||||
static void RunCommand(int argc, char** argv, Options options) {
|
||||
if (argc <= 2) {
|
||||
PrintHelp(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
LDBCommand* cmdObj = LDBCommand::InitFromCmdLineArgs(argc, argv, options);
|
||||
if (cmdObj == nullptr) {
|
||||
fprintf(stderr, "Unknown command\n");
|
||||
PrintHelp(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!cmdObj->ValidateCmdLineOptions()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cmdObj->Run();
|
||||
LDBCommandExecuteResult ret = cmdObj->GetExecuteState();
|
||||
fprintf(stderr, "%s\n", ret.ToString().c_str());
|
||||
delete cmdObj;
|
||||
|
||||
exit(ret.IsFailed());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
void LDBTool::Run(int argc, char** argv, Options options) {
|
||||
LDBCommandRunner::RunCommand(argc, argv, options);
|
||||
}
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Test for issue 178: a manual compaction causes deleted data to reappear.
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kNumKeys = 1100000;
|
||||
|
||||
std::string Key1(int i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "my_key_%d", i);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string Key2(int i) {
|
||||
return Key1(i) + "_xxx";
|
||||
}
|
||||
|
||||
class ManualCompactionTest { };
|
||||
|
||||
TEST(ManualCompactionTest, Test) {
|
||||
// Get rid of any state from an old run.
|
||||
std::string dbpath = leveldb::test::TmpDir() + "/leveldb_cbug_test";
|
||||
DestroyDB(dbpath, leveldb::Options());
|
||||
|
||||
// Open database. Disable compression since it affects the creation
|
||||
// of layers and the code below is trying to test against a very
|
||||
// specific scenario.
|
||||
leveldb::DB* db;
|
||||
leveldb::Options db_options;
|
||||
db_options.create_if_missing = true;
|
||||
db_options.compression = leveldb::kNoCompression;
|
||||
ASSERT_OK(leveldb::DB::Open(db_options, dbpath, &db));
|
||||
|
||||
// create first key range
|
||||
leveldb::WriteBatch batch;
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key1(i), "value for range 1 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// create second key range
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key2(i), "value for range 2 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// delete second key range
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Delete(Key2(i));
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// compact database
|
||||
std::string start_key = Key1(0);
|
||||
std::string end_key = Key1(kNumKeys - 1);
|
||||
leveldb::Slice least(start_key.data(), start_key.size());
|
||||
leveldb::Slice greatest(end_key.data(), end_key.size());
|
||||
|
||||
// commenting out the line below causes the example to work correctly
|
||||
db->CompactRange(&least, &greatest);
|
||||
|
||||
// count the keys
|
||||
leveldb::Iterator* iter = db->NewIterator(leveldb::ReadOptions());
|
||||
int num_keys = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
num_keys++;
|
||||
}
|
||||
delete iter;
|
||||
ASSERT_EQ(kNumKeys, num_keys) << "Bad number of keys";
|
||||
|
||||
// close database
|
||||
delete db;
|
||||
DestroyDB(dbpath, leveldb::Options());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return leveldb::test::RunAllTests();
|
||||
}
|
||||
+71
-13
@@ -6,15 +6,19 @@
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/compaction_filter.h"
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/filter_policy.h"
|
||||
#include "leveldb/cache.h"
|
||||
#include "leveldb/merge_operator.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Options::Options()
|
||||
: comparator(BytewiseComparator()),
|
||||
merge_operator(nullptr),
|
||||
compaction_filter(nullptr),
|
||||
create_if_missing(false),
|
||||
error_if_exists(false),
|
||||
paranoid_checks(false),
|
||||
@@ -22,6 +26,7 @@ Options::Options()
|
||||
info_log(nullptr),
|
||||
write_buffer_size(4<<20),
|
||||
max_write_buffer_number(2),
|
||||
min_write_buffer_number_to_merge(1),
|
||||
max_open_files(1000),
|
||||
block_size(4096),
|
||||
block_restart_interval(16),
|
||||
@@ -36,10 +41,10 @@ Options::Options()
|
||||
target_file_size_multiplier(1),
|
||||
max_bytes_for_level_base(10 * 1048576),
|
||||
max_bytes_for_level_multiplier(10),
|
||||
max_bytes_for_level_multiplier_additional(num_levels, 1),
|
||||
expanded_compaction_factor(25),
|
||||
source_compaction_factor(1),
|
||||
max_grandparent_overlap_factor(10),
|
||||
statistics(nullptr),
|
||||
disableDataSync(false),
|
||||
use_fsync(false),
|
||||
db_stats_log_interval(1800),
|
||||
@@ -55,20 +60,37 @@ Options::Options()
|
||||
max_manifest_file_size(std::numeric_limits<uint64_t>::max()),
|
||||
no_block_cache(false),
|
||||
table_cache_numshardbits(4),
|
||||
compaction_filter_args(nullptr),
|
||||
CompactionFilter(nullptr),
|
||||
disable_auto_compactions(false),
|
||||
WAL_ttl_seconds(0),
|
||||
manifest_preallocation_size(4 * 1024 * 1024),
|
||||
purge_redundant_kvs_while_flush(true) {
|
||||
|
||||
purge_redundant_kvs_while_flush(true),
|
||||
allow_os_buffer(true),
|
||||
allow_readahead(true),
|
||||
allow_readahead_compactions(true),
|
||||
allow_mmap_reads(false),
|
||||
allow_mmap_writes(true),
|
||||
is_fd_close_on_exec(true),
|
||||
skip_log_error_on_recovery(false),
|
||||
stats_dump_period_sec(3600),
|
||||
block_size_deviation (10),
|
||||
advise_random_on_open(true),
|
||||
access_hint_on_compaction_start(NORMAL),
|
||||
use_adaptive_mutex(false),
|
||||
bytes_per_sync(0) {
|
||||
}
|
||||
|
||||
static const char* const access_hints[] = {
|
||||
"NONE", "NORMAL", "SEQUENTIAL", "WILLNEED"
|
||||
};
|
||||
|
||||
void
|
||||
Options::Dump(Logger* log) const
|
||||
{
|
||||
Log(log," Options.comparator: %s", comparator->Name());
|
||||
Log(log," Options.create_if_missing: %d", create_if_missing);
|
||||
Log(log," Options.merge_operator: %s",
|
||||
merge_operator? merge_operator->Name() : "None");
|
||||
Log(log," Options.compaction_filter: %s",
|
||||
compaction_filter? compaction_filter->Name() : "None");
|
||||
Log(log," Options.error_if_exists: %d", error_if_exists);
|
||||
Log(log," Options.paranoid_checks: %d", paranoid_checks);
|
||||
Log(log," Options.env: %p", env);
|
||||
@@ -84,7 +106,7 @@ Options::Dump(Logger* log) const
|
||||
Log(log," Options.block_size: %zd", block_size);
|
||||
Log(log," Options.block_restart_interval: %d", block_restart_interval);
|
||||
if (!compression_per_level.empty()) {
|
||||
for (int i = 0; i < compression_per_level.size(); i++) {
|
||||
for (unsigned int i = 0; i < compression_per_level.size(); i++) {
|
||||
Log(log," Options.compression[%d]: %d",
|
||||
i, compression_per_level[i]);
|
||||
}
|
||||
@@ -103,6 +125,14 @@ Options::Dump(Logger* log) const
|
||||
Log(log," Options.keep_log_file_num: %ld", keep_log_file_num);
|
||||
Log(log," Options.db_stats_log_interval: %d",
|
||||
db_stats_log_interval);
|
||||
Log(log," Options.allow_os_buffer: %d", allow_os_buffer);
|
||||
Log(log," Options.allow_readahead: %d", allow_readahead);
|
||||
Log(log," Options.allow_mmap_reads: %d", allow_mmap_reads);
|
||||
Log(log," Options.allow_mmap_writes: %d", allow_mmap_writes);
|
||||
Log(log," Options.min_write_buffer_number_to_merge: %d",
|
||||
min_write_buffer_number_to_merge);
|
||||
Log(log," Options.allow_readahead_compactions: %d",
|
||||
allow_readahead_compactions);
|
||||
Log(log," Options.purge_redundant_kvs_while_flush: %d",
|
||||
purge_redundant_kvs_while_flush);
|
||||
Log(log," Options.compression_opts.window_bits: %d",
|
||||
@@ -127,6 +157,10 @@ Options::Dump(Logger* log) const
|
||||
max_bytes_for_level_base);
|
||||
Log(log," Options.max_bytes_for_level_multiplier: %d",
|
||||
max_bytes_for_level_multiplier);
|
||||
for (int i = 0; i < num_levels; i++) {
|
||||
Log(log,"Options.max_bytes_for_level_multiplier_addtl[%d]: %d",
|
||||
i, max_bytes_for_level_multiplier_additional[i]);
|
||||
}
|
||||
Log(log," Options.expanded_compaction_factor: %d",
|
||||
expanded_compaction_factor);
|
||||
Log(log," Options.source_compaction_factor: %d",
|
||||
@@ -140,7 +174,7 @@ Options::Dump(Logger* log) const
|
||||
Log(log," Options.no_block_cache: %d",
|
||||
no_block_cache);
|
||||
Log(log," Options.table_cache_numshardbits: %d",
|
||||
table_cache_numshardbits);
|
||||
table_cache_numshardbits);
|
||||
Log(log," Options.delete_obsolete_files_period_micros: %ld",
|
||||
delete_obsolete_files_period_micros);
|
||||
Log(log," Options.max_background_compactions: %d",
|
||||
@@ -149,16 +183,40 @@ Options::Dump(Logger* log) const
|
||||
rate_limit);
|
||||
Log(log," Options.rate_limit_delay_milliseconds: %d",
|
||||
rate_limit_delay_milliseconds);
|
||||
Log(log," Options.compaction_filter_args: %p",
|
||||
compaction_filter_args);
|
||||
Log(log," Options.CompactionFilter: %p",
|
||||
CompactionFilter);
|
||||
Log(log," Options.disable_auto_compactions: %d",
|
||||
disable_auto_compactions);
|
||||
Log(log," Options.WAL_ttl_seconds: %ld",
|
||||
WAL_ttl_seconds);
|
||||
Log(log," Options.manifest_preallocation_size: %ld",
|
||||
manifest_preallocation_size);
|
||||
Log(log," Options.purge_redundant_kvs_while_flush: %d",
|
||||
purge_redundant_kvs_while_flush);
|
||||
Log(log," Options.allow_os_buffer: %d",
|
||||
allow_os_buffer);
|
||||
Log(log," Options.allow_readahead: %d",
|
||||
allow_readahead);
|
||||
Log(log," Options.allow_readahead_compactions: %d",
|
||||
allow_readahead_compactions);
|
||||
Log(log," Options.allow_mmap_reads: %d",
|
||||
allow_mmap_reads);
|
||||
Log(log," Options.allow_mmap_writes: %d",
|
||||
allow_mmap_writes);
|
||||
Log(log," Options.is_fd_close_on_exec: %d",
|
||||
is_fd_close_on_exec);
|
||||
Log(log," Options.skip_log_error_on_recovery: %d",
|
||||
skip_log_error_on_recovery);
|
||||
Log(log," Options.stats_dump_period_sec: %d",
|
||||
stats_dump_period_sec);
|
||||
Log(log," Options.block_size_deviation: %d",
|
||||
block_size_deviation);
|
||||
Log(log," Options.advise_random_on_open: %d",
|
||||
advise_random_on_open);
|
||||
Log(log," Options.access_hint_on_compaction_start: %s",
|
||||
access_hints[access_hint_on_compaction_start]);
|
||||
Log(log," Options.use_adaptive_mutex: %d",
|
||||
use_adaptive_mutex);
|
||||
Log(log," Options.bytes_per_sync: %ld",
|
||||
bytes_per_sync);
|
||||
} // Options::Dump
|
||||
|
||||
//
|
||||
|
||||
+12
-10
@@ -18,6 +18,7 @@
|
||||
#include <linux/falloc.h>
|
||||
#endif
|
||||
#include "leveldb/env.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -27,8 +28,7 @@ class PosixLogger : public Logger {
|
||||
private:
|
||||
FILE* file_;
|
||||
uint64_t (*gettid_)(); // Return the thread id for the current thread
|
||||
|
||||
size_t log_size_;
|
||||
std::atomic_size_t log_size_;
|
||||
int fd_;
|
||||
public:
|
||||
PosixLogger(FILE* f, uint64_t (*gettid)()) :
|
||||
@@ -94,16 +94,17 @@ class PosixLogger : public Logger {
|
||||
}
|
||||
|
||||
assert(p <= limit);
|
||||
const size_t write_size = p - base;
|
||||
|
||||
#ifdef OS_LINUX
|
||||
// If this write would cross a boundary of kDebugLogChunkSize
|
||||
// space, pre-allocate more space to avoid overly large
|
||||
// allocations from filesystem allocsize options.
|
||||
const size_t write_size = p - base;
|
||||
const size_t log_size = log_size_;
|
||||
const int last_allocation_chunk =
|
||||
((kDebugLogChunkSize - 1 + log_size_) / kDebugLogChunkSize);
|
||||
((kDebugLogChunkSize - 1 + log_size) / kDebugLogChunkSize);
|
||||
const int desired_allocation_chunk =
|
||||
((kDebugLogChunkSize - 1 + log_size_ + write_size) /
|
||||
((kDebugLogChunkSize - 1 + log_size + write_size) /
|
||||
kDebugLogChunkSize);
|
||||
if (last_allocation_chunk != desired_allocation_chunk) {
|
||||
fallocate(fd_, FALLOC_FL_KEEP_SIZE, 0,
|
||||
@@ -111,11 +112,12 @@ class PosixLogger : public Logger {
|
||||
}
|
||||
#endif
|
||||
|
||||
fwrite(base, 1, write_size, file_);
|
||||
fflush(file_);
|
||||
|
||||
log_size_ += write_size;
|
||||
|
||||
size_t sz = fwrite(base, 1, write_size, file_);
|
||||
assert(sz == write_size);
|
||||
if (sz > 0) {
|
||||
fflush(file_);
|
||||
log_size_ += write_size;
|
||||
}
|
||||
if (base != buffer) {
|
||||
delete[] base;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
#define STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
|
||||
#include <random>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace leveldb {
|
||||
@@ -54,6 +55,35 @@ class Random {
|
||||
}
|
||||
};
|
||||
|
||||
// A simple 64bit random number generator based on std::mt19937_64
|
||||
class Random64 {
|
||||
private:
|
||||
std::mt19937_64 generator_;
|
||||
|
||||
public:
|
||||
explicit Random64(uint64_t s) : generator_(s) { }
|
||||
|
||||
// Generates the next random number
|
||||
uint64_t Next() { return generator_(); }
|
||||
|
||||
// Returns a uniformly distributed value in the range [0..n-1]
|
||||
// REQUIRES: n > 0
|
||||
uint64_t Uniform(uint64_t n) {
|
||||
return std::uniform_int_distribution<uint64_t>(0, n - 1)(generator_);
|
||||
}
|
||||
|
||||
// Randomly returns true ~"1/n" of the time, and false otherwise.
|
||||
// REQUIRES: n > 0
|
||||
bool OneIn(uint64_t n) { return Uniform(n) == 0; }
|
||||
|
||||
// Skewed: pick "base" uniformly from range [0,max_log] and then
|
||||
// return "base" random bits. The effect is to pick a number in the
|
||||
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
|
||||
uint64_t Skewed(int max_log) {
|
||||
return Uniform(1 << Uniform(max_log + 1));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "util/stack_trace.h"
|
||||
#include <assert.h>
|
||||
|
||||
void f0() {
|
||||
char *p = nullptr;
|
||||
*p = 10; /* SIGSEGV here!! */
|
||||
}
|
||||
|
||||
void f1() {
|
||||
f0();
|
||||
}
|
||||
|
||||
void f2() {
|
||||
f1();
|
||||
}
|
||||
|
||||
void f3() {
|
||||
f2();
|
||||
}
|
||||
|
||||
int main() {
|
||||
leveldb::InstallStackTraceHandler();
|
||||
|
||||
f3();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef STACK_TRACE_H
|
||||
#define STACK_TRACE_H
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Install a signal handler to print callstack on the following signals:
|
||||
// SIGILL SIGSEGV SIGBUS SIGABRT
|
||||
// Currently supports linux only. No-op otherwise.
|
||||
void InstallStackTraceHandler();
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif
|
||||
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user