mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 767c2820c9 | |||
| 8aca71474a | |||
| 73ed80f10a | |||
| 355b11c507 | |||
| 52b25c5ed8 | |||
| ddb7926a41 | |||
| 7a9dd5f214 | |||
| 4f5ccfd179 | |||
| bae495740d | |||
| 1ec2d1c69d | |||
| c4e90c79ed | |||
| 5363eb8ad4 | |||
| 167738256f | |||
| d72313a7fa | |||
| df2701373d | |||
| edd47c5104 | |||
| 0f0076ed5a | |||
| 3525aac9e5 | |||
| 8c265c08f1 | |||
| 4bff7a8a87 | |||
| b982e65f8b | |||
| 200e4b4a72 | |||
| 4f18bfe376 | |||
| e6e259b8ab | |||
| cadc1adffa | |||
| 2d02ec6533 | |||
| 4d913cfbc3 | |||
| 983c93d731 | |||
| a52a4e0952 | |||
| 9202d9b625 | |||
| c83b085770 | |||
| 578cf84ddf | |||
| c4b3817d7c | |||
| 88a1691a1e | |||
| e2d3101cf1 | |||
| 77db08f27b | |||
| 80f409ea37 | |||
| f43c8262c2 | |||
| ee5a51e6ce | |||
| 3e701a654f | |||
| 0365eaf12e | |||
| a0191c9dfe | |||
| 99d3eed2fd | |||
| e72b02e3c2 | |||
| b92a19a431 | |||
| 5d870717ae | |||
| 166cc5b456 | |||
| 1a6c1a5ddd | |||
| 2fa0a993b8 | |||
| 08314321fa | |||
| 457bae6911 | |||
| 552c49f0f4 | |||
| fd27001072 | |||
| df9069d23f | |||
| 462796697c | |||
| 91ddd587cc | |||
| 8cb7ad83c3 | |||
| 593bb2c40b | |||
| 388d2054c7 | |||
| f29c62fc6f | |||
| fa1b62cabd | |||
| 9899b12780 | |||
| ab3e566699 | |||
| 663627abf3 | |||
| 8e41e54e1b | |||
| 84cbafa5ce | |||
| 05dd018353 | |||
| f068d2a94d | |||
| 3a1cf1281b | |||
| 5fe176d8f6 | |||
| 260842a7f8 | |||
| d271cc5b4e | |||
| 5a1b41cee1 | |||
| e87973cde1 | |||
| 29eef1f87d | |||
| b830bb937f | |||
| c92425e0ba | |||
| cf0b773a29 | |||
| 036e323f7a |
@@ -4,10 +4,12 @@
|
||||
|
||||
### Public API changes
|
||||
* Replaced ColumnFamilyOptions::table_properties_collectors with ColumnFamilyOptions::table_properties_collector_factories
|
||||
* Add two paramters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing.
|
||||
|
||||
### New Features
|
||||
* Hash index for block-based table will be materialized and reconstructed more efficiently. Previously hash index is constructed by scanning the whole table during every table open.
|
||||
* FIFO compaction style
|
||||
* Add AdaptiveTableFactory, which is used to convert from a DB of PlainTable to BlockBasedTabe, or vise versa. It can be created using NewAdaptiveTableFactory()
|
||||
|
||||
## 3.0.0 (05/05/2014)
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ depend on gflags. You will need to have gflags installed to run `make all`.
|
||||
* run `brew tap homebrew/dupes; brew install gcc47 --use-llvm` to install gcc 4.7 (or higher).
|
||||
* Install zlib, bzip2 and snappy libraries for compression.
|
||||
* Install gflags. We have included a script
|
||||
`build_tools/mac-install-gflags.sh`, which should automatically install it.
|
||||
`build_tools/mac-install-gflags.sh`, which should automatically install it (execute this file instead of runing using "source" command).
|
||||
If you installed gflags by other means (for example, `brew install gflags`),
|
||||
please set `LIBRARY_PATH` and `CPATH` accordingly.
|
||||
* Please note that some of the optimizations/features are disabled in OSX.
|
||||
|
||||
@@ -49,7 +49,6 @@ else
|
||||
PLATFORM_CCFLAGS += $(JEMALLOC_INCLUDE) -DHAVE_JEMALLOC
|
||||
endif
|
||||
|
||||
WARNING_FLAGS = -Wall -Werror
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual
|
||||
|
||||
@@ -146,7 +145,7 @@ SHARED = $(SHARED1)
|
||||
else
|
||||
# Update db.h if you change these.
|
||||
SHARED_MAJOR = 3
|
||||
SHARED_MINOR = 0
|
||||
SHARED_MINOR = 2
|
||||
SHARED1 = ${LIBNAME}.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1).$(SHARED_MAJOR)
|
||||
SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
|
||||
@@ -16,8 +16,7 @@ single database.
|
||||
|
||||
Start with example usage here: https://github.com/facebook/rocksdb/tree/master/examples
|
||||
|
||||
See [doc/index.html](https://github.com/facebook/rocksdb/blob/master/doc/index.html) and
|
||||
[github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
|
||||
See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
|
||||
|
||||
The public interface is in `include/`. Callers should not include or
|
||||
rely on the details of any other header files in this package. Those
|
||||
|
||||
@@ -47,13 +47,7 @@ COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX"
|
||||
if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
FBCODE_BUILD="true"
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
CENTOS_VERSION=`rpm -q --qf "%{VERSION}" \
|
||||
$(rpm -q --whatprovides redhat-release)`
|
||||
if [ "$CENTOS_VERSION" = "6" ]; then
|
||||
source "$PWD/build_tools/fbcode.gcc481.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode.gcc471.sh"
|
||||
fi
|
||||
source $PWD/build_tools/fbcode_config.sh
|
||||
else
|
||||
source "$PWD/build_tools/fbcode.clang31.sh"
|
||||
fi
|
||||
@@ -237,7 +231,7 @@ EOF
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
# uses jemalloc
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=" -msse -msse4.2 "
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $LZ4_INCLUDE $GFLAGS_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/tools/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -283,6 +283,76 @@ make release
|
||||
--value_size=10 \
|
||||
--threads=16 > ${STAT_FILE}.memtablefillreadrandom
|
||||
|
||||
common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--num_levels=6 \
|
||||
--key_size=20 \
|
||||
--prefix_size=12 \
|
||||
--keys_per_prefix=10 \
|
||||
--value_size=100 \
|
||||
--compression_type=none \
|
||||
--compression_ratio=1 \
|
||||
--disable_seek_compaction=1 \
|
||||
--hard_rate_limit=2 \
|
||||
--write_buffer_size=134217728 \
|
||||
--max_write_buffer_number=4 \
|
||||
--level0_file_num_compaction_trigger=8 \
|
||||
--level0_slowdown_writes_trigger=16 \
|
||||
--level0_stop_writes_trigger=24 \
|
||||
--target_file_size_base=134217728 \
|
||||
--max_bytes_for_level_base=1073741824 \
|
||||
--disable_wal=0 \
|
||||
--wal_dir=/dev/shm/rocksdb \
|
||||
--sync=0 \
|
||||
--disable_data_sync=1 \
|
||||
--verify_checksum=1 \
|
||||
--delete_obsolete_files_period_micros=314572800 \
|
||||
--max_grandparent_overlap_factor=10 \
|
||||
--use_plain_table=1 \
|
||||
--open_files=-1 \
|
||||
--mmap_read=1 \
|
||||
--mmap_write=0 \
|
||||
--memtablerep=prefix_hash \
|
||||
--bloom_bits=10 \
|
||||
--bloom_locality=1 \
|
||||
--perf_level=0"
|
||||
|
||||
# prepare a in-memory DB with 50M keys, total DB size is ~6G
|
||||
./db_bench \
|
||||
$common_in_mem_args \
|
||||
--statistics=0 \
|
||||
--max_background_compactions=16 \
|
||||
--max_background_flushes=16 \
|
||||
--benchmarks=filluniquerandom \
|
||||
--use_existing_db=0 \
|
||||
--num=52428800 \
|
||||
--threads=1 > /dev/null
|
||||
|
||||
# Readwhilewriting
|
||||
./db_bench \
|
||||
$common_in_mem_args \
|
||||
--statistics=1 \
|
||||
--max_background_compactions=4 \
|
||||
--max_background_flushes=0 \
|
||||
--benchmarks=readwhilewriting\
|
||||
--use_existing_db=1 \
|
||||
--duration=600 \
|
||||
--threads=32 \
|
||||
--writes_per_second=81920 > ${STAT_FILE}.readwhilewriting_in_ram
|
||||
|
||||
# Seekrandomwhilewriting
|
||||
./db_bench \
|
||||
$common_in_mem_args \
|
||||
--statistics=1 \
|
||||
--max_background_compactions=4 \
|
||||
--max_background_flushes=0 \
|
||||
--benchmarks=seekrandomwhilewriting \
|
||||
--use_existing_db=1 \
|
||||
--use_tailing_iterator=1 \
|
||||
--duration=600 \
|
||||
--threads=32 \
|
||||
--writes_per_second=81920 > ${STAT_FILE}.seekwhilewriting_in_ram
|
||||
|
||||
|
||||
# send data to ods
|
||||
function send_to_ods {
|
||||
key="$1"
|
||||
@@ -308,9 +378,9 @@ function send_benchmark_to_ods {
|
||||
file="$3"
|
||||
|
||||
QPS=$(grep $bench $file | awk '{print $5}')
|
||||
P50_MICROS=$(grep $bench $file -A 4 | tail -n1 | awk '{print $3}' )
|
||||
P75_MICROS=$(grep $bench $file -A 4 | tail -n1 | awk '{print $5}' )
|
||||
P99_MICROS=$(grep $bench $file -A 4 | tail -n1 | awk '{print $7}' )
|
||||
P50_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $3}' )
|
||||
P75_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $5}' )
|
||||
P99_MICROS=$(grep $bench $file -A 6 | grep "Percentiles" | awk '{print $7}' )
|
||||
|
||||
send_to_ods rocksdb.build.$bench_key.qps $QPS
|
||||
send_to_ods rocksdb.build.$bench_key.p50_micros $P50_MICROS
|
||||
@@ -328,3 +398,5 @@ send_benchmark_to_ods readrandom readrandom_fillunique_random $STAT_FILE.readran
|
||||
send_benchmark_to_ods fillrandom memtablefillrandom $STAT_FILE.memtablefillreadrandom
|
||||
send_benchmark_to_ods readrandom memtablereadrandom $STAT_FILE.memtablefillreadrandom
|
||||
send_benchmark_to_ods readwhilewriting readwhilewriting $STAT_FILE.readwhilewriting
|
||||
send_benchmark_to_ods readwhilewriting readwhilewriting_in_ram ${STAT_FILE}.readwhilewriting_in_ram
|
||||
send_benchmark_to_ods seekrandomwhilewriting seekwhilewriting_in_ram ${STAT_FILE}.seekwhilewriting_in_ram
|
||||
|
||||
+6
-6
@@ -42,7 +42,7 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
const SequenceNumber earliest_seqno_in_memtable,
|
||||
const CompressionType compression) {
|
||||
Status s;
|
||||
meta->file_size = 0;
|
||||
meta->fd.file_size = 0;
|
||||
meta->smallest_seqno = meta->largest_seqno = 0;
|
||||
iter->SeekToFirst();
|
||||
|
||||
@@ -54,7 +54,7 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
purge = false;
|
||||
}
|
||||
|
||||
std::string fname = TableFileName(dbname, meta->number);
|
||||
std::string fname = TableFileName(dbname, meta->fd.GetNumber());
|
||||
if (iter->Valid()) {
|
||||
unique_ptr<WritableFile> file;
|
||||
s = env->NewWritableFile(fname, &file, soptions);
|
||||
@@ -177,8 +177,8 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
if (s.ok()) {
|
||||
s = builder->Finish();
|
||||
if (s.ok()) {
|
||||
meta->file_size = builder->FileSize();
|
||||
assert(meta->file_size > 0);
|
||||
meta->fd.file_size = builder->FileSize();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
}
|
||||
} else {
|
||||
builder->Abandon();
|
||||
@@ -202,7 +202,7 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
if (s.ok()) {
|
||||
// Verify that the table is usable
|
||||
Iterator* it = table_cache->NewIterator(ReadOptions(), soptions,
|
||||
internal_comparator, *meta);
|
||||
internal_comparator, meta->fd);
|
||||
s = it->status();
|
||||
delete it;
|
||||
}
|
||||
@@ -213,7 +213,7 @@ Status BuildTable(const std::string& dbname, Env* env, const Options& options,
|
||||
s = iter->status();
|
||||
}
|
||||
|
||||
if (s.ok() && meta->file_size > 0) {
|
||||
if (s.ok() && meta->fd.GetFileSize() > 0) {
|
||||
// Keep it
|
||||
} else {
|
||||
env->DeleteFile(fname);
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ extern TableBuilder* NewTableBuilder(
|
||||
WritableFile* file, CompressionType compression_type);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to meta->number. On success, the rest of
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
// *meta will be filled with metadata about the generated table.
|
||||
// If no data is present in *iter, meta->file_size will be set to
|
||||
// zero, and no Table file will be produced.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -30,6 +31,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
using rocksdb::Cache;
|
||||
using rocksdb::CompactionFilter;
|
||||
using rocksdb::Comparator;
|
||||
using rocksdb::CompressionType;
|
||||
using rocksdb::DB;
|
||||
@@ -77,6 +79,49 @@ struct rocksdb_logger_t { shared_ptr<Logger> rep; };
|
||||
struct rocksdb_cache_t { shared_ptr<Cache> rep; };
|
||||
struct rocksdb_livefiles_t { std::vector<LiveFileMetaData> rep; };
|
||||
|
||||
struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
unsigned char (*filter_)(
|
||||
void*,
|
||||
int level,
|
||||
const char* key, size_t key_length,
|
||||
const char* existing_value, size_t value_length,
|
||||
char** new_value, size_t *new_value_length,
|
||||
unsigned char* value_changed);
|
||||
const char* (*name_)(void*);
|
||||
|
||||
virtual ~rocksdb_compactionfilter_t() {
|
||||
(*destructor_)(state_);
|
||||
}
|
||||
|
||||
virtual bool Filter(
|
||||
int level,
|
||||
const Slice& key,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const {
|
||||
char* c_new_value = NULL;
|
||||
size_t new_value_length = 0;
|
||||
unsigned char c_value_changed = 0;
|
||||
unsigned char result = (*filter_)(
|
||||
state_,
|
||||
level,
|
||||
key.data(), key.size(),
|
||||
existing_value.data(), existing_value.size(),
|
||||
&c_new_value, &new_value_length, &c_value_changed);
|
||||
if (c_value_changed) {
|
||||
new_value->assign(c_new_value, new_value_length);
|
||||
*value_changed = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual const char* Name() const {
|
||||
return (*name_)(state_);
|
||||
}
|
||||
};
|
||||
|
||||
struct rocksdb_comparator_t : public Comparator {
|
||||
void* state_;
|
||||
void (*destructor_)(void*);
|
||||
@@ -631,6 +676,12 @@ void rocksdb_options_destroy(rocksdb_options_t* options) {
|
||||
delete options;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_compactionfilter_t* filter) {
|
||||
opt->rep.compaction_filter = filter;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_comparator_t* cmp) {
|
||||
@@ -1119,10 +1170,32 @@ DB::GetUpdatesSince
|
||||
DB::GetDbIdentity
|
||||
DB::RunManualCompaction
|
||||
custom cache
|
||||
compaction_filter
|
||||
table_properties_collectors
|
||||
*/
|
||||
|
||||
rocksdb_compactionfilter_t* rocksdb_compactionfilter_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
unsigned char (*filter)(
|
||||
void*,
|
||||
int level,
|
||||
const char* key, size_t key_length,
|
||||
const char* existing_value, size_t value_length,
|
||||
char** new_value, size_t *new_value_length,
|
||||
unsigned char* value_changed),
|
||||
const char* (*name)(void*)) {
|
||||
rocksdb_compactionfilter_t* result = new rocksdb_compactionfilter_t;
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->filter_ = filter;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilter_destroy(rocksdb_compactionfilter_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
rocksdb_comparator_t* rocksdb_comparator_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
|
||||
+53
@@ -154,6 +154,28 @@ static unsigned char FilterKeyMatch(
|
||||
return fake_filter_result;
|
||||
}
|
||||
|
||||
// Custom compaction filter
|
||||
static void CFilterDestroy(void* arg) {}
|
||||
static const char* CFilterName(void* arg) { return "foo"; }
|
||||
static unsigned char CFilterFilter(void* arg, int level, const char* key,
|
||||
size_t key_length,
|
||||
const char* existing_value,
|
||||
size_t value_length, char** new_value,
|
||||
size_t* new_value_length,
|
||||
unsigned char* value_changed) {
|
||||
if (key_length == 3) {
|
||||
if (memcmp(key, "bar", key_length) == 0) {
|
||||
return 1;
|
||||
} else if (memcmp(key, "baz", key_length) == 0) {
|
||||
*value_changed = 1;
|
||||
*new_value = "newbazvalue";
|
||||
*new_value_length = 11;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Custom merge operator
|
||||
static void MergeOperatorDestroy(void* arg) { }
|
||||
static const char* MergeOperatorName(void* arg) {
|
||||
@@ -407,6 +429,37 @@ int main(int argc, char** argv) {
|
||||
rocksdb_filterpolicy_destroy(policy);
|
||||
}
|
||||
|
||||
StartPhase("compaction_filter");
|
||||
{
|
||||
rocksdb_compactionfilter_t* cfilter;
|
||||
cfilter = rocksdb_compactionfilter_create(NULL, CFilterDestroy,
|
||||
CFilterFilter, CFilterName);
|
||||
// Create new database
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
rocksdb_options_set_compaction_filter(options, cfilter);
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo", 3, "foovalue", 8, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "foo", "foovalue");
|
||||
rocksdb_put(db, woptions, "bar", 3, "barvalue", 8, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "bar", "barvalue");
|
||||
rocksdb_put(db, woptions, "baz", 3, "bazvalue", 8, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "baz", "bazvalue");
|
||||
|
||||
// Force compaction
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
// should have filtered bar, but not foo
|
||||
CheckGet(db, roptions, "foo", "foovalue");
|
||||
CheckGet(db, roptions, "bar", NULL);
|
||||
CheckGet(db, roptions, "baz", "newbazvalue");
|
||||
|
||||
rocksdb_compactionfilter_destroy(cfilter);
|
||||
}
|
||||
|
||||
StartPhase("merge_operator");
|
||||
{
|
||||
rocksdb_mergeoperator_t* merge_operator;
|
||||
|
||||
@@ -92,6 +92,9 @@ ColumnFamilyOptions SanitizeOptions(const InternalKeyComparator* icmp,
|
||||
if (result.soft_rate_limit > result.hard_rate_limit) {
|
||||
result.soft_rate_limit = result.hard_rate_limit;
|
||||
}
|
||||
if (result.max_write_buffer_number < 2) {
|
||||
result.max_write_buffer_number = 2;
|
||||
}
|
||||
if (!result.prefix_extractor) {
|
||||
assert(result.memtable_factory);
|
||||
Slice name = result.memtable_factory->Name();
|
||||
@@ -506,6 +509,10 @@ void ColumnFamilySet::UpdateMaxColumnFamily(uint32_t new_max_column_family) {
|
||||
max_column_family_ = std::max(new_max_column_family, max_column_family_);
|
||||
}
|
||||
|
||||
size_t ColumnFamilySet::NumberOfColumnFamilies() const {
|
||||
return column_families_.size();
|
||||
}
|
||||
|
||||
// under a DB mutex
|
||||
ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
|
||||
+4
-3
@@ -180,7 +180,7 @@ class ColumnFamilyData {
|
||||
void SetCurrent(Version* current);
|
||||
void CreateNewMemtable();
|
||||
|
||||
TableCache* table_cache() { return table_cache_.get(); }
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
Compaction* PickCompaction(LogBuffer* log_buffer);
|
||||
@@ -302,8 +302,8 @@ class ColumnFamilyData {
|
||||
// family might get dropped when the DB mutex is released
|
||||
// * GetDefault() -- thread safe
|
||||
// * GetColumnFamily() -- either inside of DB mutex or call Lock() <-> Unlock()
|
||||
// * GetNextColumnFamilyID(), GetMaxColumnFamily(), UpdateMaxColumnFamily() --
|
||||
// inside of DB mutex
|
||||
// * GetNextColumnFamilyID(), GetMaxColumnFamily(), UpdateMaxColumnFamily(),
|
||||
// NumberOfColumnFamilies -- inside of DB mutex
|
||||
class ColumnFamilySet {
|
||||
public:
|
||||
// ColumnFamilySet supports iteration
|
||||
@@ -342,6 +342,7 @@ class ColumnFamilySet {
|
||||
uint32_t GetNextColumnFamilyID();
|
||||
uint32_t GetMaxColumnFamily();
|
||||
void UpdateMaxColumnFamily(uint32_t new_max_column_family);
|
||||
size_t NumberOfColumnFamilies() const;
|
||||
|
||||
ColumnFamilyData* CreateColumnFamily(const std::string& name, uint32_t id,
|
||||
Version* dummy_version,
|
||||
|
||||
@@ -970,6 +970,15 @@ TEST(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
Status s = TryOpen({"one", "two"});
|
||||
ASSERT_TRUE(!s.ok());
|
||||
db_options_.create_missing_column_families = true;
|
||||
s = TryOpen({"default", "one", "two"});
|
||||
ASSERT_TRUE(s.ok());
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+22
-6
@@ -21,7 +21,7 @@ namespace rocksdb {
|
||||
static uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->file_size;
|
||||
sum += files[i]->fd.GetFileSize();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ bool Compaction::IsDeletionCompaction() const { return deletion_compaction_; }
|
||||
void Compaction::AddInputDeletions(VersionEdit* edit) {
|
||||
for (int which = 0; which < 2; which++) {
|
||||
for (size_t i = 0; i < inputs_[which].size(); i++) {
|
||||
edit->DeleteFile(level_ + which, inputs_[which][i]->number);
|
||||
edit->DeleteFile(level_ + which, inputs_[which][i]->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ bool Compaction::ShouldStopBefore(const Slice& internal_key) {
|
||||
icmp->Compare(internal_key,
|
||||
grandparents_[grandparent_index_]->largest.Encode()) > 0) {
|
||||
if (seen_key_) {
|
||||
overlapped_bytes_ += grandparents_[grandparent_index_]->file_size;
|
||||
overlapped_bytes_ += grandparents_[grandparent_index_]->fd.GetFileSize();
|
||||
}
|
||||
assert(grandparent_index_ + 1 >= grandparents_.size() ||
|
||||
icmp->Compare(grandparents_[grandparent_index_]->largest.Encode(),
|
||||
@@ -212,9 +212,9 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int sz = len - write;
|
||||
int ret;
|
||||
char sztxt[16];
|
||||
AppendHumanBytes(files.at(i)->file_size, sztxt, 16);
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ", files.at(i)->number,
|
||||
sztxt);
|
||||
AppendHumanBytes(files.at(i)->fd.GetFileSize(), sztxt, 16);
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt);
|
||||
if (ret < 0 || ret >= sz) break;
|
||||
write += ret;
|
||||
}
|
||||
@@ -250,4 +250,20 @@ void Compaction::Summary(char* output, int len) {
|
||||
snprintf(output + write, len - write, "]");
|
||||
}
|
||||
|
||||
uint64_t Compaction::OutputFilePreallocationSize() {
|
||||
uint64_t preallocation_size = 0;
|
||||
|
||||
if (cfd_->options()->compaction_style == kCompactionStyleLevel) {
|
||||
preallocation_size =
|
||||
cfd_->compaction_picker()->MaxFileSizeForLevel(output_level());
|
||||
} else {
|
||||
for (const auto& f : inputs_[0]) {
|
||||
preallocation_size += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
return preallocation_size * 1.1;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -91,6 +91,11 @@ class Compaction {
|
||||
// Was this compaction triggered manually by the client?
|
||||
bool IsManualCompaction() { return is_manual_compaction_; }
|
||||
|
||||
// Returns a number of byte that the output file should be preallocated to
|
||||
// In level compaction, that is max_file_size_. In universal compaction, that
|
||||
// is the sum of all input file sizes
|
||||
uint64_t OutputFilePreallocationSize();
|
||||
|
||||
private:
|
||||
friend class CompactionPicker;
|
||||
friend class UniversalCompactionPicker;
|
||||
|
||||
+48
-40
@@ -22,7 +22,7 @@ namespace {
|
||||
uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->file_size;
|
||||
sum += files[i]->fd.GetFileSize();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ void CompactionPicker::SizeBeingCompacted(std::vector<uint64_t>& sizes) {
|
||||
for (auto c : compactions_in_progress_[level]) {
|
||||
assert(c->level() == level);
|
||||
for (int i = 0; i < c->num_input_files(0); i++) {
|
||||
total += c->input(0,i)->file_size;
|
||||
total += c->input(0, i)->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
sizes[level] = total;
|
||||
@@ -335,7 +335,7 @@ Compaction* CompactionPicker::CompactRange(Version* version, int input_level,
|
||||
MaxFileSizeForLevel(input_level) * options_->source_compaction_factor;
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i + 1 < inputs.size(); ++i) {
|
||||
uint64_t s = inputs[i]->file_size;
|
||||
uint64_t s = inputs[i]->fd.GetFileSize();
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
**compaction_end = inputs[i + 1]->smallest;
|
||||
@@ -508,10 +508,11 @@ Compaction* LevelCompactionPicker::PickCompactionBySize(Version* version,
|
||||
FileMetaData* f = c->input_version_->files_[level][index];
|
||||
|
||||
// check to verify files are arranged in descending size
|
||||
assert((i == file_size.size() - 1) ||
|
||||
(i >= Version::number_of_files_to_sort_ - 1) ||
|
||||
(f->file_size >=
|
||||
c->input_version_->files_[level][file_size[i + 1]]->file_size));
|
||||
assert(
|
||||
(i == file_size.size() - 1) ||
|
||||
(i >= Version::number_of_files_to_sort_ - 1) ||
|
||||
(f->fd.GetFileSize() >=
|
||||
c->input_version_->files_[level][file_size[i + 1]]->fd.GetFileSize()));
|
||||
|
||||
// do not pick a file to compact if it is being compacted
|
||||
// from n-1 level.
|
||||
@@ -680,19 +681,21 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
candidate_count = 1;
|
||||
break;
|
||||
}
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: file %lu[%d] being compacted, skipping",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number, loop);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: file %lu[%d] being compacted, skipping",
|
||||
version->cfd_->GetName().c_str(),
|
||||
(unsigned long)f->fd.GetNumber(), loop);
|
||||
f = nullptr;
|
||||
}
|
||||
|
||||
// This file is not being compacted. Consider it as the
|
||||
// first candidate to be compacted.
|
||||
uint64_t candidate_size = f != nullptr? f->file_size : 0;
|
||||
uint64_t candidate_size = f != nullptr ? f->fd.GetFileSize() : 0;
|
||||
if (f != nullptr) {
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: Possible candidate file %lu[%d].",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number, loop);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: Possible candidate file %lu[%d].",
|
||||
version->cfd_->GetName().c_str(),
|
||||
(unsigned long)f->fd.GetNumber(), loop);
|
||||
}
|
||||
|
||||
// Check if the suceeding files need compaction.
|
||||
@@ -711,13 +714,13 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
// kCompactionStopStyleSimilarSize, it's simply the size of the last
|
||||
// picked file.
|
||||
uint64_t sz = (candidate_size * (100L + ratio)) /100;
|
||||
if (sz < f->file_size) {
|
||||
if (sz < f->fd.GetFileSize()) {
|
||||
break;
|
||||
}
|
||||
if (options_->compaction_options_universal.stop_style == kCompactionStopStyleSimilarSize) {
|
||||
// Similar-size stopping rule: also check the last picked file isn't
|
||||
// far larger than the next candidate file.
|
||||
sz = (f->file_size * (100L + ratio)) / 100;
|
||||
sz = (f->fd.GetFileSize() * (100L + ratio)) / 100;
|
||||
if (sz < candidate_size) {
|
||||
// If the small file we've encountered begins a run of similar-size
|
||||
// files, we'll pick them up on a future iteration of the outer
|
||||
@@ -725,9 +728,9 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
// by the last-resort read amp strategy which disregards size ratios.
|
||||
break;
|
||||
}
|
||||
candidate_size = f->file_size;
|
||||
candidate_size = f->fd.GetFileSize();
|
||||
} else { // default kCompactionStopStyleTotalSize
|
||||
candidate_size += f->file_size;
|
||||
candidate_size += f->fd.GetFileSize();
|
||||
}
|
||||
candidate_count++;
|
||||
}
|
||||
@@ -744,8 +747,9 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
FileMetaData* f = version->files_[level][index];
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: Skipping file %lu[%d] with size %lu %d\n",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number,
|
||||
i, (unsigned long)f->file_size, f->being_compacted);
|
||||
version->cfd_->GetName().c_str(),
|
||||
(unsigned long)f->fd.GetNumber(), i,
|
||||
(unsigned long)f->fd.GetFileSize(), f->being_compacted);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -763,7 +767,8 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
uint64_t older_file_size = 0;
|
||||
for (unsigned int i = file_by_time.size() - 1; i >= first_index_after;
|
||||
i--) {
|
||||
older_file_size += version->files_[level][file_by_time[i]]->file_size;
|
||||
older_file_size +=
|
||||
version->files_[level][file_by_time[i]]->fd.GetFileSize();
|
||||
if (older_file_size * 100L >= total_size * (long) ratio_to_compress) {
|
||||
enable_compression = false;
|
||||
break;
|
||||
@@ -779,10 +784,10 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
int index = file_by_time[i];
|
||||
FileMetaData* f = c->input_version_->files_[level][index];
|
||||
c->inputs_[0].push_back(f);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: Picking file %lu[%d] with size %lu\n",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number, i,
|
||||
(unsigned long)f->file_size);
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: Picking file %lu[%d] with size %lu\n",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->fd.GetNumber(), i,
|
||||
(unsigned long)f->fd.GetFileSize());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -818,10 +823,10 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
break;
|
||||
}
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: skipping file %lu[%d] compacted %s",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number,
|
||||
loop, " cannot be a candidate to reduce size amp.\n");
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: skipping file %lu[%d] compacted %s",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->fd.GetNumber(),
|
||||
loop, " cannot be a candidate to reduce size amp.\n");
|
||||
f = nullptr;
|
||||
}
|
||||
if (f == nullptr) {
|
||||
@@ -829,8 +834,9 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
}
|
||||
|
||||
LogToBuffer(log_buffer, "[%s] Universal: First candidate file %lu[%d] %s",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number,
|
||||
start_index, " to reduce size amp.\n");
|
||||
version->cfd_->GetName().c_str(),
|
||||
(unsigned long)f->fd.GetNumber(), start_index,
|
||||
" to reduce size amp.\n");
|
||||
|
||||
// keep adding up all the remaining files
|
||||
for (unsigned int loop = start_index; loop < file_by_time.size() - 1;
|
||||
@@ -840,11 +846,12 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
if (f->being_compacted) {
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Universal: Possible candidate file %lu[%d] %s.",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number, loop,
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->fd.GetNumber(),
|
||||
loop,
|
||||
" is already being compacted. No size amp reduction possible.\n");
|
||||
return nullptr;
|
||||
}
|
||||
candidate_size += f->file_size;
|
||||
candidate_size += f->fd.GetFileSize();
|
||||
candidate_count++;
|
||||
}
|
||||
if (candidate_count == 0) {
|
||||
@@ -853,7 +860,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
|
||||
// size of earliest file
|
||||
int index = file_by_time[file_by_time.size() - 1];
|
||||
uint64_t earliest_file_size = version->files_[level][index]->file_size;
|
||||
uint64_t earliest_file_size = version->files_[level][index]->fd.GetFileSize();
|
||||
|
||||
// size amplification = percentage of additional size
|
||||
if (candidate_size * 100 < ratio * earliest_file_size) {
|
||||
@@ -885,8 +892,9 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
c->inputs_[0].push_back(f);
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: size amp picking file %lu[%d] with size %lu",
|
||||
version->cfd_->GetName().c_str(), (unsigned long)f->number,
|
||||
index, (unsigned long)f->file_size);
|
||||
version->cfd_->GetName().c_str(),
|
||||
(unsigned long)f->fd.GetNumber(), index,
|
||||
(unsigned long)f->fd.GetFileSize());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -896,7 +904,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(Version* version,
|
||||
assert(version->NumberLevels() == 1);
|
||||
uint64_t total_size = 0;
|
||||
for (const auto& file : version->files_[0]) {
|
||||
total_size += file->file_size;
|
||||
total_size += file->fd.GetFileSize();
|
||||
}
|
||||
|
||||
if (total_size <= options_->compaction_options_fifo.max_table_files_size ||
|
||||
@@ -924,13 +932,13 @@ Compaction* FIFOCompactionPicker::PickCompaction(Version* version,
|
||||
for (auto ritr = version->files_[0].rbegin();
|
||||
ritr != version->files_[0].rend(); ++ritr) {
|
||||
auto f = *ritr;
|
||||
total_size -= f->file_size;
|
||||
total_size -= f->fd.GetFileSize();
|
||||
c->inputs_[0].push_back(f);
|
||||
char tmp_fsize[16];
|
||||
AppendHumanBytes(f->file_size, tmp_fsize, sizeof(tmp_fsize));
|
||||
AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
|
||||
LogToBuffer(log_buffer, "[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with size %s for deletion",
|
||||
version->cfd_->GetName().c_str(), f->number, tmp_fsize);
|
||||
version->cfd_->GetName().c_str(), f->fd.GetNumber(), tmp_fsize);
|
||||
if (total_size <= options_->compaction_options_fifo.max_table_files_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
+26
-2
@@ -465,6 +465,8 @@ static auto FLAGS_compaction_fadvice_e =
|
||||
|
||||
DEFINE_bool(use_tailing_iterator, false,
|
||||
"Use tailing iterator to access a series of keys instead of get");
|
||||
DEFINE_int64(iter_refresh_interval_us, -1,
|
||||
"How often to refresh iterators. Disable refresh when -1");
|
||||
|
||||
DEFINE_bool(use_adaptive_mutex, rocksdb::Options().use_adaptive_mutex,
|
||||
"Use adaptive mutex");
|
||||
@@ -1926,7 +1928,7 @@ class Benchmark {
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)",
|
||||
snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)\n",
|
||||
found, read);
|
||||
|
||||
thread->stats.AddMessage(msg);
|
||||
@@ -2009,12 +2011,31 @@ class Benchmark {
|
||||
multi_iters.push_back(db->NewIterator(options));
|
||||
}
|
||||
}
|
||||
uint64_t last_refresh = FLAGS_env->NowMicros();
|
||||
|
||||
Slice key = AllocateKey();
|
||||
std::unique_ptr<const char[]> key_guard(key.data());
|
||||
|
||||
Duration duration(FLAGS_duration, reads_);
|
||||
while (!duration.Done(1)) {
|
||||
if (!FLAGS_use_tailing_iterator && FLAGS_iter_refresh_interval_us >= 0) {
|
||||
uint64_t now = FLAGS_env->NowMicros();
|
||||
if (now - last_refresh > (uint64_t)FLAGS_iter_refresh_interval_us) {
|
||||
if (db_ != nullptr) {
|
||||
delete single_iter;
|
||||
single_iter = db_->NewIterator(options);
|
||||
} else {
|
||||
for (auto iter : multi_iters) {
|
||||
delete iter;
|
||||
}
|
||||
multi_iters.clear();
|
||||
for (DB* db : multi_dbs_) {
|
||||
multi_iters.push_back(db->NewIterator(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
last_refresh = now;
|
||||
}
|
||||
// Pick a Iterator to use
|
||||
Iterator* iter_to_use = single_iter;
|
||||
if (single_iter == nullptr) {
|
||||
@@ -2035,9 +2056,12 @@ class Benchmark {
|
||||
}
|
||||
|
||||
char msg[100];
|
||||
snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)",
|
||||
snprintf(msg, sizeof(msg), "(%" PRIu64 " of %" PRIu64 " found)\n",
|
||||
found, read);
|
||||
thread->stats.AddMessage(msg);
|
||||
if (FLAGS_perf_level > 0) {
|
||||
thread->stats.AddMessage(perf_context.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void SeekRandomWhileWriting(ThreadState* thread) {
|
||||
|
||||
+227
-117
@@ -36,6 +36,7 @@
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/tailing_iter.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "db/transaction_log_impl.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -643,8 +644,7 @@ void DBImpl::PurgeObsoleteFiles(DeletionState& state) {
|
||||
const char* kDumbDbName = "";
|
||||
for (auto file : state.sst_delete_files) {
|
||||
candidate_files.push_back(
|
||||
TableFileName(kDumbDbName, file->number).substr(1)
|
||||
);
|
||||
TableFileName(kDumbDbName, file->fd.GetNumber()).substr(1));
|
||||
delete file;
|
||||
}
|
||||
|
||||
@@ -1148,6 +1148,8 @@ Status DBImpl::Recover(
|
||||
SequenceNumber max_sequence(0);
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
// Recover from all newer log files than the ones named in the
|
||||
// descriptor (new log files may have been added by the previous
|
||||
@@ -1367,14 +1369,14 @@ Status DBImpl::WriteLevel0TableForRecovery(ColumnFamilyData* cfd, MemTable* mem,
|
||||
mutex_.AssertHeld();
|
||||
const uint64_t start_micros = env_->NowMicros();
|
||||
FileMetaData meta;
|
||||
meta.number = versions_->NewFileNumber();
|
||||
pending_outputs_.insert(meta.number);
|
||||
meta.fd.number = versions_->NewFileNumber();
|
||||
pending_outputs_.insert(meta.fd.GetNumber());
|
||||
Iterator* iter = mem->NewIterator(ReadOptions(), true);
|
||||
const SequenceNumber newest_snapshot = snapshots_.GetNewest();
|
||||
const SequenceNumber earliest_seqno_in_memtable =
|
||||
mem->GetFirstSequenceNumber();
|
||||
Log(options_.info_log, "[%s] Level-0 table #%lu: started",
|
||||
cfd->GetName().c_str(), (unsigned long)meta.number);
|
||||
cfd->GetName().c_str(), (unsigned long)meta.fd.GetNumber());
|
||||
|
||||
Status s;
|
||||
{
|
||||
@@ -1388,27 +1390,28 @@ Status DBImpl::WriteLevel0TableForRecovery(ColumnFamilyData* cfd, MemTable* mem,
|
||||
}
|
||||
|
||||
Log(options_.info_log, "[%s] Level-0 table #%lu: %lu bytes %s",
|
||||
cfd->GetName().c_str(), (unsigned long)meta.number,
|
||||
(unsigned long)meta.file_size, s.ToString().c_str());
|
||||
cfd->GetName().c_str(), (unsigned long)meta.fd.GetNumber(),
|
||||
(unsigned long)meta.fd.GetFileSize(), s.ToString().c_str());
|
||||
delete iter;
|
||||
|
||||
pending_outputs_.erase(meta.number);
|
||||
pending_outputs_.erase(meta.fd.GetNumber());
|
||||
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
int level = 0;
|
||||
if (s.ok() && meta.file_size > 0) {
|
||||
edit->AddFile(level, meta.number, meta.file_size,
|
||||
meta.smallest, meta.largest,
|
||||
meta.smallest_seqno, meta.largest_seqno);
|
||||
if (s.ok() && meta.fd.GetFileSize() > 0) {
|
||||
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetFileSize(),
|
||||
meta.smallest, meta.largest, meta.smallest_seqno,
|
||||
meta.largest_seqno);
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats;
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.file_size;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.files_out_levelnp1 = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES, meta.file_size);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES,
|
||||
meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1418,9 +1421,9 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
mutex_.AssertHeld();
|
||||
const uint64_t start_micros = env_->NowMicros();
|
||||
FileMetaData meta;
|
||||
meta.number = versions_->NewFileNumber();
|
||||
*filenumber = meta.number;
|
||||
pending_outputs_.insert(meta.number);
|
||||
meta.fd.number = versions_->NewFileNumber();
|
||||
*filenumber = meta.fd.GetNumber();
|
||||
pending_outputs_.insert(meta.fd.GetNumber());
|
||||
|
||||
const SequenceNumber newest_snapshot = snapshots_.GetNewest();
|
||||
const SequenceNumber earliest_seqno_in_memtable =
|
||||
@@ -1440,7 +1443,7 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
Iterator* iter = NewMergingIterator(&cfd->internal_comparator(),
|
||||
&memtables[0], memtables.size());
|
||||
Log(options_.info_log, "[%s] Level-0 flush table #%lu: started",
|
||||
cfd->GetName().c_str(), (unsigned long)meta.number);
|
||||
cfd->GetName().c_str(), (unsigned long)meta.fd.GetNumber());
|
||||
|
||||
s = BuildTable(dbname_, env_, *cfd->options(), storage_options_,
|
||||
cfd->table_cache(), iter, &meta, cfd->internal_comparator(),
|
||||
@@ -1449,8 +1452,8 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
LogFlush(options_.info_log);
|
||||
delete iter;
|
||||
Log(options_.info_log, "[%s] Level-0 flush table #%lu: %lu bytes %s",
|
||||
cfd->GetName().c_str(), (unsigned long)meta.number,
|
||||
(unsigned long)meta.file_size, s.ToString().c_str());
|
||||
cfd->GetName().c_str(), (unsigned long)meta.fd.GetFileSize(),
|
||||
(unsigned long)meta.fd.GetFileSize(), s.ToString().c_str());
|
||||
|
||||
if (!options_.disableDataSync) {
|
||||
db_directory_->Fsync();
|
||||
@@ -1474,7 +1477,7 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
int level = 0;
|
||||
if (s.ok() && meta.file_size > 0) {
|
||||
if (s.ok() && meta.fd.GetFileSize() > 0) {
|
||||
const Slice min_user_key = meta.smallest.user_key();
|
||||
const Slice max_user_key = meta.largest.user_key();
|
||||
// if we have more than 1 background thread, then we cannot
|
||||
@@ -1485,16 +1488,17 @@ Status DBImpl::WriteLevel0Table(ColumnFamilyData* cfd,
|
||||
cfd->options()->compaction_style == kCompactionStyleLevel) {
|
||||
level = base->PickLevelForMemTableOutput(min_user_key, max_user_key);
|
||||
}
|
||||
edit->AddFile(level, meta.number, meta.file_size,
|
||||
meta.smallest, meta.largest,
|
||||
meta.smallest_seqno, meta.largest_seqno);
|
||||
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetFileSize(),
|
||||
meta.smallest, meta.largest, meta.smallest_seqno,
|
||||
meta.largest_seqno);
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats;
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.file_size;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES, meta.file_size);
|
||||
RecordTick(options_.statistics.get(), COMPACT_WRITE_BYTES,
|
||||
meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1685,9 +1689,10 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (const auto& f : cfd->current()->files_[level]) {
|
||||
edit.DeleteFile(level, f->number);
|
||||
edit.AddFile(to_level, f->number, f->file_size, f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno);
|
||||
edit.DeleteFile(level, f->fd.GetNumber());
|
||||
edit.AddFile(to_level, f->fd.GetNumber(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->smallest_seqno,
|
||||
f->largest_seqno);
|
||||
}
|
||||
Log(options_.info_log, "[%s] Apply version edit:\n%s",
|
||||
cfd->GetName().c_str(), edit.DebugString().data());
|
||||
@@ -2067,7 +2072,15 @@ void DBImpl::BackgroundCallCompaction() {
|
||||
if (madeProgress || bg_schedule_needed_) {
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
bg_cv_.SignalAll();
|
||||
if (madeProgress || bg_compaction_scheduled_ == 0 || bg_manual_only_ > 0) {
|
||||
// signal if
|
||||
// * madeProgress -- need to wakeup MakeRoomForWrite
|
||||
// * bg_compaction_scheduled_ == 0 -- need to wakeup ~DBImpl
|
||||
// * bg_manual_only_ > 0 -- need to wakeup RunManualCompaction
|
||||
// If none of this is true, there is no need to signal since nobody is
|
||||
// waiting for it
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
// IMPORTANT: there should be no code after calling SignalAll. This call may
|
||||
// signal the DB destructor that it's OK to proceed with destruction. In
|
||||
// that case, all DB variables will be dealloacated and referencing them
|
||||
@@ -2161,7 +2174,7 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
assert(c->column_family_data()->options()->compaction_style ==
|
||||
kCompactionStyleFIFO);
|
||||
for (const auto& f : *c->inputs(0)) {
|
||||
c->edit()->DeleteFile(c->level(), f->number);
|
||||
c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
|
||||
}
|
||||
status = versions_->LogAndApply(c->column_family_data(), c->edit(), &mutex_,
|
||||
db_directory_.get());
|
||||
@@ -2175,21 +2188,21 @@ Status DBImpl::BackgroundCompaction(bool* madeProgress,
|
||||
// Move file to next level
|
||||
assert(c->num_input_files(0) == 1);
|
||||
FileMetaData* f = c->input(0, 0);
|
||||
c->edit()->DeleteFile(c->level(), f->number);
|
||||
c->edit()->AddFile(c->level() + 1, f->number, f->file_size,
|
||||
f->smallest, f->largest,
|
||||
f->smallest_seqno, f->largest_seqno);
|
||||
c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
|
||||
c->edit()->AddFile(c->level() + 1, f->fd.GetNumber(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->smallest_seqno,
|
||||
f->largest_seqno);
|
||||
status = versions_->LogAndApply(c->column_family_data(), c->edit(), &mutex_,
|
||||
db_directory_.get());
|
||||
InstallSuperVersion(c->column_family_data(), deletion_state);
|
||||
|
||||
Version::LevelSummaryStorage tmp;
|
||||
LogToBuffer(log_buffer, "[%s] Moved #%lld to level-%d %lld bytes %s: %s\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
static_cast<unsigned long long>(f->number), c->level() + 1,
|
||||
static_cast<unsigned long long>(f->file_size),
|
||||
status.ToString().c_str(),
|
||||
c->input_version()->LevelSummary(&tmp));
|
||||
LogToBuffer(
|
||||
log_buffer, "[%s] Moved #%lld to level-%d %lld bytes %s: %s\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
static_cast<unsigned long long>(f->fd.GetNumber()), c->level() + 1,
|
||||
static_cast<unsigned long long>(f->fd.GetFileSize()),
|
||||
status.ToString().c_str(), c->input_version()->LevelSummary(&tmp));
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*madeProgress = true;
|
||||
} else {
|
||||
@@ -2325,13 +2338,10 @@ Status DBImpl::OpenCompactionOutputFile(CompactionState* compact) {
|
||||
Status s = env_->NewWritableFile(fname, &compact->outfile, storage_options_);
|
||||
|
||||
if (s.ok()) {
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold.
|
||||
ColumnFamilyData* cfd = compact->compaction->column_family_data();
|
||||
compact->outfile->SetPreallocationBlockSize(
|
||||
1.1 * cfd->compaction_picker()->MaxFileSizeForLevel(
|
||||
compact->compaction->output_level()));
|
||||
compact->compaction->OutputFilePreallocationSize());
|
||||
|
||||
ColumnFamilyData* cfd = compact->compaction->column_family_data();
|
||||
CompressionType compression_type =
|
||||
GetCompressionType(*cfd->options(), compact->compaction->output_level(),
|
||||
compact->compaction->enable_compression());
|
||||
@@ -2386,7 +2396,7 @@ Status DBImpl::FinishCompactionOutputFile(CompactionState* compact,
|
||||
if (s.ok() && current_entries > 0) {
|
||||
// Verify that the table is usable
|
||||
ColumnFamilyData* cfd = compact->compaction->column_family_data();
|
||||
FileMetaData meta(output_number, current_bytes);
|
||||
FileDescriptor meta(output_number, current_bytes);
|
||||
Iterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), storage_options_, cfd->internal_comparator(), meta);
|
||||
s = iter->status();
|
||||
@@ -2448,9 +2458,6 @@ Status DBImpl::InstallCompactionResults(CompactionState* compact,
|
||||
inline SequenceNumber DBImpl::findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, std::vector<SequenceNumber>& snapshots,
|
||||
SequenceNumber* prev_snapshot) {
|
||||
if (!IsSnapshotSupported()) {
|
||||
return 0;
|
||||
}
|
||||
SequenceNumber prev __attribute__((unused)) = 0;
|
||||
for (const auto cur : snapshots) {
|
||||
assert(prev <= cur);
|
||||
@@ -2489,6 +2496,7 @@ uint64_t DBImpl::CallFlushDuringCompaction(ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
Status DBImpl::ProcessKeyValueCompaction(
|
||||
bool is_snapshot_supported,
|
||||
SequenceNumber visible_at_tip,
|
||||
SequenceNumber earliest_snapshot,
|
||||
SequenceNumber latest_snapshot,
|
||||
@@ -2578,7 +2586,7 @@ Status DBImpl::ProcessKeyValueCompaction(
|
||||
cfd->user_comparator()->Compare(ikey.user_key,
|
||||
current_user_key.GetKey()) != 0) {
|
||||
// First occurrence of this user key
|
||||
current_user_key.SetUserKey(ikey.user_key);
|
||||
current_user_key.SetKey(ikey.user_key);
|
||||
has_current_user_key = true;
|
||||
last_sequence_for_key = kMaxSequenceNumber;
|
||||
visible_in_snapshot = kMaxSequenceNumber;
|
||||
@@ -2617,11 +2625,10 @@ Status DBImpl::ProcessKeyValueCompaction(
|
||||
// Otherwise, search though all existing snapshots to find
|
||||
// the earlist snapshot that is affected by this kv.
|
||||
SequenceNumber prev_snapshot = 0; // 0 means no previous snapshot
|
||||
SequenceNumber visible = visible_at_tip ?
|
||||
visible_at_tip :
|
||||
findEarliestVisibleSnapshot(ikey.sequence,
|
||||
compact->existing_snapshots,
|
||||
&prev_snapshot);
|
||||
SequenceNumber visible = visible_at_tip ? visible_at_tip :
|
||||
is_snapshot_supported ? findEarliestVisibleSnapshot(ikey.sequence,
|
||||
compact->existing_snapshots, &prev_snapshot)
|
||||
: 0;
|
||||
|
||||
if (visible_in_snapshot == visible) {
|
||||
// If the earliest snapshot is which this key is visible in
|
||||
@@ -2887,6 +2894,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
// Allocate the output file numbers before we release the lock
|
||||
AllocateCompactionOutputFileNumbers(compact);
|
||||
|
||||
bool is_snapshot_supported = IsSnapshotSupported();
|
||||
// Release mutex while we're actually doing the compaction work
|
||||
mutex_.Unlock();
|
||||
log_buffer->FlushBufferToLog();
|
||||
@@ -2973,6 +2981,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
// Done buffering for the current prefix. Spit it out to disk
|
||||
// Now just iterate through all the kv-pairs
|
||||
status = ProcessKeyValueCompaction(
|
||||
is_snapshot_supported,
|
||||
visible_at_tip,
|
||||
earliest_snapshot,
|
||||
latest_snapshot,
|
||||
@@ -3008,6 +3017,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
compact->MergeKeyValueSliceBuffer(&cfd->internal_comparator());
|
||||
|
||||
status = ProcessKeyValueCompaction(
|
||||
is_snapshot_supported,
|
||||
visible_at_tip,
|
||||
earliest_snapshot,
|
||||
latest_snapshot,
|
||||
@@ -3029,6 +3039,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
}
|
||||
compact->MergeKeyValueSliceBuffer(&cfd->internal_comparator());
|
||||
status = ProcessKeyValueCompaction(
|
||||
is_snapshot_supported,
|
||||
visible_at_tip,
|
||||
earliest_snapshot,
|
||||
latest_snapshot,
|
||||
@@ -3043,6 +3054,7 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
|
||||
if (!compaction_filter_v2) {
|
||||
status = ProcessKeyValueCompaction(
|
||||
is_snapshot_supported,
|
||||
visible_at_tip,
|
||||
earliest_snapshot,
|
||||
latest_snapshot,
|
||||
@@ -3086,15 +3098,15 @@ Status DBImpl::DoCompactionWork(CompactionState* compact,
|
||||
stats.files_out_levelnp1 = num_output_files;
|
||||
|
||||
for (int i = 0; i < compact->compaction->num_input_files(0); i++) {
|
||||
stats.bytes_readn += compact->compaction->input(0, i)->file_size;
|
||||
stats.bytes_readn += compact->compaction->input(0, i)->fd.GetFileSize();
|
||||
RecordTick(options_.statistics.get(), COMPACT_READ_BYTES,
|
||||
compact->compaction->input(0, i)->file_size);
|
||||
compact->compaction->input(0, i)->fd.GetFileSize());
|
||||
}
|
||||
|
||||
for (int i = 0; i < compact->compaction->num_input_files(1); i++) {
|
||||
stats.bytes_readnp1 += compact->compaction->input(1, i)->file_size;
|
||||
stats.bytes_readnp1 += compact->compaction->input(1, i)->fd.GetFileSize();
|
||||
RecordTick(options_.statistics.get(), COMPACT_READ_BYTES,
|
||||
compact->compaction->input(1, i)->file_size);
|
||||
compact->compaction->input(1, i)->fd.GetFileSize());
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_output_files; i++) {
|
||||
@@ -3170,18 +3182,34 @@ static void CleanupIteratorState(void* arg1, void* arg2) {
|
||||
|
||||
Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version) {
|
||||
std::vector<Iterator*> iterator_list;
|
||||
// Collect iterator for mutable mem
|
||||
iterator_list.push_back(super_version->mem->NewIterator(options));
|
||||
// Collect all needed child iterators for immutable memtables
|
||||
super_version->imm->AddIterators(options, &iterator_list);
|
||||
// Collect iterators for files in L0 - Ln
|
||||
super_version->current->AddIterators(options, storage_options_,
|
||||
&iterator_list);
|
||||
Iterator* internal_iter = NewMergingIterator(
|
||||
&cfd->internal_comparator(), &iterator_list[0], iterator_list.size());
|
||||
|
||||
SuperVersion* super_version,
|
||||
Arena* arena) {
|
||||
Iterator* internal_iter;
|
||||
if (arena != nullptr) {
|
||||
// Need to create internal iterator from the arena.
|
||||
MergeIteratorBuilder merge_iter_builder(&cfd->internal_comparator(), arena);
|
||||
// Collect iterator for mutable mem
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(options, false, arena));
|
||||
// Collect all needed child iterators for immutable memtables
|
||||
super_version->imm->AddIterators(options, &merge_iter_builder);
|
||||
// Collect iterators for files in L0 - Ln
|
||||
super_version->current->AddIterators(options, storage_options_,
|
||||
&merge_iter_builder);
|
||||
internal_iter = merge_iter_builder.Finish();
|
||||
} else {
|
||||
// Need to create internal iterator using malloc.
|
||||
std::vector<Iterator*> iterator_list;
|
||||
// Collect iterator for mutable mem
|
||||
iterator_list.push_back(super_version->mem->NewIterator(options));
|
||||
// Collect all needed child iterators for immutable memtables
|
||||
super_version->imm->AddIterators(options, &iterator_list);
|
||||
// Collect iterators for files in L0 - Ln
|
||||
super_version->current->AddIterators(options, storage_options_,
|
||||
&iterator_list);
|
||||
internal_iter = NewMergingIterator(&cfd->internal_comparator(),
|
||||
&iterator_list[0], iterator_list.size());
|
||||
}
|
||||
IterState* cleanup = new IterState(this, &mutex_, super_version);
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
|
||||
@@ -3455,6 +3483,7 @@ Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& options,
|
||||
Status s = versions_->LogAndApply(nullptr, &edit, &mutex_,
|
||||
db_directory_.get(), false, &options);
|
||||
if (s.ok()) {
|
||||
single_column_family_mode_ = false;
|
||||
auto cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
assert(cfd != nullptr);
|
||||
@@ -3532,30 +3561,77 @@ Iterator* DBImpl::NewIterator(const ReadOptions& options,
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
Iterator* iter;
|
||||
if (options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
return nullptr;
|
||||
#else
|
||||
iter = new TailingIterator(env_, this, options, cfd);
|
||||
// TODO(ljin): remove tailing iterator
|
||||
auto iter = new ForwardIterator(this, options, cfd);
|
||||
return NewDBIterator(env_, *cfd->options(), cfd->user_comparator(), iter,
|
||||
kMaxSequenceNumber);
|
||||
// return new TailingIterator(env_, this, options, cfd);
|
||||
#endif
|
||||
} else {
|
||||
SequenceNumber latest_snapshot = versions_->LastSequence();
|
||||
SuperVersion* sv = nullptr;
|
||||
sv = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
|
||||
iter = NewInternalIterator(options, cfd, sv);
|
||||
|
||||
auto snapshot =
|
||||
options.snapshot != nullptr
|
||||
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
|
||||
: latest_snapshot;
|
||||
iter = NewDBIterator(env_, *cfd->options(),
|
||||
cfd->user_comparator(), iter, snapshot);
|
||||
}
|
||||
|
||||
return iter;
|
||||
// Try to generate a DB iterator tree in continuous memory area to be
|
||||
// cache friendly. Here is an example of result:
|
||||
// +-------------------------------+
|
||||
// | |
|
||||
// | ArenaWrappedDBIter |
|
||||
// | + |
|
||||
// | +---> Inner Iterator ------------+
|
||||
// | | | |
|
||||
// | | +-- -- -- -- -- -- -- --+ |
|
||||
// | +--- | Arena | |
|
||||
// | | | |
|
||||
// | Allocated Memory: | |
|
||||
// | | +-------------------+ |
|
||||
// | | | DBIter | <---+
|
||||
// | | + |
|
||||
// | | | +-> iter_ ------------+
|
||||
// | | | | |
|
||||
// | | +-------------------+ |
|
||||
// | | | MergingIterator | <---+
|
||||
// | | + |
|
||||
// | | | +->child iter1 ------------+
|
||||
// | | | | | |
|
||||
// | | +->child iter2 ----------+ |
|
||||
// | | | | | | |
|
||||
// | | | +->child iter3 --------+ | |
|
||||
// | | | | | |
|
||||
// | | +-------------------+ | | |
|
||||
// | | | Iterator1 | <--------+
|
||||
// | | +-------------------+ | |
|
||||
// | | | Iterator2 | <------+
|
||||
// | | +-------------------+ |
|
||||
// | | | Iterator3 | <----+
|
||||
// | | +-------------------+
|
||||
// | | |
|
||||
// +-------+-----------------------+
|
||||
//
|
||||
// ArenaWrappedDBIter inlines an arena area where all the iterartor in the
|
||||
// the iterator tree is allocated in the order of being accessed when
|
||||
// querying.
|
||||
// Laying out the iterators in the order of being accessed makes it more
|
||||
// likely that any iterator pointer is close to the iterator it points to so
|
||||
// that they are likely to be in the same cache line and/or page.
|
||||
ArenaWrappedDBIter* db_iter = NewArenaWrappedDbIterator(
|
||||
env_, *cfd->options(), cfd->user_comparator(), snapshot);
|
||||
Iterator* internal_iter =
|
||||
NewInternalIterator(options, cfd, sv, db_iter->GetArena());
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
|
||||
return db_iter;
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
@@ -3618,9 +3694,9 @@ bool DBImpl::IsSnapshotSupported() const {
|
||||
}
|
||||
|
||||
const Snapshot* DBImpl::GetSnapshot() {
|
||||
MutexLock l(&mutex_);
|
||||
// returns null if the underlying memtable does not support snapshot.
|
||||
if (!IsSnapshotSupported()) return nullptr;
|
||||
MutexLock l(&mutex_);
|
||||
return snapshots_.New(versions_->LastSequence());
|
||||
}
|
||||
|
||||
@@ -3677,42 +3753,55 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
RecordTick(options_.statistics.get(), WRITE_DONE_BY_SELF, 1);
|
||||
}
|
||||
|
||||
assert(!single_column_family_mode_ ||
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
|
||||
|
||||
uint64_t flush_column_family_if_log_file = 0;
|
||||
uint64_t max_total_wal_size = (options_.max_total_wal_size == 0)
|
||||
? 2 * max_total_in_memory_state_
|
||||
? 4 * max_total_in_memory_state_
|
||||
: options_.max_total_wal_size;
|
||||
if (alive_log_files_.begin()->getting_flushed == false &&
|
||||
if (UNLIKELY(!single_column_family_mode_) &&
|
||||
alive_log_files_.begin()->getting_flushed == false &&
|
||||
total_log_size_ > max_total_wal_size) {
|
||||
flush_column_family_if_log_file = alive_log_files_.begin()->number;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
Log(options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64,
|
||||
flush_column_family_if_log_file);
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
flush_column_family_if_log_file, total_log_size_, max_total_wal_size);
|
||||
}
|
||||
|
||||
Status status;
|
||||
// refcounting cfd in iteration
|
||||
bool dead_cfd = false;
|
||||
autovector<SuperVersion*> superversions_to_free;
|
||||
autovector<log::Writer*> logs_to_free;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->Ref();
|
||||
bool force_flush = my_batch == nullptr ||
|
||||
(flush_column_family_if_log_file != 0 &&
|
||||
cfd->GetLogNumber() <= flush_column_family_if_log_file);
|
||||
// May temporarily unlock and wait.
|
||||
status = MakeRoomForWrite(cfd, force_flush, &superversions_to_free,
|
||||
&logs_to_free);
|
||||
if (cfd->Unref()) {
|
||||
dead_cfd = true;
|
||||
|
||||
if (LIKELY(single_column_family_mode_)) {
|
||||
// fast path
|
||||
status = MakeRoomForWrite(default_cf_handle_->cfd(), my_batch == nullptr,
|
||||
&superversions_to_free, &logs_to_free);
|
||||
} else {
|
||||
// refcounting cfd in iteration
|
||||
bool dead_cfd = false;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->Ref();
|
||||
bool force_flush =
|
||||
my_batch == nullptr ||
|
||||
(flush_column_family_if_log_file != 0 &&
|
||||
cfd->GetLogNumber() <= flush_column_family_if_log_file);
|
||||
// May temporarily unlock and wait.
|
||||
status = MakeRoomForWrite(cfd, force_flush, &superversions_to_free,
|
||||
&logs_to_free);
|
||||
if (cfd->Unref()) {
|
||||
dead_cfd = true;
|
||||
}
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
if (dead_cfd) {
|
||||
versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
|
||||
}
|
||||
}
|
||||
if (dead_cfd) {
|
||||
versions_->GetColumnFamilySet()->FreeDeadColumnFamilies();
|
||||
}
|
||||
|
||||
uint64_t last_sequence = versions_->LastSequence();
|
||||
Writer* last_writer = &w;
|
||||
@@ -3773,19 +3862,19 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
}
|
||||
if (status.ok()) {
|
||||
PERF_TIMER_START(write_memtable_time);
|
||||
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
updates, column_family_memtables_.get(), false, 0, this, false);
|
||||
// A non-OK status here indicates iteration failure (either in-memory
|
||||
// writebatch corruption (very bad), or the client specified invalid
|
||||
// column family). This will later on trigger bg_error_.
|
||||
//
|
||||
// Note that existing logic was not sound. Any partial failure writing
|
||||
// into the memtable would result in a state that some write ops might
|
||||
// have succeeded in memtable but Status reports error for all writes.
|
||||
|
||||
PERF_TIMER_STOP(write_memtable_time);
|
||||
|
||||
if (!status.ok()) {
|
||||
// Iteration failed (either in-memory writebatch corruption (very
|
||||
// bad), or the client specified invalid column family). Return
|
||||
// failure.
|
||||
// Note that existing logic was not sound. Any partial failure writing
|
||||
// into the memtable would result in a state that some write ops might
|
||||
// have succeeded in memtable but Status reports error for all writes.
|
||||
return status;
|
||||
}
|
||||
SetTickerCount(options_.statistics.get(), SEQUENCE_NUMBER,
|
||||
last_sequence);
|
||||
}
|
||||
@@ -3923,6 +4012,10 @@ Status DBImpl::MakeRoomForWrite(
|
||||
uint64_t rate_limit_delay_millis = 0;
|
||||
Status s;
|
||||
double score;
|
||||
// Once we schedule background work, we shouldn't schedule it again, since it
|
||||
// might generate a tight feedback loop, constantly scheduling more background
|
||||
// work, even if additional background work is not needed
|
||||
bool schedule_background_work = true;
|
||||
|
||||
while (true) {
|
||||
if (!bg_error_.ok()) {
|
||||
@@ -3966,7 +4059,10 @@ Status DBImpl::MakeRoomForWrite(
|
||||
DelayLoggingAndReset();
|
||||
Log(options_.info_log, "[%s] wait for memtable flush...\n",
|
||||
cfd->GetName().c_str());
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
if (schedule_background_work) {
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
schedule_background_work = false;
|
||||
}
|
||||
uint64_t stall;
|
||||
{
|
||||
StopWatch sw(env_, options_.statistics.get(),
|
||||
@@ -4467,12 +4563,26 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (cfd == nullptr) {
|
||||
s = Status::InvalidArgument("Column family not found: ", cf.name);
|
||||
break;
|
||||
if (cfd != nullptr) {
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
} else {
|
||||
if (db_options.create_missing_column_families) {
|
||||
// missing column family, create it
|
||||
ColumnFamilyHandle* handle;
|
||||
impl->mutex_.Unlock();
|
||||
s = impl->CreateColumnFamily(cf.options, cf.name, &handle);
|
||||
impl->mutex_.Lock();
|
||||
if (s.ok()) {
|
||||
handles->push_back(handle);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
s = Status::InvalidArgument("Column family not found: ", cf.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
handles->push_back(
|
||||
new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
|
||||
+18
-2
@@ -39,6 +39,7 @@ class Version;
|
||||
class VersionEdit;
|
||||
class VersionSet;
|
||||
class CompactionFilterV2;
|
||||
class Arena;
|
||||
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
@@ -278,13 +279,15 @@ class DBImpl : public DB {
|
||||
const DBOptions options_;
|
||||
|
||||
Iterator* NewInternalIterator(const ReadOptions&, ColumnFamilyData* cfd,
|
||||
SuperVersion* super_version);
|
||||
SuperVersion* super_version,
|
||||
Arena* arena = nullptr);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class InternalStats;
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class TailingIterator;
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
friend struct SuperVersion;
|
||||
struct CompactionState;
|
||||
@@ -373,6 +376,7 @@ class DBImpl : public DB {
|
||||
// Call compaction filter if is_compaction_v2 is not true. Then iterate
|
||||
// through input and compact the kv-pairs
|
||||
Status ProcessKeyValueCompaction(
|
||||
bool is_snapshot_supported,
|
||||
SequenceNumber visible_at_tip,
|
||||
SequenceNumber earliest_snapshot,
|
||||
SequenceNumber latest_snapshot,
|
||||
@@ -449,7 +453,16 @@ class DBImpl : public DB {
|
||||
// State below is protected by mutex_
|
||||
port::Mutex mutex_;
|
||||
port::AtomicPointer shutting_down_;
|
||||
port::CondVar bg_cv_; // Signalled when background work finishes
|
||||
// This condition variable is signaled on these conditions:
|
||||
// * whenever bg_compaction_scheduled_ goes down to 0
|
||||
// * if bg_manual_only_ > 0, whenever a compaction finishes, even if it hasn't
|
||||
// made any progress
|
||||
// * whenever a compaction made any progress
|
||||
// * whenever bg_flush_scheduled_ value decreases (i.e. whenever a flush is
|
||||
// done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background flush or compaction
|
||||
// * whenever bg_logstats_scheduled_ turns to false
|
||||
port::CondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
unique_ptr<log::Writer> log_;
|
||||
bool log_empty_;
|
||||
@@ -468,6 +481,9 @@ class DBImpl : public DB {
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
std::string host_name_;
|
||||
|
||||
|
||||
+61
-20
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "port/port.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
@@ -37,8 +38,6 @@ static void DumpInternalIter(Iterator* iter) {
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// Memtables and sstables that make the DB representation contain
|
||||
// (userkey,seq,type) => uservalue entries. DBIter
|
||||
// combines multiple entries for the same userkey found in the DB
|
||||
@@ -57,9 +56,10 @@ class DBIter: public Iterator {
|
||||
kReverse
|
||||
};
|
||||
|
||||
DBIter(Env* env, const Options& options,
|
||||
const Comparator* cmp, Iterator* iter, SequenceNumber s)
|
||||
: env_(env),
|
||||
DBIter(Env* env, const Options& options, const Comparator* cmp,
|
||||
Iterator* iter, SequenceNumber s, bool arena_mode)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(env),
|
||||
logger_(options.info_log.get()),
|
||||
user_comparator_(cmp),
|
||||
user_merge_operator_(options.merge_operator.get()),
|
||||
@@ -74,7 +74,15 @@ class DBIter: public Iterator {
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
RecordTick(statistics_, NO_ITERATORS, -1);
|
||||
delete iter_;
|
||||
if (!arena_mode_) {
|
||||
delete iter_;
|
||||
} else {
|
||||
iter_->~Iterator();
|
||||
}
|
||||
}
|
||||
virtual void SetIter(Iterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
}
|
||||
virtual bool Valid() const { return valid_; }
|
||||
virtual Slice key() const {
|
||||
@@ -116,11 +124,12 @@ class DBIter: public Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
bool arena_mode_;
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
const MergeOperator* const user_merge_operator_;
|
||||
Iterator* const iter_;
|
||||
Iterator* iter_;
|
||||
SequenceNumber const sequence_;
|
||||
|
||||
Status status_;
|
||||
@@ -211,18 +220,18 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
case kTypeDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetUserKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
valid_ = true;
|
||||
saved_key_.SetUserKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
return;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
saved_key_.SetUserKey(ikey.user_key);
|
||||
saved_key_.SetKey(ikey.user_key);
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
MergeValuesNewToOld(); // Go to a different state machine
|
||||
@@ -331,7 +340,7 @@ void DBIter::Prev() {
|
||||
// iter_ is pointing at the current entry. Scan backwards until
|
||||
// the key changes so we can use the normal reverse scanning code.
|
||||
assert(iter_->Valid()); // Otherwise valid_ would have been false
|
||||
saved_key_.SetUserKey(ExtractUserKey(iter_->key()));
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
while (true) {
|
||||
iter_->Prev();
|
||||
if (!iter_->Valid()) {
|
||||
@@ -377,7 +386,7 @@ void DBIter::FindPrevUserEntry() {
|
||||
std::string empty;
|
||||
swap(empty, saved_value_);
|
||||
}
|
||||
saved_key_.SetUserKey(ExtractUserKey(iter_->key()));
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()));
|
||||
saved_value_.assign(raw_value.data(), raw_value.size());
|
||||
}
|
||||
} else {
|
||||
@@ -461,16 +470,48 @@ void DBIter::SeekToLast() {
|
||||
FindPrevUserEntry();
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
Iterator* NewDBIterator(Env* env, const Options& options,
|
||||
const Comparator* user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence) {
|
||||
return new DBIter(env, options, user_key_comparator, internal_iter, sequence,
|
||||
false);
|
||||
}
|
||||
|
||||
Iterator* NewDBIterator(
|
||||
Env* env,
|
||||
const Options& options,
|
||||
const Comparator *user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
ArenaWrappedDBIter::~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
|
||||
void ArenaWrappedDBIter::SetDBIter(DBIter* iter) { db_iter_ = iter; }
|
||||
|
||||
void ArenaWrappedDBIter::SetIterUnderDBIter(Iterator* iter) {
|
||||
static_cast<DBIter*>(db_iter_)->SetIter(iter);
|
||||
}
|
||||
|
||||
inline bool ArenaWrappedDBIter::Valid() const { return db_iter_->Valid(); }
|
||||
inline void ArenaWrappedDBIter::SeekToFirst() { db_iter_->SeekToFirst(); }
|
||||
inline void ArenaWrappedDBIter::SeekToLast() { db_iter_->SeekToLast(); }
|
||||
inline void ArenaWrappedDBIter::Seek(const Slice& target) {
|
||||
db_iter_->Seek(target);
|
||||
}
|
||||
inline void ArenaWrappedDBIter::Next() { db_iter_->Next(); }
|
||||
inline void ArenaWrappedDBIter::Prev() { db_iter_->Prev(); }
|
||||
inline Slice ArenaWrappedDBIter::key() const { return db_iter_->key(); }
|
||||
inline Slice ArenaWrappedDBIter::value() const { return db_iter_->value(); }
|
||||
inline Status ArenaWrappedDBIter::status() const { return db_iter_->status(); }
|
||||
void ArenaWrappedDBIter::RegisterCleanup(CleanupFunction function, void* arg1,
|
||||
void* arg2) {
|
||||
db_iter_->RegisterCleanup(function, arg1, arg2);
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const Options& options, const Comparator* user_key_comparator,
|
||||
const SequenceNumber& sequence) {
|
||||
return new DBIter(env, options, user_key_comparator,
|
||||
internal_iter, sequence);
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
Arena* arena = iter->GetArena();
|
||||
auto mem = arena->AllocateAligned(sizeof(DBIter));
|
||||
DBIter* db_iter = new (mem)
|
||||
DBIter(env, options, user_key_comparator, nullptr, sequence, true);
|
||||
iter->SetDBIter(db_iter);
|
||||
return iter;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -11,9 +11,14 @@
|
||||
#include <stdint.h>
|
||||
#include "rocksdb/db.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class DBIter;
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
// "*internal_iter") that were live at the specified "sequence" number
|
||||
// into appropriate user keys.
|
||||
@@ -24,4 +29,45 @@ extern Iterator* NewDBIterator(
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence);
|
||||
|
||||
// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
|
||||
// iterator is supposed be allocated. This class is used as an entry point of
|
||||
// a iterator hierarchy whose memory can be allocated inline. In that way,
|
||||
// accessing the iterator tree can be more cache friendly. It is also faster
|
||||
// to allocate.
|
||||
class ArenaWrappedDBIter : public Iterator {
|
||||
public:
|
||||
virtual ~ArenaWrappedDBIter();
|
||||
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
virtual Arena* GetArena() { return &arena_; }
|
||||
|
||||
// Set the DB Iterator to be wrapped
|
||||
|
||||
virtual void SetDBIter(DBIter* iter);
|
||||
|
||||
// Set the internal iterator wrapped inside the DB Iterator. Usually it is
|
||||
// a merging iterator.
|
||||
virtual void SetIterUnderDBIter(Iterator* iter);
|
||||
virtual bool Valid() const override;
|
||||
virtual void SeekToFirst() override;
|
||||
virtual void SeekToLast() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void Next() override;
|
||||
virtual void Prev() override;
|
||||
virtual Slice key() const override;
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
|
||||
|
||||
private:
|
||||
DBIter* db_iter_;
|
||||
Arena arena_;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const Options& options, const Comparator* user_key_comparator,
|
||||
const SequenceNumber& sequence);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -6704,6 +6704,53 @@ TEST(DBTest, TailingIteratorKeepAdding) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, TailingIteratorSeekToNext) {
|
||||
CreateAndReopenWithCF({"pikachu"});
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 1000;
|
||||
for (int i = 1; i < num_records; ++i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
for (int i = 2 * num_records; i > 0; --i) {
|
||||
char buf1[32];
|
||||
char buf2[32];
|
||||
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
||||
|
||||
Slice key(buf1, 20);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
||||
Slice target(buf2, 20);
|
||||
iter->Seek(target);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, TailingIteratorDeletes) {
|
||||
CreateAndReopenWithCF({"pikachu"});
|
||||
ReadOptions read_options;
|
||||
@@ -6775,6 +6822,31 @@ TEST(DBTest, TailingIteratorPrefixSeek) {
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST(DBTest, BlockBasedTablePrefixIndexTest) {
|
||||
// create a DB with block prefix index
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
|
||||
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
Flush();
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
|
||||
// Reopen it without prefix extractor, make sure everything still works.
|
||||
// RocksDB should just fall back to the binary index.
|
||||
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.prefix_extractor.reset();
|
||||
|
||||
Reopen(&options);
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("v2", Get("k2"));
|
||||
}
|
||||
|
||||
TEST(DBTest, ChecksumTest) {
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+30
-9
@@ -256,24 +256,45 @@ class IterKey {
|
||||
|
||||
void Clear() { key_size_ = 0; }
|
||||
|
||||
void SetUserKey(const Slice& user_key) {
|
||||
size_t size = user_key.size();
|
||||
void SetKey(const Slice& key) {
|
||||
size_t size = key.size();
|
||||
EnlargeBufferIfNeeded(size);
|
||||
memcpy(key_, user_key.data(), size);
|
||||
memcpy(key_, key.data(), size);
|
||||
key_size_ = size;
|
||||
}
|
||||
|
||||
void SetInternalKey(const Slice& key_prefix, const Slice& user_key,
|
||||
SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek) {
|
||||
size_t psize = key_prefix.size();
|
||||
size_t usize = user_key.size();
|
||||
EnlargeBufferIfNeeded(psize + usize + sizeof(uint64_t));
|
||||
if (psize > 0) {
|
||||
memcpy(key_, key_prefix.data(), psize);
|
||||
}
|
||||
memcpy(key_ + psize, user_key.data(), usize);
|
||||
EncodeFixed64(key_ + usize + psize, PackSequenceAndType(s, value_type));
|
||||
key_size_ = psize + usize + sizeof(uint64_t);
|
||||
}
|
||||
|
||||
void SetInternalKey(const Slice& user_key, SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek) {
|
||||
size_t usize = user_key.size();
|
||||
EnlargeBufferIfNeeded(usize + sizeof(uint64_t));
|
||||
memcpy(key_, user_key.data(), usize);
|
||||
EncodeFixed64(key_ + usize, PackSequenceAndType(s, value_type));
|
||||
key_size_ = usize + sizeof(uint64_t);
|
||||
SetInternalKey(Slice(), user_key, s, value_type);
|
||||
}
|
||||
|
||||
void Reserve(size_t size) {
|
||||
EnlargeBufferIfNeeded(size);
|
||||
key_size_ = size;
|
||||
}
|
||||
|
||||
void SetInternalKey(const ParsedInternalKey& parsed_key) {
|
||||
SetInternalKey(parsed_key.user_key, parsed_key.sequence, parsed_key.type);
|
||||
SetInternalKey(Slice(), parsed_key);
|
||||
}
|
||||
|
||||
void SetInternalKey(const Slice& key_prefix,
|
||||
const ParsedInternalKey& parsed_key_suffix) {
|
||||
SetInternalKey(key_prefix, parsed_key_suffix.user_key,
|
||||
parsed_key_suffix.sequence, parsed_key_suffix.type);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "db/forward_iterator.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/merger.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Usage:
|
||||
// LevelIterator iter;
|
||||
// iter.SetFileIndex(file_index);
|
||||
// iter.Seek(target);
|
||||
// iter.Next()
|
||||
class LevelIterator : public Iterator {
|
||||
public:
|
||||
LevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files)
|
||||
: cfd_(cfd), read_options_(read_options), files_(files), valid_(false),
|
||||
file_index_(std::numeric_limits<uint32_t>::max()) {}
|
||||
|
||||
void SetFileIndex(uint32_t file_index) {
|
||||
assert(file_index < files_.size());
|
||||
if (file_index != file_index_) {
|
||||
file_index_ = file_index;
|
||||
file_iter_.reset(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd, nullptr /* table_reader_ptr */, false));
|
||||
}
|
||||
valid_ = false;
|
||||
}
|
||||
void SeekToLast() override {
|
||||
status_ = Status::NotSupported("LevelIterator::SeekToLast()");
|
||||
valid_ = false;
|
||||
}
|
||||
void Prev() {
|
||||
status_ = Status::NotSupported("LevelIterator::Prev()");
|
||||
valid_ = false;
|
||||
}
|
||||
bool Valid() const override {
|
||||
return valid_;
|
||||
}
|
||||
void SeekToFirst() override {
|
||||
SetFileIndex(0);
|
||||
file_iter_->SeekToFirst();
|
||||
valid_ = file_iter_->Valid();
|
||||
}
|
||||
void Seek(const Slice& internal_key) override {
|
||||
assert(file_iter_ != nullptr);
|
||||
file_iter_->Seek(internal_key);
|
||||
valid_ = file_iter_->Valid();
|
||||
assert(valid_);
|
||||
}
|
||||
void Next() override {
|
||||
assert(valid_);
|
||||
file_iter_->Next();
|
||||
while (!file_iter_->Valid()) {
|
||||
if (file_index_ + 1 >= files_.size()) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
SetFileIndex(file_index_ + 1);
|
||||
file_iter_->SeekToFirst();
|
||||
}
|
||||
valid_ = file_iter_->Valid();
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(valid_);
|
||||
return file_iter_->key();
|
||||
}
|
||||
Slice value() const override {
|
||||
assert(valid_);
|
||||
return file_iter_->value();
|
||||
}
|
||||
Status status() const override {
|
||||
return status_;
|
||||
}
|
||||
|
||||
private:
|
||||
const ColumnFamilyData* const cfd_;
|
||||
const ReadOptions& read_options_;
|
||||
const std::vector<FileMetaData*>& files_;
|
||||
|
||||
bool valid_;
|
||||
uint32_t file_index_;
|
||||
Status status_;
|
||||
std::unique_ptr<Iterator> file_iter_;
|
||||
};
|
||||
|
||||
ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd)
|
||||
: db_(db),
|
||||
read_options_(read_options),
|
||||
cfd_(cfd),
|
||||
prefix_extractor_(cfd->options()->prefix_extractor.get()),
|
||||
user_comparator_(cfd->user_comparator()),
|
||||
immutable_min_heap_(MinIterComparator(&cfd_->internal_comparator())),
|
||||
sv_(nullptr),
|
||||
mutable_iter_(nullptr),
|
||||
current_(nullptr),
|
||||
valid_(false),
|
||||
is_prev_set_(false) {}
|
||||
|
||||
ForwardIterator::~ForwardIterator() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void ForwardIterator::Cleanup() {
|
||||
delete mutable_iter_;
|
||||
for (auto* m : imm_iters_) {
|
||||
delete m;
|
||||
}
|
||||
imm_iters_.clear();
|
||||
for (auto* f : l0_iters_) {
|
||||
delete f;
|
||||
}
|
||||
l0_iters_.clear();
|
||||
for (auto* l : level_iters_) {
|
||||
delete l;
|
||||
}
|
||||
level_iters_.clear();
|
||||
|
||||
if (sv_ != nullptr && sv_->Unref()) {
|
||||
DBImpl::DeletionState deletion_state;
|
||||
db_->mutex_.Lock();
|
||||
sv_->Cleanup();
|
||||
db_->FindObsoleteFiles(deletion_state, false, true);
|
||||
db_->mutex_.Unlock();
|
||||
delete sv_;
|
||||
if (deletion_state.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(deletion_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ForwardIterator::Valid() const {
|
||||
return valid_;
|
||||
}
|
||||
|
||||
void ForwardIterator::SeekToFirst() {
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RebuildIterators();
|
||||
}
|
||||
SeekInternal(Slice(), true);
|
||||
}
|
||||
|
||||
void ForwardIterator::Seek(const Slice& internal_key) {
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
RebuildIterators();
|
||||
}
|
||||
SeekInternal(internal_key, false);
|
||||
}
|
||||
|
||||
void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
bool seek_to_first) {
|
||||
// mutable
|
||||
seek_to_first ? mutable_iter_->SeekToFirst() :
|
||||
mutable_iter_->Seek(internal_key);
|
||||
|
||||
// immutable
|
||||
// TODO(ljin): NeedToSeekImmutable has negative impact on performance
|
||||
// if it turns to need to seek immutable often. We probably want to have
|
||||
// an option to turn it off.
|
||||
if (seek_to_first || NeedToSeekImmutable(internal_key)) {
|
||||
{
|
||||
auto tmp = MinIterHeap(MinIterComparator(&cfd_->internal_comparator()));
|
||||
immutable_min_heap_.swap(tmp);
|
||||
}
|
||||
for (auto* m : imm_iters_) {
|
||||
seek_to_first ? m->SeekToFirst() : m->Seek(internal_key);
|
||||
if (m->Valid()) {
|
||||
immutable_min_heap_.push(m);
|
||||
}
|
||||
}
|
||||
|
||||
auto* files = sv_->current->files_;
|
||||
for (uint32_t i = 0; i < files[0].size(); ++i) {
|
||||
if (seek_to_first) {
|
||||
l0_iters_[i]->SeekToFirst();
|
||||
} else {
|
||||
// If the target key passes over the larget key, we are sure Next()
|
||||
// won't go over this file.
|
||||
if (user_comparator_->Compare(ExtractUserKey(internal_key),
|
||||
files[0][i]->largest.user_key()) > 0) {
|
||||
continue;
|
||||
}
|
||||
l0_iters_[i]->Seek(internal_key);
|
||||
}
|
||||
if (l0_iters_[i]->Valid()) {
|
||||
immutable_min_heap_.push(l0_iters_[i]);
|
||||
}
|
||||
}
|
||||
for (int32_t level = 1; level < sv_->current->NumberLevels(); ++level) {
|
||||
if (files[level].empty()) {
|
||||
continue;
|
||||
}
|
||||
assert(level_iters_[level - 1] != nullptr);
|
||||
uint32_t f_idx = 0;
|
||||
if (!seek_to_first) {
|
||||
f_idx = FindFileInRange(
|
||||
files[level], internal_key, 0, files[level].size());
|
||||
}
|
||||
if (f_idx < files[level].size()) {
|
||||
level_iters_[level - 1]->SetFileIndex(f_idx);
|
||||
seek_to_first ? level_iters_[level - 1]->SeekToFirst() :
|
||||
level_iters_[level - 1]->Seek(internal_key);
|
||||
if (level_iters_[level - 1]->Valid()) {
|
||||
immutable_min_heap_.push(level_iters_[level - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (seek_to_first || immutable_min_heap_.empty()) {
|
||||
is_prev_set_ = false;
|
||||
} else {
|
||||
prev_key_.SetKey(internal_key);
|
||||
is_prev_set_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ForwardIterator::Next() {
|
||||
assert(valid_);
|
||||
|
||||
if (sv_ == nullptr ||
|
||||
sv_ ->version_number != cfd_->GetSuperVersionNumber()) {
|
||||
std::string current_key = key().ToString();
|
||||
Slice old_key(current_key.data(), current_key.size());
|
||||
|
||||
RebuildIterators();
|
||||
SeekInternal(old_key, false);
|
||||
if (!valid_ || key().compare(old_key) != 0) {
|
||||
return;
|
||||
}
|
||||
} else if (current_ != mutable_iter_) {
|
||||
// It is going to advance immutable iterator
|
||||
prev_key_.SetKey(current_->key());
|
||||
is_prev_set_ = true;
|
||||
}
|
||||
|
||||
current_->Next();
|
||||
if (current_->Valid() && current_ != mutable_iter_) {
|
||||
immutable_min_heap_.push(current_);
|
||||
}
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
Slice ForwardIterator::key() const {
|
||||
assert(valid_);
|
||||
return current_->key();
|
||||
}
|
||||
|
||||
Slice ForwardIterator::value() const {
|
||||
assert(valid_);
|
||||
return current_->value();
|
||||
}
|
||||
|
||||
Status ForwardIterator::status() const {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
} else if (!mutable_iter_->status().ok()) {
|
||||
return mutable_iter_->status();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void ForwardIterator::RebuildIterators() {
|
||||
// Clean up
|
||||
Cleanup();
|
||||
// New
|
||||
sv_ = cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
mutable_iter_ = sv_->mem->NewIterator(read_options_);
|
||||
sv_->imm->AddIterators(read_options_, &imm_iters_);
|
||||
const auto& l0_files = sv_->current->files_[0];
|
||||
l0_iters_.reserve(l0_files.size());
|
||||
for (const auto* l0 : l0_files) {
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd));
|
||||
}
|
||||
level_iters_.reserve(sv_->current->NumberLevels() - 1);
|
||||
for (int32_t level = 1; level < sv_->current->NumberLevels(); ++level) {
|
||||
if (sv_->current->files_[level].empty()) {
|
||||
level_iters_.push_back(nullptr);
|
||||
} else {
|
||||
level_iters_.push_back(new LevelIterator(cfd_, read_options_,
|
||||
sv_->current->files_[level]));
|
||||
}
|
||||
}
|
||||
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
}
|
||||
|
||||
void ForwardIterator::UpdateCurrent() {
|
||||
if (immutable_min_heap_.empty() && !mutable_iter_->Valid()) {
|
||||
current_ = nullptr;
|
||||
} else if (immutable_min_heap_.empty()) {
|
||||
current_ = mutable_iter_;
|
||||
} else if (!mutable_iter_->Valid()) {
|
||||
current_ = immutable_min_heap_.top();
|
||||
immutable_min_heap_.pop();
|
||||
} else {
|
||||
current_ = immutable_min_heap_.top();
|
||||
assert(current_ != nullptr);
|
||||
assert(current_->Valid());
|
||||
int cmp = cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
mutable_iter_->key(), current_->key());
|
||||
assert(cmp != 0);
|
||||
if (cmp > 0) {
|
||||
immutable_min_heap_.pop();
|
||||
} else {
|
||||
current_ = mutable_iter_;
|
||||
}
|
||||
}
|
||||
valid_ = (current_ != nullptr);
|
||||
if (!status_.ok()) {
|
||||
status_ = Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
bool ForwardIterator::NeedToSeekImmutable(const Slice& target) {
|
||||
if (!is_prev_set_) {
|
||||
return true;
|
||||
}
|
||||
Slice prev_key = prev_key_.GetKey();
|
||||
if (prefix_extractor_ && prefix_extractor_->Transform(target).compare(
|
||||
prefix_extractor_->Transform(prev_key)) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
prev_key, target) >= 0) {
|
||||
return true;
|
||||
}
|
||||
if (immutable_min_heap_.empty() ||
|
||||
cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
target, current_ == mutable_iter_ ? immutable_min_heap_.top()->key()
|
||||
: current_->key()) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t ForwardIterator::FindFileInRange(
|
||||
const std::vector<FileMetaData*>& files, const Slice& internal_key,
|
||||
uint32_t left, uint32_t right) {
|
||||
while (left < right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
const FileMetaData* f = files[mid];
|
||||
if (cfd_->internal_comparator().InternalKeyComparator::Compare(
|
||||
f->largest.Encode(), internal_key) < 0) {
|
||||
// Key at "mid.largest" is < "target". Therefore all
|
||||
// files at or before "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "mid.largest" is >= "target". Therefore all files
|
||||
// after "mid" are uninteresting.
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
return right;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBImpl;
|
||||
class Env;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
class LevelIterator;
|
||||
struct FileMetaData;
|
||||
|
||||
class MinIterComparator {
|
||||
public:
|
||||
explicit MinIterComparator(const Comparator* comparator) :
|
||||
comparator_(comparator) {}
|
||||
|
||||
bool operator()(Iterator* a, Iterator* b) {
|
||||
return comparator_->Compare(a->key(), b->key()) > 0;
|
||||
}
|
||||
private:
|
||||
const Comparator* comparator_;
|
||||
};
|
||||
|
||||
typedef std::priority_queue<Iterator*,
|
||||
std::vector<Iterator*>,
|
||||
MinIterComparator> MinIterHeap;
|
||||
|
||||
/**
|
||||
* ForwardIterator is a special type of iterator that only supports Seek()
|
||||
* and Next(). It is expected to perform better than TailingIterator by
|
||||
* removing the encapsulation and making all information accessible within
|
||||
* the iterator. At the current implementation, snapshot is taken at the
|
||||
* time Seek() is called. The Next() followed do not see new values after.
|
||||
*/
|
||||
class ForwardIterator : public Iterator {
|
||||
public:
|
||||
ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd);
|
||||
virtual ~ForwardIterator();
|
||||
|
||||
void SeekToLast() override {
|
||||
status_ = Status::NotSupported("ForwardIterator::SeekToLast()");
|
||||
valid_ = false;
|
||||
}
|
||||
void Prev() {
|
||||
status_ = Status::NotSupported("ForwardIterator::Prev");
|
||||
valid_ = false;
|
||||
}
|
||||
|
||||
virtual bool Valid() const override;
|
||||
void SeekToFirst() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void Next() override;
|
||||
virtual Slice key() const override;
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
|
||||
private:
|
||||
void Cleanup();
|
||||
void RebuildIterators();
|
||||
void SeekInternal(const Slice& internal_key, bool seek_to_first);
|
||||
void UpdateCurrent();
|
||||
bool NeedToSeekImmutable(const Slice& internal_key);
|
||||
uint32_t FindFileInRange(
|
||||
const std::vector<FileMetaData*>& files, const Slice& internal_key,
|
||||
uint32_t left, uint32_t right);
|
||||
|
||||
DBImpl* const db_;
|
||||
const ReadOptions read_options_;
|
||||
ColumnFamilyData* const cfd_;
|
||||
const SliceTransform* const prefix_extractor_;
|
||||
const Comparator* user_comparator_;
|
||||
MinIterHeap immutable_min_heap_;
|
||||
|
||||
SuperVersion* sv_;
|
||||
Iterator* mutable_iter_;
|
||||
std::vector<Iterator*> imm_iters_;
|
||||
std::vector<Iterator*> l0_iters_;
|
||||
std::vector<LevelIterator*> level_iters_;
|
||||
Iterator* current_;
|
||||
// internal iterator status
|
||||
Status status_;
|
||||
bool valid_;
|
||||
|
||||
IterKey prev_key_;
|
||||
bool is_prev_set_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
+25
-8
@@ -20,6 +20,7 @@
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/merger.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/murmurhash.h"
|
||||
@@ -173,15 +174,24 @@ const char* EncodeKey(std::string* scratch, const Slice& target) {
|
||||
class MemTableIterator: public Iterator {
|
||||
public:
|
||||
MemTableIterator(const MemTable& mem, const ReadOptions& options,
|
||||
bool enforce_total_order)
|
||||
bool enforce_total_order, Arena* arena)
|
||||
: bloom_(nullptr),
|
||||
prefix_extractor_(mem.prefix_extractor_),
|
||||
valid_(false) {
|
||||
valid_(false),
|
||||
arena_mode_(arena != nullptr) {
|
||||
if (prefix_extractor_ != nullptr && !enforce_total_order) {
|
||||
bloom_ = mem.prefix_bloom_.get();
|
||||
iter_.reset(mem.table_->GetDynamicPrefixIterator());
|
||||
iter_ = mem.table_->GetDynamicPrefixIterator(arena);
|
||||
} else {
|
||||
iter_.reset(mem.table_->GetIterator());
|
||||
iter_ = mem.table_->GetIterator(arena);
|
||||
}
|
||||
}
|
||||
|
||||
~MemTableIterator() {
|
||||
if (arena_mode_) {
|
||||
iter_->~Iterator();
|
||||
} else {
|
||||
delete iter_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +238,9 @@ class MemTableIterator: public Iterator {
|
||||
private:
|
||||
DynamicBloom* bloom_;
|
||||
const SliceTransform* const prefix_extractor_;
|
||||
std::unique_ptr<MemTableRep::Iterator> iter_;
|
||||
MemTableRep::Iterator* iter_;
|
||||
bool valid_;
|
||||
bool arena_mode_;
|
||||
|
||||
// No copying allowed
|
||||
MemTableIterator(const MemTableIterator&);
|
||||
@@ -237,8 +248,14 @@ class MemTableIterator: public Iterator {
|
||||
};
|
||||
|
||||
Iterator* MemTable::NewIterator(const ReadOptions& options,
|
||||
bool enforce_total_order) {
|
||||
return new MemTableIterator(*this, options, enforce_total_order);
|
||||
bool enforce_total_order, Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return new MemTableIterator(*this, options, enforce_total_order, nullptr);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
|
||||
return new (mem)
|
||||
MemTableIterator(*this, options, enforce_total_order, arena);
|
||||
}
|
||||
}
|
||||
|
||||
port::RWMutex* MemTable::GetLock(const Slice& key) {
|
||||
@@ -349,7 +366,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
s->value->assign(v.data(), v.size());
|
||||
}
|
||||
if (s->inplace_update_support) {
|
||||
s->mem->GetLock(s->key->user_key())->Unlock();
|
||||
s->mem->GetLock(s->key->user_key())->ReadUnlock();
|
||||
}
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
|
||||
+6
-1
@@ -21,6 +21,7 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class Mutex;
|
||||
class MemTableIterator;
|
||||
class MergeContext;
|
||||
@@ -77,8 +78,12 @@ class MemTable {
|
||||
//
|
||||
// By default, it returns an iterator for prefix seek if prefix_extractor
|
||||
// is configured in Options.
|
||||
// arena: If not null, the arena needs to be used to allocate the Iterator.
|
||||
// Calling ~Iterator of the iterator will destroy all the states but
|
||||
// those allocated in arena.
|
||||
Iterator* NewIterator(const ReadOptions& options,
|
||||
bool enforce_total_order = false);
|
||||
bool enforce_total_order = false,
|
||||
Arena* arena = nullptr);
|
||||
|
||||
// Add an entry into memtable that maps key to value at the
|
||||
// specified sequence number and with the specified type.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "table/merger.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/log_buffer.h"
|
||||
|
||||
@@ -78,6 +79,14 @@ void MemTableListVersion::AddIterators(const ReadOptions& options,
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options, MergeIteratorBuilder* merge_iter_builder) {
|
||||
for (auto& m : memlist_) {
|
||||
merge_iter_builder->AddIterator(
|
||||
m->NewIterator(options, merge_iter_builder->GetArena()));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t MemTableListVersion::GetTotalNumEntries() const {
|
||||
uint64_t total_num = 0;
|
||||
for (auto& m : memlist_) {
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace rocksdb {
|
||||
class ColumnFamilyData;
|
||||
class InternalKeyComparator;
|
||||
class Mutex;
|
||||
class MergeIteratorBuilder;
|
||||
|
||||
// keeps a list of immutable memtables in a vector. the list is immutable
|
||||
// if refcount is bigger than one. It is used as a state for Get() and
|
||||
@@ -49,6 +50,9 @@ class MemTableListVersion {
|
||||
void AddIterators(const ReadOptions& options,
|
||||
std::vector<Iterator*>* iterator_list);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
MergeIteratorBuilder* merge_iter_builder);
|
||||
|
||||
uint64_t GetTotalNumEntries() const;
|
||||
|
||||
private:
|
||||
|
||||
+138
-27
@@ -61,7 +61,8 @@ class PlainTableDBTest {
|
||||
// Return the current option configuration.
|
||||
Options CurrentOptions() {
|
||||
Options options;
|
||||
options.table_factory.reset(NewPlainTableFactory(16, 2, 0.8, 3));
|
||||
options.table_factory.reset(NewPlainTableFactory(0, 2, 0.8, 3, 0, kPrefix));
|
||||
options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(8));
|
||||
options.allow_mmap_reads = true;
|
||||
return options;
|
||||
@@ -178,16 +179,21 @@ class TestPlainTableReader : public PlainTableReader {
|
||||
public:
|
||||
TestPlainTableReader(const EnvOptions& storage_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
uint64_t file_size, int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness,
|
||||
EncodingType encoding_type, uint64_t file_size,
|
||||
int bloom_bits_per_key, double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
const TableProperties* table_properties,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
const Options& options, bool* expect_bloom_not_match)
|
||||
: PlainTableReader(options, std::move(file), storage_options, icomparator,
|
||||
file_size, bloom_bits_per_key, hash_table_ratio,
|
||||
index_sparseness, table_properties, 2 * 1024 * 1024),
|
||||
encoding_type, file_size, table_properties),
|
||||
expect_bloom_not_match_(expect_bloom_not_match) {
|
||||
Status s = PopulateIndex(const_cast<TableProperties*>(table_properties));
|
||||
Status s = MmapDataFile();
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
s = PopulateIndex(const_cast<TableProperties*>(table_properties),
|
||||
bloom_bits_per_key, hash_table_ratio, index_sparseness,
|
||||
2 * 1024 * 1024);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
@@ -209,9 +215,10 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
uint32_t user_key_len, int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
size_t huge_page_tlb_size)
|
||||
: PlainTableFactory(user_key_len, user_key_len, hash_table_ratio,
|
||||
index_sparseness, huge_page_tlb_size),
|
||||
size_t huge_page_tlb_size,
|
||||
EncodingType encoding_type)
|
||||
: PlainTableFactory(user_key_len, bloom_bits_per_key, hash_table_ratio,
|
||||
index_sparseness, huge_page_tlb_size, encoding_type),
|
||||
bloom_bits_per_key_(bloom_bits_per_key),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
index_sparseness_(index_sparseness),
|
||||
@@ -226,10 +233,17 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
options.env, options.info_log.get(), &props);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto encoding_type_prop =
|
||||
user_props.find(PlainTablePropertyNames::kEncodingType);
|
||||
assert(encoding_type_prop != user_props.end());
|
||||
EncodingType encoding_type = static_cast<EncodingType>(
|
||||
DecodeFixed32(encoding_type_prop->second.c_str()));
|
||||
|
||||
std::unique_ptr<PlainTableReader> new_reader(new TestPlainTableReader(
|
||||
soptions, internal_comparator, file_size, bloom_bits_per_key_,
|
||||
hash_table_ratio_, index_sparseness_, props, std::move(file), options,
|
||||
expect_bloom_not_match_));
|
||||
soptions, internal_comparator, encoding_type, file_size,
|
||||
bloom_bits_per_key_, hash_table_ratio_, index_sparseness_, props,
|
||||
std::move(file), options, expect_bloom_not_match_));
|
||||
|
||||
*table = std::move(new_reader);
|
||||
return s;
|
||||
@@ -245,18 +259,22 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
TEST(PlainTableDBTest, Flush) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
if (encoding_type == kPrefix && total_order == 1) {
|
||||
continue;
|
||||
}
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
// Set only one bucket to force bucket conflict.
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.table_factory.reset(NewTotalOrderPlainTableFactory(
|
||||
16, bloom_bits, 2, huge_page_tlb_size));
|
||||
0, bloom_bits, 2, huge_page_tlb_size));
|
||||
} else {
|
||||
options.table_factory.reset(NewPlainTableFactory(
|
||||
16, bloom_bits, 0.75, 16, huge_page_tlb_size));
|
||||
0, bloom_bits, 0.75, 16, huge_page_tlb_size, encoding_type));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -279,14 +297,19 @@ TEST(PlainTableDBTest, Flush) {
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PlainTableDBTest, Flush2) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
if (encoding_type == kPrefix && total_order == 1) {
|
||||
continue;
|
||||
}
|
||||
bool expect_bloom_not_match = false;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -294,13 +317,13 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.prefix_extractor = nullptr;
|
||||
options.table_factory.reset(
|
||||
new TestPlainTableFactory(&expect_bloom_not_match, 16, bloom_bits,
|
||||
0, 2, huge_page_tlb_size));
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 0, bloom_bits, 0, 2, huge_page_tlb_size,
|
||||
encoding_type));
|
||||
} else {
|
||||
options.table_factory.reset(
|
||||
new TestPlainTableFactory(&expect_bloom_not_match, 16, bloom_bits,
|
||||
0.75, 16, huge_page_tlb_size));
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 0, bloom_bits, 0.75, 16,
|
||||
huge_page_tlb_size, encoding_type));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("0000000000000bar", "b"));
|
||||
@@ -339,14 +362,19 @@ TEST(PlainTableDBTest, Flush2) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PlainTableDBTest, Iterator) {
|
||||
for (size_t huge_page_tlb_size = 0; huge_page_tlb_size <= 2 * 1024 * 1024;
|
||||
huge_page_tlb_size += 2 * 1024 * 1024) {
|
||||
for (EncodingType encoding_type : {kPlain, kPrefix}) {
|
||||
for (int bloom_bits = 0; bloom_bits <= 117; bloom_bits += 117) {
|
||||
for (int total_order = 0; total_order <= 1; total_order++) {
|
||||
if (encoding_type == kPrefix && total_order == 1) {
|
||||
continue;
|
||||
}
|
||||
bool expect_bloom_not_match = false;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -354,13 +382,13 @@ TEST(PlainTableDBTest, Iterator) {
|
||||
// Test index interval for the same prefix to be 1, 2 and 4
|
||||
if (total_order) {
|
||||
options.prefix_extractor = nullptr;
|
||||
options.table_factory.reset(
|
||||
new TestPlainTableFactory(&expect_bloom_not_match, 16, bloom_bits,
|
||||
0, 2, huge_page_tlb_size));
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 16, bloom_bits, 0, 2, huge_page_tlb_size,
|
||||
encoding_type));
|
||||
} else {
|
||||
options.table_factory.reset(
|
||||
new TestPlainTableFactory(&expect_bloom_not_match, 16, bloom_bits,
|
||||
0.75, 16, huge_page_tlb_size));
|
||||
options.table_factory.reset(new TestPlainTableFactory(
|
||||
&expect_bloom_not_match, 16, bloom_bits, 0.75, 16,
|
||||
huge_page_tlb_size, encoding_type));
|
||||
}
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
@@ -447,6 +475,7 @@ TEST(PlainTableDBTest, Iterator) {
|
||||
delete iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +487,7 @@ std::string MakeLongKey(size_t length, char c) {
|
||||
|
||||
TEST(PlainTableDBTest, IteratorLargeKeys) {
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewTotalOrderPlainTableFactory(0, 0, 16));
|
||||
options.table_factory.reset(NewTotalOrderPlainTableFactory(0, 0, 16, 0));
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset();
|
||||
DestroyAndReopen(&options);
|
||||
@@ -494,6 +523,45 @@ TEST(PlainTableDBTest, IteratorLargeKeys) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string MakeLongKeyWithPrefix(size_t length, char c) {
|
||||
return "00000000" + std::string(length - 8, c);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(PlainTableDBTest, IteratorLargeKeysWithPrefix) {
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewPlainTableFactory(16, 0, 0.8, 3, 0, kPrefix));
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
std::string key_list[] = {
|
||||
MakeLongKeyWithPrefix(30, '0'), MakeLongKeyWithPrefix(16, '1'),
|
||||
MakeLongKeyWithPrefix(32, '2'), MakeLongKeyWithPrefix(60, '3'),
|
||||
MakeLongKeyWithPrefix(90, '4'), MakeLongKeyWithPrefix(50, '5'),
|
||||
MakeLongKeyWithPrefix(26, '6')};
|
||||
|
||||
for (size_t i = 0; i < 7; i++) {
|
||||
ASSERT_OK(Put(key_list[i], std::to_string(i)));
|
||||
}
|
||||
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
Iterator* iter = dbfull()->NewIterator(ReadOptions());
|
||||
iter->Seek(key_list[0]);
|
||||
|
||||
for (size_t i = 0; i < 7; i++) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(key_list[i], iter->key().ToString());
|
||||
ASSERT_EQ(std::to_string(i), iter->value().ToString());
|
||||
iter->Next();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// A test comparator which compare two strings in this way:
|
||||
// (1) first compare prefix of 8 bytes in alphabet order,
|
||||
// (2) if two strings share the same prefix, sort the other part of the string
|
||||
@@ -846,6 +914,49 @@ TEST(PlainTableDBTest, CompactionTrigger) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
}
|
||||
|
||||
TEST(PlainTableDBTest, AdaptiveTable) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
ASSERT_OK(Put("1000000000000foo", "v1"));
|
||||
ASSERT_OK(Put("0000000000000bar", "v2"));
|
||||
ASSERT_OK(Put("1000000000000foo", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
|
||||
options.create_if_missing = false;
|
||||
std::shared_ptr<TableFactory> dummy_factory;
|
||||
std::shared_ptr<TableFactory> block_based_factory(
|
||||
NewBlockBasedTableFactory());
|
||||
options.table_factory.reset(NewAdaptiveTableFactory(
|
||||
block_based_factory, dummy_factory, dummy_factory));
|
||||
Reopen(&options);
|
||||
ASSERT_EQ("v3", Get("1000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
|
||||
ASSERT_OK(Put("2000000000000foo", "v4"));
|
||||
ASSERT_OK(Put("3000000000000bar", "v5"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_EQ("v4", Get("2000000000000foo"));
|
||||
ASSERT_EQ("v5", Get("3000000000000bar"));
|
||||
|
||||
Reopen(&options);
|
||||
ASSERT_EQ("v3", Get("1000000000000foo"));
|
||||
ASSERT_EQ("v2", Get("0000000000000bar"));
|
||||
ASSERT_EQ("v4", Get("2000000000000foo"));
|
||||
ASSERT_EQ("v5", Get("3000000000000bar"));
|
||||
|
||||
options.table_factory.reset(NewBlockBasedTableFactory());
|
||||
Reopen(&options);
|
||||
ASSERT_NE("v3", Get("1000000000000foo"));
|
||||
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
Reopen(&options);
|
||||
ASSERT_NE("v5", Get("3000000000000bar"));
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+15
-19
@@ -84,7 +84,7 @@ class Repairer {
|
||||
if (status.ok()) {
|
||||
unsigned long long bytes = 0;
|
||||
for (size_t i = 0; i < tables_.size(); i++) {
|
||||
bytes += tables_[i].meta.file_size;
|
||||
bytes += tables_[i].meta.fd.GetFileSize();
|
||||
}
|
||||
Log(options_.info_log,
|
||||
"**** Repaired rocksdb %s; "
|
||||
@@ -230,7 +230,7 @@ class Repairer {
|
||||
// Do not record a version edit for this conversion to a Table
|
||||
// since ExtractMetaData() will also generate edits.
|
||||
FileMetaData meta;
|
||||
meta.number = next_file_number_++;
|
||||
meta.fd.number = next_file_number_++;
|
||||
ReadOptions ro;
|
||||
Iterator* iter = mem->NewIterator(ro, true /* enforce_total_order */);
|
||||
status = BuildTable(dbname_, env_, options_, storage_options_, table_cache_,
|
||||
@@ -240,22 +240,20 @@ class Repairer {
|
||||
delete cf_mems_default;
|
||||
mem = nullptr;
|
||||
if (status.ok()) {
|
||||
if (meta.file_size > 0) {
|
||||
table_numbers_.push_back(meta.number);
|
||||
if (meta.fd.GetFileSize() > 0) {
|
||||
table_numbers_.push_back(meta.fd.GetNumber());
|
||||
}
|
||||
}
|
||||
Log(options_.info_log, "Log #%llu: %d ops saved to Table #%llu %s",
|
||||
(unsigned long long) log,
|
||||
counter,
|
||||
(unsigned long long) meta.number,
|
||||
status.ToString().c_str());
|
||||
(unsigned long long)log, counter,
|
||||
(unsigned long long)meta.fd.GetNumber(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
void ExtractMetaData() {
|
||||
for (size_t i = 0; i < table_numbers_.size(); i++) {
|
||||
TableInfo t;
|
||||
t.meta.number = table_numbers_[i];
|
||||
t.meta.fd.number = table_numbers_[i];
|
||||
Status status = ScanTable(&t);
|
||||
if (!status.ok()) {
|
||||
std::string fname = TableFileName(dbname_, table_numbers_[i]);
|
||||
@@ -270,13 +268,12 @@ class Repairer {
|
||||
}
|
||||
|
||||
Status ScanTable(TableInfo* t) {
|
||||
std::string fname = TableFileName(dbname_, t->meta.number);
|
||||
std::string fname = TableFileName(dbname_, t->meta.fd.GetNumber());
|
||||
int counter = 0;
|
||||
Status status = env_->GetFileSize(fname, &t->meta.file_size);
|
||||
Status status = env_->GetFileSize(fname, &t->meta.fd.file_size);
|
||||
if (status.ok()) {
|
||||
FileMetaData dummy_meta(t->meta.number, t->meta.file_size);
|
||||
Iterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), storage_options_, icmp_, dummy_meta);
|
||||
ReadOptions(), storage_options_, icmp_, t->meta.fd);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->min_sequence = 0;
|
||||
@@ -285,7 +282,7 @@ class Repairer {
|
||||
Slice key = iter->key();
|
||||
if (!ParseInternalKey(key, &parsed)) {
|
||||
Log(options_.info_log, "Table #%llu: unparsable key %s",
|
||||
(unsigned long long) t->meta.number,
|
||||
(unsigned long long)t->meta.fd.GetNumber(),
|
||||
EscapeString(key).c_str());
|
||||
continue;
|
||||
}
|
||||
@@ -309,8 +306,7 @@ class Repairer {
|
||||
delete iter;
|
||||
}
|
||||
Log(options_.info_log, "Table #%llu: %d entries %s",
|
||||
(unsigned long long) t->meta.number,
|
||||
counter,
|
||||
(unsigned long long)t->meta.fd.GetNumber(), counter,
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
@@ -339,9 +335,9 @@ class Repairer {
|
||||
for (size_t i = 0; i < tables_.size(); i++) {
|
||||
// TODO(opt): separate out into multiple levels
|
||||
const TableInfo& t = tables_[i];
|
||||
edit_->AddFile(0, t.meta.number, t.meta.file_size,
|
||||
t.meta.smallest, t.meta.largest,
|
||||
t.min_sequence, t.max_sequence);
|
||||
edit_->AddFile(0, t.meta.fd.GetNumber(), t.meta.fd.GetFileSize(),
|
||||
t.meta.smallest, t.meta.largest, t.min_sequence,
|
||||
t.max_sequence);
|
||||
}
|
||||
|
||||
//fprintf(stderr, "NewDescriptor:\n%s\n", edit_.DebugString().c_str());
|
||||
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table_reader);
|
||||
|
||||
Iterator* NewIterator(const ReadOptions&) override;
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena) override;
|
||||
|
||||
Status Get(const ReadOptions&, const Slice& key, void* arg,
|
||||
bool (*handle_result)(void* arg, const ParsedInternalKey& k,
|
||||
@@ -218,8 +218,14 @@ std::shared_ptr<const TableProperties> SimpleTableReader::GetTableProperties()
|
||||
return rep_->table_properties;
|
||||
}
|
||||
|
||||
Iterator* SimpleTableReader::NewIterator(const ReadOptions& options) {
|
||||
return new SimpleTableIterator(this);
|
||||
Iterator* SimpleTableReader::NewIterator(const ReadOptions& options,
|
||||
Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return new SimpleTableIterator(this);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(SimpleTableIterator));
|
||||
return new (mem) SimpleTableIterator(this);
|
||||
}
|
||||
}
|
||||
|
||||
Status SimpleTableReader::GetOffset(const Slice& target, uint64_t* offset) {
|
||||
|
||||
+22
-24
@@ -13,6 +13,7 @@
|
||||
#include "db/version_edit.h"
|
||||
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "table/table_reader.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -30,7 +31,7 @@ static void UnrefEntry(void* arg1, void* arg2) {
|
||||
cache->Release(h);
|
||||
}
|
||||
|
||||
static Slice GetSliceForFileNumber(uint64_t* file_number) {
|
||||
static Slice GetSliceForFileNumber(const uint64_t* file_number) {
|
||||
return Slice(reinterpret_cast<const char*>(file_number),
|
||||
sizeof(*file_number));
|
||||
}
|
||||
@@ -56,11 +57,10 @@ void TableCache::ReleaseHandle(Cache::Handle* handle) {
|
||||
|
||||
Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
Cache::Handle** handle, bool* table_io,
|
||||
const bool no_io) {
|
||||
const FileDescriptor& fd, Cache::Handle** handle,
|
||||
bool* table_io, const bool no_io) {
|
||||
Status s;
|
||||
Slice key = GetSliceForFileNumber(&file_number);
|
||||
Slice key = GetSliceForFileNumber(&fd.number);
|
||||
*handle = cache_->Lookup(key);
|
||||
if (*handle == nullptr) {
|
||||
if (no_io) { // Dont do IO and return a not-found status
|
||||
@@ -69,7 +69,7 @@ Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
if (table_io != nullptr) {
|
||||
*table_io = true; // we had to do IO from storage
|
||||
}
|
||||
std::string fname = TableFileName(dbname_, file_number);
|
||||
std::string fname = TableFileName(dbname_, fd.GetNumber());
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
unique_ptr<TableReader> table_reader;
|
||||
s = env_->NewRandomAccessFile(fname, &file, toptions);
|
||||
@@ -80,8 +80,8 @@ Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
}
|
||||
StopWatch sw(env_, options_->statistics.get(), TABLE_OPEN_IO_MICROS);
|
||||
s = options_->table_factory->NewTableReader(
|
||||
*options_, toptions, internal_comparator, std::move(file), file_size,
|
||||
&table_reader);
|
||||
*options_, toptions, internal_comparator, std::move(file),
|
||||
fd.GetFileSize(), &table_reader);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -100,25 +100,25 @@ Status TableCache::FindTable(const EnvOptions& toptions,
|
||||
Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
const EnvOptions& toptions,
|
||||
const InternalKeyComparator& icomparator,
|
||||
const FileMetaData& file_meta,
|
||||
const FileDescriptor& fd,
|
||||
TableReader** table_reader_ptr,
|
||||
bool for_compaction) {
|
||||
bool for_compaction, Arena* arena) {
|
||||
if (table_reader_ptr != nullptr) {
|
||||
*table_reader_ptr = nullptr;
|
||||
}
|
||||
TableReader* table_reader = file_meta.table_reader;
|
||||
TableReader* table_reader = fd.table_reader;
|
||||
Cache::Handle* handle = nullptr;
|
||||
Status s;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(toptions, icomparator, file_meta.number, file_meta.file_size,
|
||||
&handle, nullptr, options.read_tier == kBlockCacheTier);
|
||||
s = FindTable(toptions, icomparator, fd, &handle, nullptr,
|
||||
options.read_tier == kBlockCacheTier);
|
||||
if (!s.ok()) {
|
||||
return NewErrorIterator(s);
|
||||
return NewErrorIterator(s, arena);
|
||||
}
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
|
||||
Iterator* result = table_reader->NewIterator(options);
|
||||
Iterator* result = table_reader->NewIterator(options, arena);
|
||||
if (handle != nullptr) {
|
||||
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
||||
}
|
||||
@@ -135,16 +135,15 @@ Iterator* TableCache::NewIterator(const ReadOptions& options,
|
||||
|
||||
Status TableCache::Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k, void* arg,
|
||||
const FileDescriptor& fd, const Slice& k, void* arg,
|
||||
bool (*saver)(void*, const ParsedInternalKey&,
|
||||
const Slice&, bool),
|
||||
bool* table_io, void (*mark_key_may_exist)(void*)) {
|
||||
TableReader* t = file_meta.table_reader;
|
||||
TableReader* t = fd.table_reader;
|
||||
Status s;
|
||||
Cache::Handle* handle = nullptr;
|
||||
if (!t) {
|
||||
s = FindTable(storage_options_, internal_comparator, file_meta.number,
|
||||
file_meta.file_size, &handle, table_io,
|
||||
s = FindTable(storage_options_, internal_comparator, fd, &handle, table_io,
|
||||
options.read_tier == kBlockCacheTier);
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
@@ -164,11 +163,10 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
}
|
||||
Status TableCache::GetTableProperties(
|
||||
const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
|
||||
std::shared_ptr<const TableProperties>* properties, bool no_io) {
|
||||
Status s;
|
||||
auto table_reader = file_meta.table_reader;
|
||||
auto table_reader = fd.table_reader;
|
||||
// table already been pre-loaded?
|
||||
if (table_reader) {
|
||||
*properties = table_reader->GetTableProperties();
|
||||
@@ -178,8 +176,8 @@ Status TableCache::GetTableProperties(
|
||||
|
||||
bool table_io;
|
||||
Cache::Handle* table_handle = nullptr;
|
||||
s = FindTable(toptions, internal_comparator, file_meta.number,
|
||||
file_meta.file_size, &table_handle, &table_io, no_io);
|
||||
s = FindTable(toptions, internal_comparator, fd, &table_handle, &table_io,
|
||||
no_io);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+7
-8
@@ -23,10 +23,9 @@
|
||||
namespace rocksdb {
|
||||
|
||||
class Env;
|
||||
struct FileMetaData;
|
||||
class Arena;
|
||||
struct FileDescriptor;
|
||||
|
||||
// TODO(sdong): try to come up with a better API to pass the file information
|
||||
// other than simply passing FileMetaData.
|
||||
class TableCache {
|
||||
public:
|
||||
TableCache(const std::string& dbname, const Options* options,
|
||||
@@ -42,16 +41,16 @@ class TableCache {
|
||||
// returned iterator is live.
|
||||
Iterator* NewIterator(const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const FileDescriptor& file_fd,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
bool for_compaction = false);
|
||||
bool for_compaction = false, Arena* arena = nullptr);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call (*handle_result)(arg, found_key, found_value) repeatedly until
|
||||
// it returns false.
|
||||
Status Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k, void* arg,
|
||||
const FileDescriptor& file_fd, const Slice& k, void* arg,
|
||||
bool (*handle_result)(void*, const ParsedInternalKey&,
|
||||
const Slice&, bool),
|
||||
bool* table_io, void (*mark_key_may_exist)(void*) = nullptr);
|
||||
@@ -62,7 +61,7 @@ class TableCache {
|
||||
// Find table reader
|
||||
Status FindTable(const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint64_t file_number, uint64_t file_size, Cache::Handle**,
|
||||
const FileDescriptor& file_fd, Cache::Handle**,
|
||||
bool* table_io = nullptr, const bool no_io = false);
|
||||
|
||||
// Get TableReader from a cache handle.
|
||||
@@ -76,7 +75,7 @@ class TableCache {
|
||||
// we set `no_io` to be true.
|
||||
Status GetTableProperties(const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const FileDescriptor& file_meta,
|
||||
std::shared_ptr<const TableProperties>* properties,
|
||||
bool no_io = false);
|
||||
|
||||
|
||||
+19
-14
@@ -95,8 +95,8 @@ void VersionEdit::EncodeTo(std::string* dst) const {
|
||||
const FileMetaData& f = new_files_[i].second;
|
||||
PutVarint32(dst, kNewFile2);
|
||||
PutVarint32(dst, new_files_[i].first); // level
|
||||
PutVarint64(dst, f.number);
|
||||
PutVarint64(dst, f.file_size);
|
||||
PutVarint64(dst, f.fd.GetNumber());
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
PutLengthPrefixedSlice(dst, f.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, f.largest.Encode());
|
||||
PutVarint64(dst, f.smallest_seqno);
|
||||
@@ -230,12 +230,14 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
break;
|
||||
|
||||
case kNewFile:
|
||||
if (GetLevel(&input, &level, &msg) &&
|
||||
GetVarint64(&input, &f.number) &&
|
||||
GetVarint64(&input, &f.file_size) &&
|
||||
case kNewFile: {
|
||||
uint64_t number;
|
||||
uint64_t file_size;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest)) {
|
||||
f.fd = FileDescriptor(number, file_size);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -243,15 +245,17 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kNewFile2:
|
||||
if (GetLevel(&input, &level, &msg) &&
|
||||
GetVarint64(&input, &f.number) &&
|
||||
GetVarint64(&input, &f.file_size) &&
|
||||
}
|
||||
case kNewFile2: {
|
||||
uint64_t number;
|
||||
uint64_t file_size;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &f.smallest_seqno) &&
|
||||
GetVarint64(&input, &f.largest_seqno) ) {
|
||||
GetVarint64(&input, &f.largest_seqno)) {
|
||||
f.fd = FileDescriptor(number, file_size);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -259,6 +263,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case kColumnFamily:
|
||||
if (!GetVarint32(&input, &column_family_)) {
|
||||
@@ -336,9 +341,9 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
r.append("\n AddFile: ");
|
||||
AppendNumberTo(&r, new_files_[i].first);
|
||||
r.append(" ");
|
||||
AppendNumberTo(&r, f.number);
|
||||
AppendNumberTo(&r, f.fd.GetNumber());
|
||||
r.append(" ");
|
||||
AppendNumberTo(&r, f.file_size);
|
||||
AppendNumberTo(&r, f.fd.GetFileSize());
|
||||
r.append(" ");
|
||||
r.append(f.smallest.DebugString(hex_key));
|
||||
r.append(" .. ");
|
||||
|
||||
+23
-12
@@ -19,11 +19,28 @@ namespace rocksdb {
|
||||
|
||||
class VersionSet;
|
||||
|
||||
// A copyable structure contains information needed to read data from an SST
|
||||
// file. It can contains a pointer to a table reader opened for the file, or
|
||||
// file number and size, which can be used to create a new table reader for it.
|
||||
// The behavior is undefined when a copied of the structure is used when the
|
||||
// file is not in any live version any more.
|
||||
struct FileDescriptor {
|
||||
uint64_t number;
|
||||
uint64_t file_size; // File size in bytes
|
||||
// Table reader in table_reader_handle
|
||||
TableReader* table_reader;
|
||||
|
||||
FileDescriptor(uint64_t number, uint64_t file_size)
|
||||
: number(number), file_size(file_size), table_reader(nullptr) {}
|
||||
|
||||
uint64_t GetNumber() const { return number; }
|
||||
uint64_t GetFileSize() const { return file_size; }
|
||||
};
|
||||
|
||||
struct FileMetaData {
|
||||
int refs;
|
||||
FileDescriptor fd;
|
||||
int allowed_seeks; // Seeks allowed until compaction
|
||||
uint64_t number;
|
||||
uint64_t file_size; // File size in bytes
|
||||
InternalKey smallest; // Smallest internal key served by table
|
||||
InternalKey largest; // Largest internal key served by table
|
||||
bool being_compacted; // Is this file undergoing compaction?
|
||||
@@ -32,18 +49,13 @@ struct FileMetaData {
|
||||
|
||||
// Needs to be disposed when refs becomes 0.
|
||||
Cache::Handle* table_reader_handle;
|
||||
// Table reader in table_reader_handle
|
||||
TableReader* table_reader;
|
||||
|
||||
FileMetaData(uint64_t number, uint64_t file_size)
|
||||
FileMetaData()
|
||||
: refs(0),
|
||||
fd(0, 0),
|
||||
allowed_seeks(1 << 30),
|
||||
number(number),
|
||||
file_size(file_size),
|
||||
being_compacted(false),
|
||||
table_reader_handle(nullptr),
|
||||
table_reader(nullptr) {}
|
||||
FileMetaData() : FileMetaData(0, 0) {}
|
||||
table_reader_handle(nullptr) {}
|
||||
};
|
||||
|
||||
class VersionEdit {
|
||||
@@ -89,8 +101,7 @@ class VersionEdit {
|
||||
const SequenceNumber& largest_seqno) {
|
||||
assert(smallest_seqno <= largest_seqno);
|
||||
FileMetaData f;
|
||||
f.number = file;
|
||||
f.file_size = file_size;
|
||||
f.fd = FileDescriptor(file, file_size);
|
||||
f.smallest = smallest;
|
||||
f.largest = largest;
|
||||
f.smallest_seqno = smallest_seqno;
|
||||
|
||||
+147
-101
@@ -42,7 +42,7 @@ namespace rocksdb {
|
||||
static uint64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 0; i < files.size() && files[i]; i++) {
|
||||
sum += files[i]->file_size;
|
||||
sum += files[i]->fd.GetFileSize();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@@ -150,18 +150,6 @@ bool SomeFileOverlapsRange(
|
||||
return !BeforeFile(ucmp, largest_user_key, files[index]);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Used for LevelFileNumIterator to pass "block handle" value,
|
||||
// which actually means file information in this iterator.
|
||||
// It contains subset of fields of FileMetaData, that is sufficient
|
||||
// for table cache to use.
|
||||
struct EncodedFileMetaData {
|
||||
uint64_t number; // file number
|
||||
uint64_t file_size; // file size
|
||||
TableReader* table_reader; // cached table reader
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// An internal iterator. For a given version/level pair, yields
|
||||
// information about the files in the level. For a given entry, key()
|
||||
// is the largest key that occurs in the file, and value() is an
|
||||
@@ -173,7 +161,8 @@ class Version::LevelFileNumIterator : public Iterator {
|
||||
const std::vector<FileMetaData*>* flist)
|
||||
: icmp_(icmp),
|
||||
flist_(flist),
|
||||
index_(flist->size()) { // Marks as invalid
|
||||
index_(flist->size()),
|
||||
current_value_(0, 0) { // Marks as invalid
|
||||
}
|
||||
virtual bool Valid() const {
|
||||
return index_ < flist_->size();
|
||||
@@ -204,18 +193,16 @@ class Version::LevelFileNumIterator : public Iterator {
|
||||
Slice value() const {
|
||||
assert(Valid());
|
||||
auto* file_meta = (*flist_)[index_];
|
||||
current_value_.number = file_meta->number;
|
||||
current_value_.file_size = file_meta->file_size;
|
||||
current_value_.table_reader = file_meta->table_reader;
|
||||
current_value_ = file_meta->fd;
|
||||
return Slice(reinterpret_cast<const char*>(¤t_value_),
|
||||
sizeof(EncodedFileMetaData));
|
||||
sizeof(FileDescriptor));
|
||||
}
|
||||
virtual Status status() const { return Status::OK(); }
|
||||
private:
|
||||
const InternalKeyComparator icmp_;
|
||||
const std::vector<FileMetaData*>* const flist_;
|
||||
uint32_t index_;
|
||||
mutable EncodedFileMetaData current_value_;
|
||||
mutable FileDescriptor current_value_;
|
||||
};
|
||||
|
||||
class Version::LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
@@ -230,17 +217,15 @@ class Version::LevelFileIteratorState : public TwoLevelIteratorState {
|
||||
for_compaction_(for_compaction) {}
|
||||
|
||||
Iterator* NewSecondaryIterator(const Slice& meta_handle) override {
|
||||
if (meta_handle.size() != sizeof(EncodedFileMetaData)) {
|
||||
if (meta_handle.size() != sizeof(FileDescriptor)) {
|
||||
return NewErrorIterator(
|
||||
Status::Corruption("FileReader invoked with unexpected value"));
|
||||
} else {
|
||||
const EncodedFileMetaData* encoded_meta =
|
||||
reinterpret_cast<const EncodedFileMetaData*>(meta_handle.data());
|
||||
FileMetaData meta(encoded_meta->number, encoded_meta->file_size);
|
||||
meta.table_reader = encoded_meta->table_reader;
|
||||
return table_cache_->NewIterator(read_options_, env_options_,
|
||||
icomparator_, meta, nullptr /* don't need reference to table*/,
|
||||
for_compaction_);
|
||||
const FileDescriptor* fd =
|
||||
reinterpret_cast<const FileDescriptor*>(meta_handle.data());
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, *fd,
|
||||
nullptr /* don't need reference to table*/, for_compaction_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,12 +246,12 @@ Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props) {
|
||||
auto options = cfd_->options();
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
for (const auto& file_meta : files_[level]) {
|
||||
auto fname = TableFileName(vset_->dbname_, file_meta->number);
|
||||
auto fname = TableFileName(vset_->dbname_, file_meta->fd.GetNumber());
|
||||
// 1. If the table is already present in table cache, load table
|
||||
// properties from there.
|
||||
std::shared_ptr<const TableProperties> table_properties;
|
||||
Status s = table_cache->GetTableProperties(
|
||||
vset_->storage_options_, cfd_->internal_comparator(), *file_meta,
|
||||
vset_->storage_options_, cfd_->internal_comparator(), file_meta->fd,
|
||||
&table_properties, true /* no io */);
|
||||
if (s.ok()) {
|
||||
props->insert({fname, table_properties});
|
||||
@@ -292,7 +277,7 @@ Status Version::GetPropertiesOfAllTables(TablePropertiesCollection* props) {
|
||||
// By setting the magic number to kInvalidTableMagicNumber, we can by
|
||||
// pass the magic number check in the footer.
|
||||
s = ReadTableProperties(
|
||||
file.get(), file_meta->file_size,
|
||||
file.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */,
|
||||
vset_->env_, options->info_log.get(), &raw_table_properties);
|
||||
if (!s.ok()) {
|
||||
@@ -315,7 +300,7 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (const FileMetaData* file : files_[0]) {
|
||||
iters->push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), *file));
|
||||
read_options, soptions, cfd_->internal_comparator(), file->fd));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
@@ -332,6 +317,32 @@ void Version::AddIterators(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
|
||||
void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder) {
|
||||
// Merge all level zero files together since they may overlap
|
||||
for (const FileMetaData* file : files_[0]) {
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file->fd, nullptr,
|
||||
false, merge_iter_builder->GetArena()));
|
||||
}
|
||||
|
||||
// For levels > 0, we can use a concatenating iterator that sequentially
|
||||
// walks through the non-overlapping files in the level, opening them
|
||||
// lazily.
|
||||
for (int level = 1; level < num_levels_; level++) {
|
||||
if (!files_[level].empty()) {
|
||||
merge_iter_builder->AddIterator(NewTwoLevelIterator(
|
||||
new LevelFileIteratorState(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), false /* for_compaction */,
|
||||
cfd_->options()->prefix_extractor != nullptr),
|
||||
new LevelFileNumIterator(cfd_->internal_comparator(), &files_[level]),
|
||||
merge_iter_builder->GetArena()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Callback from TableCache::Get()
|
||||
namespace {
|
||||
enum SaverState {
|
||||
@@ -435,7 +446,7 @@ static bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
|
||||
|
||||
namespace {
|
||||
bool NewestFirst(FileMetaData* a, FileMetaData* b) {
|
||||
return a->number > b->number;
|
||||
return a->fd.GetNumber() > b->fd.GetNumber();
|
||||
}
|
||||
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) {
|
||||
if (a->smallest_seqno != b->smallest_seqno) {
|
||||
@@ -454,7 +465,7 @@ bool BySmallestKey(FileMetaData* a, FileMetaData* b,
|
||||
return (r < 0);
|
||||
}
|
||||
// Break ties by file number
|
||||
return (a->number < b->number);
|
||||
return (a->fd.GetNumber() < b->fd.GetNumber());
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -472,12 +483,13 @@ Version::Version(ColumnFamilyData* cfd, VersionSet* vset,
|
||||
info_log_((cfd == nullptr) ? nullptr : cfd->options()->info_log.get()),
|
||||
db_statistics_((cfd == nullptr) ? nullptr
|
||||
: cfd->options()->statistics.get()),
|
||||
// cfd is nullptr if Version is dummy
|
||||
num_levels_(cfd == nullptr ? 0 : cfd->NumberLevels()),
|
||||
num_non_empty_levels_(num_levels_),
|
||||
vset_(vset),
|
||||
next_(this),
|
||||
prev_(this),
|
||||
refs_(0),
|
||||
// cfd is nullptr if Version is dummy
|
||||
num_levels_(cfd == nullptr ? 0 : cfd->NumberLevels()),
|
||||
files_(new std::vector<FileMetaData*>[num_levels_]),
|
||||
files_by_size_(num_levels_),
|
||||
next_file_to_compact_by_size_(num_levels_),
|
||||
@@ -525,7 +537,7 @@ void Version::Get(const ReadOptions& options,
|
||||
|
||||
int32_t search_left_bound = 0;
|
||||
int32_t search_right_bound = FileIndexer::kLevelMaxIndex;
|
||||
for (int level = 0; level < num_levels_; ++level) {
|
||||
for (int level = 0; level < num_non_empty_levels_; ++level) {
|
||||
int num_files = files_[level].size();
|
||||
if (num_files == 0) {
|
||||
// When current level is empty, the search bound generated from upper
|
||||
@@ -541,6 +553,16 @@ void Version::Get(const ReadOptions& options,
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prefetch table data to avoid cache miss if possible
|
||||
if (level == 0) {
|
||||
for (int i = 0; i < num_files; ++i) {
|
||||
auto* r = files_[0][i]->fd.table_reader;
|
||||
if (r) {
|
||||
r->Prepare(ikey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list of files to search in this level
|
||||
FileMetaData* const* files = &files_[level][0];
|
||||
|
||||
@@ -581,30 +603,46 @@ void Version::Get(const ReadOptions& options,
|
||||
|
||||
for (int32_t i = start_index; i < num_files;) {
|
||||
FileMetaData* f = files[i];
|
||||
// Check if key is within a file's range. If search left bound and right
|
||||
// bound point to the same find, we are sure key falls in range.
|
||||
assert(level == 0 || i == start_index ||
|
||||
user_comparator_->Compare(user_key, f->smallest.user_key()) <= 0);
|
||||
|
||||
int cmp_smallest = user_comparator_->Compare(user_key, f->smallest.user_key());
|
||||
int cmp_largest = -1;
|
||||
if (cmp_smallest >= 0) {
|
||||
cmp_largest = user_comparator_->Compare(user_key, f->largest.user_key());
|
||||
}
|
||||
|
||||
// Setup file search bound for the next level based on the comparison
|
||||
// results
|
||||
if (level > 0) {
|
||||
file_indexer_.GetNextLevelIndex(level, i, cmp_smallest, cmp_largest,
|
||||
&search_left_bound, &search_right_bound);
|
||||
}
|
||||
// Key falls out of current file's range
|
||||
if (cmp_smallest < 0 || cmp_largest > 0) {
|
||||
if (level == 0) {
|
||||
++i;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
// Do key range filtering of files or/and fractional cascading if:
|
||||
// (1) not all the files are in level 0, or
|
||||
// (2) there are more than 3 Level 0 files
|
||||
// If there are only 3 or less level 0 files in the system, we skip the
|
||||
// key range filtering. In this case, more likely, the system is highly
|
||||
// tuned to minimize number of tables queried by each query, so it is
|
||||
// unlikely that key range filtering is more efficient than querying the
|
||||
// files.
|
||||
if (num_non_empty_levels_ > 1 || num_files > 3) {
|
||||
// Check if key is within a file's range. If search left bound and right
|
||||
// bound point to the same find, we are sure key falls in range.
|
||||
assert(
|
||||
level == 0 || i == start_index
|
||||
|| user_comparator_->Compare(user_key, f->smallest.user_key())
|
||||
<= 0);
|
||||
|
||||
int cmp_smallest = user_comparator_->Compare(user_key,
|
||||
f->smallest.user_key());
|
||||
if (cmp_smallest >= 0) {
|
||||
cmp_largest = user_comparator_->Compare(user_key,
|
||||
f->largest.user_key());
|
||||
}
|
||||
|
||||
// Setup file search bound for the next level based on the comparison
|
||||
// results
|
||||
if (level > 0) {
|
||||
file_indexer_.GetNextLevelIndex(level, i, cmp_smallest, cmp_largest,
|
||||
&search_left_bound,
|
||||
&search_right_bound);
|
||||
}
|
||||
// Key falls out of current file's range
|
||||
if (cmp_smallest < 0 || cmp_largest > 0) {
|
||||
if (level == 0) {
|
||||
++i;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +665,7 @@ void Version::Get(const ReadOptions& options,
|
||||
prev_file = f;
|
||||
#endif
|
||||
bool tableIO = false;
|
||||
*status = table_cache_->Get(options, *internal_comparator_, *f, ikey,
|
||||
*status = table_cache_->Get(options, *internal_comparator_, f->fd, ikey,
|
||||
&saver, SaveValue, &tableIO, MarkKeyMayExist);
|
||||
// TODO: examine the behavior for corrupted key
|
||||
if (!status->ok()) {
|
||||
@@ -705,6 +743,12 @@ bool Version::UpdateStats(const GetStats& stats) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Version::PrepareApply(std::vector<uint64_t>& size_being_compacted) {
|
||||
ComputeCompactionScore(size_being_compacted);
|
||||
UpdateFilesBySize();
|
||||
UpdateNumNonEmptyLevels();
|
||||
}
|
||||
|
||||
void Version::ComputeCompactionScore(
|
||||
std::vector<uint64_t>& size_being_compacted) {
|
||||
double max_score = 0;
|
||||
@@ -734,7 +778,7 @@ void Version::ComputeCompactionScore(
|
||||
uint64_t total_size = 0;
|
||||
for (unsigned int i = 0; i < files_[level].size(); i++) {
|
||||
if (!files_[level][i]->being_compacted) {
|
||||
total_size += files_[level][i]->file_size;
|
||||
total_size += files_[level][i]->fd.GetFileSize();
|
||||
numfiles++;
|
||||
}
|
||||
}
|
||||
@@ -791,7 +835,7 @@ namespace {
|
||||
// In normal mode: descending size
|
||||
bool CompareSizeDescending(const Version::Fsize& first,
|
||||
const Version::Fsize& second) {
|
||||
return (first.file->file_size > second.file->file_size);
|
||||
return (first.file->fd.GetFileSize() > second.file->fd.GetFileSize());
|
||||
}
|
||||
// A static compator used to sort files based on their seqno
|
||||
// In universal style : descending seqno
|
||||
@@ -807,6 +851,17 @@ bool CompareSeqnoDescending(const Version::Fsize& first,
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void Version::UpdateNumNonEmptyLevels() {
|
||||
num_non_empty_levels_ = num_levels_;
|
||||
for (int i = num_levels_ - 1; i >= 0; i--) {
|
||||
if (files_[i].size() != 0) {
|
||||
return;
|
||||
} else {
|
||||
num_non_empty_levels_ = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Version::UpdateFilesBySize() {
|
||||
if (cfd_->options()->compaction_style == kCompactionStyleFIFO) {
|
||||
// don't need this
|
||||
@@ -1175,10 +1230,10 @@ const char* Version::LevelFileSummary(FileSummaryStorage* scratch,
|
||||
for (const auto& f : files_[level]) {
|
||||
int sz = sizeof(scratch->buffer) - len;
|
||||
char sztxt[16];
|
||||
AppendHumanBytes(f->file_size, sztxt, 16);
|
||||
AppendHumanBytes(f->fd.GetFileSize(), sztxt, 16);
|
||||
int ret = snprintf(scratch->buffer + len, sz,
|
||||
"#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ", f->number,
|
||||
f->smallest_seqno, sztxt,
|
||||
"#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ",
|
||||
f->fd.GetNumber(), f->smallest_seqno, sztxt,
|
||||
static_cast<int>(f->being_compacted));
|
||||
if (ret < 0 || ret >= sz)
|
||||
break;
|
||||
@@ -1211,7 +1266,7 @@ void Version::AddLiveFiles(std::set<uint64_t>* live) {
|
||||
for (int level = 0; level < NumberLevels(); level++) {
|
||||
const std::vector<FileMetaData*>& files = files_[level];
|
||||
for (const auto& file : files) {
|
||||
live->insert(file->number);
|
||||
live->insert(file->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1231,9 +1286,9 @@ std::string Version::DebugString(bool hex) const {
|
||||
const std::vector<FileMetaData*>& files = files_[level];
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
r.push_back(' ');
|
||||
AppendNumberTo(&r, files[i]->number);
|
||||
AppendNumberTo(&r, files[i]->fd.GetNumber());
|
||||
r.push_back(':');
|
||||
AppendNumberTo(&r, files[i]->file_size);
|
||||
AppendNumberTo(&r, files[i]->fd.GetFileSize());
|
||||
r.append("[");
|
||||
r.append(files[i]->smallest.DebugString(hex));
|
||||
r.append(" .. ");
|
||||
@@ -1382,7 +1437,7 @@ class VersionSet::Builder {
|
||||
const std::vector<FileMetaData*>& base_files = base_->files_[l];
|
||||
for (unsigned int i = 0; i < base_files.size(); i++) {
|
||||
FileMetaData* f = base_files[i];
|
||||
if (f->number == number) {
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -1396,7 +1451,7 @@ class VersionSet::Builder {
|
||||
for (FileSet::const_iterator added_iter = added->begin();
|
||||
added_iter != added->end(); ++added_iter) {
|
||||
FileMetaData* f = *added_iter;
|
||||
if (f->number == number) {
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -1409,7 +1464,7 @@ class VersionSet::Builder {
|
||||
for (FileSet::const_iterator added_iter = added->begin();
|
||||
added_iter != added->end(); ++added_iter) {
|
||||
FileMetaData* f = *added_iter;
|
||||
if (f->number == number) {
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -1451,10 +1506,10 @@ class VersionSet::Builder {
|
||||
// same as the compaction of 40KB of data. We are a little
|
||||
// conservative and allow approximately one seek for every 16KB
|
||||
// of data before triggering a compaction.
|
||||
f->allowed_seeks = (f->file_size / 16384);
|
||||
f->allowed_seeks = (f->fd.GetFileSize() / 16384);
|
||||
if (f->allowed_seeks < 100) f->allowed_seeks = 100;
|
||||
|
||||
levels_[level].deleted_files.erase(f->number);
|
||||
levels_[level].deleted_files.erase(f->fd.GetNumber());
|
||||
levels_[level].added_files->insert(f);
|
||||
}
|
||||
}
|
||||
@@ -1503,11 +1558,10 @@ class VersionSet::Builder {
|
||||
bool table_io;
|
||||
cfd_->table_cache()->FindTable(
|
||||
base_->vset_->storage_options_, cfd_->internal_comparator(),
|
||||
file_meta->number, file_meta->file_size,
|
||||
&file_meta->table_reader_handle, &table_io, false);
|
||||
file_meta->fd, &file_meta->table_reader_handle, &table_io, false);
|
||||
if (file_meta->table_reader_handle != nullptr) {
|
||||
// Load table_reader
|
||||
file_meta->table_reader =
|
||||
file_meta->fd.table_reader =
|
||||
cfd_->table_cache()->GetTableReaderFromHandle(
|
||||
file_meta->table_reader_handle);
|
||||
}
|
||||
@@ -1516,7 +1570,7 @@ class VersionSet::Builder {
|
||||
}
|
||||
|
||||
void MaybeAddFile(Version* v, int level, FileMetaData* f) {
|
||||
if (levels_[level].deleted_files.count(f->number) > 0) {
|
||||
if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) {
|
||||
// File is deleted: do nothing
|
||||
} else {
|
||||
auto* files = &v->files_[level];
|
||||
@@ -1698,10 +1752,8 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
}
|
||||
|
||||
if (!edit->IsColumnFamilyManipulation()) {
|
||||
// The calls to ComputeCompactionScore and UpdateFilesBySize are cpu-heavy
|
||||
// and is best called outside the mutex.
|
||||
v->ComputeCompactionScore(size_being_compacted);
|
||||
v->UpdateFilesBySize();
|
||||
// This is cpu-heavy operations, which should be called outside mutex.
|
||||
v->PrepareApply(size_being_compacted);
|
||||
}
|
||||
|
||||
// Write new record to MANIFEST log
|
||||
@@ -2118,8 +2170,7 @@ Status VersionSet::Recover(
|
||||
// Install recovered version
|
||||
std::vector<uint64_t> size_being_compacted(v->NumberLevels() - 1);
|
||||
cfd->compaction_picker()->SizeBeingCompacted(size_being_compacted);
|
||||
v->ComputeCompactionScore(size_being_compacted);
|
||||
v->UpdateFilesBySize();
|
||||
v->PrepareApply(size_being_compacted);
|
||||
AppendVersion(cfd, v);
|
||||
}
|
||||
|
||||
@@ -2452,8 +2503,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
builder->SaveTo(v);
|
||||
std::vector<uint64_t> size_being_compacted(v->NumberLevels() - 1);
|
||||
cfd->compaction_picker()->SizeBeingCompacted(size_being_compacted);
|
||||
v->ComputeCompactionScore(size_being_compacted);
|
||||
v->UpdateFilesBySize();
|
||||
v->PrepareApply(size_being_compacted);
|
||||
delete builder;
|
||||
|
||||
printf("--------------- Column family \"%s\" (ID %u) --------------\n",
|
||||
@@ -2526,12 +2576,8 @@ Status VersionSet::WriteSnapshot(log::Writer* log) {
|
||||
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
for (const auto& f : cfd->current()->files_[level]) {
|
||||
edit.AddFile(level,
|
||||
f->number,
|
||||
f->file_size,
|
||||
f->smallest,
|
||||
f->largest,
|
||||
f->smallest_seqno,
|
||||
edit.AddFile(level, f->fd.GetNumber(), f->fd.GetFileSize(),
|
||||
f->smallest, f->largest, f->smallest_seqno,
|
||||
f->largest_seqno);
|
||||
}
|
||||
}
|
||||
@@ -2587,7 +2633,7 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
|
||||
if (v->cfd_->internal_comparator().Compare(files[i]->largest, ikey) <=
|
||||
0) {
|
||||
// Entire file is before "ikey", so just add the file size
|
||||
result += files[i]->file_size;
|
||||
result += files[i]->fd.GetFileSize();
|
||||
} else if (v->cfd_->internal_comparator().Compare(files[i]->smallest,
|
||||
ikey) > 0) {
|
||||
// Entire file is after "ikey", so ignore
|
||||
@@ -2603,7 +2649,7 @@ uint64_t VersionSet::ApproximateOffsetOf(Version* v, const InternalKey& ikey) {
|
||||
TableReader* table_reader_ptr;
|
||||
Iterator* iter = v->cfd_->table_cache()->NewIterator(
|
||||
ReadOptions(), storage_options_, v->cfd_->internal_comparator(),
|
||||
*(files[i]), &table_reader_ptr);
|
||||
files[i]->fd, &table_reader_ptr);
|
||||
if (table_reader_ptr != nullptr) {
|
||||
result += table_reader_ptr->ApproximateOffsetOf(ikey.Encode());
|
||||
}
|
||||
@@ -2636,7 +2682,7 @@ void VersionSet::AddLiveFiles(std::vector<uint64_t>* live_list) {
|
||||
v = v->next_) {
|
||||
for (int level = 0; level < v->NumberLevels(); level++) {
|
||||
for (const auto& f : v->files_[level]) {
|
||||
live_list->push_back(f->number);
|
||||
live_list->push_back(f->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2662,7 +2708,7 @@ Iterator* VersionSet::MakeInputIterator(Compaction* c) {
|
||||
for (const auto& file : *c->inputs(which)) {
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, storage_options_compactions_,
|
||||
cfd->internal_comparator(), *file, nullptr,
|
||||
cfd->internal_comparator(), file->fd, nullptr,
|
||||
true /* for compaction */);
|
||||
}
|
||||
} else {
|
||||
@@ -2697,13 +2743,13 @@ bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) {
|
||||
// verify files in level
|
||||
int level = c->level();
|
||||
for (int i = 0; i < c->num_input_files(0); i++) {
|
||||
uint64_t number = c->input(0,i)->number;
|
||||
uint64_t number = c->input(0, i)->fd.GetNumber();
|
||||
|
||||
// look for this file in the current version
|
||||
bool found = false;
|
||||
for (unsigned int j = 0; j < version->files_[level].size(); j++) {
|
||||
FileMetaData* f = version->files_[level][j];
|
||||
if (f->number == number) {
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -2715,13 +2761,13 @@ bool VersionSet::VerifyCompactionFileConsistency(Compaction* c) {
|
||||
// verify level+1 files
|
||||
level++;
|
||||
for (int i = 0; i < c->num_input_files(1); i++) {
|
||||
uint64_t number = c->input(1,i)->number;
|
||||
uint64_t number = c->input(1, i)->fd.GetNumber();
|
||||
|
||||
// look for this file in the current version
|
||||
bool found = false;
|
||||
for (unsigned int j = 0; j < version->files_[level].size(); j++) {
|
||||
FileMetaData* f = version->files_[level][j];
|
||||
if (f->number == number) {
|
||||
if (f->fd.GetNumber() == number) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -2741,7 +2787,7 @@ Status VersionSet::GetMetadataForFile(uint64_t number, int* filelevel,
|
||||
Version* version = cfd_iter->current();
|
||||
for (int level = 0; level < version->NumberLevels(); level++) {
|
||||
for (const auto& file : version->files_[level]) {
|
||||
if (file->number == number) {
|
||||
if (file->fd.GetNumber() == number) {
|
||||
*meta = file;
|
||||
*filelevel = level;
|
||||
*cfd = cfd_iter;
|
||||
@@ -2759,9 +2805,9 @@ void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
for (const auto& file : cfd->current()->files_[level]) {
|
||||
LiveFileMetaData filemetadata;
|
||||
filemetadata.column_family_name = cfd->GetName();
|
||||
filemetadata.name = TableFileName("", file->number);
|
||||
filemetadata.name = TableFileName("", file->fd.GetNumber());
|
||||
filemetadata.level = level;
|
||||
filemetadata.size = file->file_size;
|
||||
filemetadata.size = file->fd.GetFileSize();
|
||||
filemetadata.smallestkey = file->smallest.user_key().ToString();
|
||||
filemetadata.largestkey = file->largest.user_key().ToString();
|
||||
filemetadata.smallest_seqno = file->smallest_seqno;
|
||||
|
||||
+16
-1
@@ -51,6 +51,7 @@ class MergeContext;
|
||||
class ColumnFamilyData;
|
||||
class ColumnFamilySet;
|
||||
class TableCache;
|
||||
class MergeIteratorBuilder;
|
||||
|
||||
// Return the smallest index i such that files[i]->largest >= key.
|
||||
// Return files.size() if there is no such file.
|
||||
@@ -80,6 +81,9 @@ class Version {
|
||||
void AddIterators(const ReadOptions&, const EnvOptions& soptions,
|
||||
std::vector<Iterator*>* iters);
|
||||
|
||||
void AddIterators(const ReadOptions&, const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merger_iter_builder);
|
||||
|
||||
// Lookup the value for key. If found, store it in *val and
|
||||
// return OK. Else return a non-OK status. Fills *stats.
|
||||
// Uses *operands to store merge_operator operations to apply later
|
||||
@@ -88,6 +92,7 @@ class Version {
|
||||
FileMetaData* seek_file;
|
||||
int seek_file_level;
|
||||
};
|
||||
|
||||
void Get(const ReadOptions&, const LookupKey& key, std::string* val,
|
||||
Status* status, MergeContext* merge_context, GetStats* stats,
|
||||
bool* value_found = nullptr);
|
||||
@@ -103,6 +108,10 @@ class Version {
|
||||
// a lock. Once a version is saved to current_, call only with mutex held
|
||||
void ComputeCompactionScore(std::vector<uint64_t>& size_being_compacted);
|
||||
|
||||
// Update scores, pre-calculated variables. It needs to be called before
|
||||
// applying the version to the version set.
|
||||
void PrepareApply(std::vector<uint64_t>& size_being_compacted);
|
||||
|
||||
// Reference count management (so Versions do not disappear out from
|
||||
// under live iterators)
|
||||
void Ref();
|
||||
@@ -218,6 +227,7 @@ class Version {
|
||||
friend class LevelCompactionPicker;
|
||||
friend class UniversalCompactionPicker;
|
||||
friend class FIFOCompactionPicker;
|
||||
friend class ForwardIterator;
|
||||
|
||||
class LevelFileNumIterator;
|
||||
class LevelFileIteratorState;
|
||||
@@ -225,6 +235,9 @@ class Version {
|
||||
bool PrefixMayMatch(const ReadOptions& options, Iterator* level_iter,
|
||||
const Slice& internal_prefix) const;
|
||||
|
||||
// Update num_non_empty_levels_.
|
||||
void UpdateNumNonEmptyLevels();
|
||||
|
||||
// Sort all files for this version based on their file size and
|
||||
// record results in files_by_size_. The largest files are listed first.
|
||||
void UpdateFilesBySize();
|
||||
@@ -236,11 +249,13 @@ class Version {
|
||||
const MergeOperator* merge_operator_;
|
||||
Logger* info_log_;
|
||||
Statistics* db_statistics_;
|
||||
int num_levels_; // Number of levels
|
||||
int num_non_empty_levels_; // Number of levels. Any level larger than it
|
||||
// is guaranteed to be empty.
|
||||
VersionSet* vset_; // VersionSet to which this Version belongs
|
||||
Version* next_; // Next version in linked list
|
||||
Version* prev_; // Previous version in linked list
|
||||
int refs_; // Number of live refs to this version
|
||||
int num_levels_; // Number of levels
|
||||
|
||||
// List of files per level, files in each level are arranged
|
||||
// in increasing order of keys
|
||||
|
||||
@@ -31,7 +31,7 @@ class FindFileTest {
|
||||
SequenceNumber smallest_seq = 100,
|
||||
SequenceNumber largest_seq = 100) {
|
||||
FileMetaData* f = new FileMetaData;
|
||||
f->number = files_.size() + 1;
|
||||
f->fd = FileDescriptor(files_.size() + 1, 0);
|
||||
f->smallest = InternalKey(smallest, smallest_seq, kTypeValue);
|
||||
f->largest = InternalKey(largest, largest_seq, kTypeValue);
|
||||
files_.push_back(f);
|
||||
|
||||
@@ -56,6 +56,7 @@ extern "C" {
|
||||
|
||||
typedef struct rocksdb_t rocksdb_t;
|
||||
typedef struct rocksdb_cache_t rocksdb_cache_t;
|
||||
typedef struct rocksdb_compactionfilter_t rocksdb_compactionfilter_t;
|
||||
typedef struct rocksdb_comparator_t rocksdb_comparator_t;
|
||||
typedef struct rocksdb_env_t rocksdb_env_t;
|
||||
typedef struct rocksdb_filelock_t rocksdb_filelock_t;
|
||||
@@ -229,6 +230,9 @@ extern const char* rocksdb_writebatch_data(rocksdb_writebatch_t*, size_t *size);
|
||||
|
||||
extern rocksdb_options_t* rocksdb_options_create();
|
||||
extern void rocksdb_options_destroy(rocksdb_options_t*);
|
||||
extern void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_compactionfilter_t*);
|
||||
extern void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t*,
|
||||
rocksdb_comparator_t*);
|
||||
@@ -401,6 +405,22 @@ enum {
|
||||
};
|
||||
extern void rocksdb_options_set_compaction_style(rocksdb_options_t*, int);
|
||||
extern void rocksdb_options_set_universal_compaction_options(rocksdb_options_t*, rocksdb_universal_compaction_options_t*);
|
||||
|
||||
/* Compaction Filter */
|
||||
|
||||
extern rocksdb_compactionfilter_t* rocksdb_compactionfilter_create(
|
||||
void* state,
|
||||
void (*destructor)(void*),
|
||||
unsigned char (*filter)(
|
||||
void*,
|
||||
int level,
|
||||
const char* key, size_t key_length,
|
||||
const char* existing_value, size_t value_length,
|
||||
char** new_value, size_t *new_value_length,
|
||||
unsigned char* value_changed),
|
||||
const char* (*name)(void*));
|
||||
extern void rocksdb_compactionfilter_destroy(rocksdb_compactionfilter_t*);
|
||||
|
||||
/* Comparator */
|
||||
|
||||
extern rocksdb_comparator_t* rocksdb_comparator_create(
|
||||
|
||||
@@ -142,16 +142,28 @@ class MemTableRep {
|
||||
};
|
||||
|
||||
// Return an iterator over the keys in this representation.
|
||||
virtual Iterator* GetIterator() = 0;
|
||||
// arena: If not null, the arena needs to be used to allocate the Iterator.
|
||||
// When destroying the iterator, the caller will not call "delete"
|
||||
// but Iterator::~Iterator() directly. The destructor needs to destroy
|
||||
// all the states but those allocated in arena.
|
||||
virtual Iterator* GetIterator(Arena* arena = nullptr) = 0;
|
||||
|
||||
// Return an iterator over at least the keys with the specified user key. The
|
||||
// iterator may also allow access to other keys, but doesn't have to. Default:
|
||||
// GetIterator().
|
||||
virtual Iterator* GetIterator(const Slice& user_key) { return GetIterator(); }
|
||||
virtual Iterator* GetIterator(const Slice& user_key) {
|
||||
return GetIterator(nullptr);
|
||||
}
|
||||
|
||||
// Return an iterator that has a special Seek semantics. The result of
|
||||
// a Seek might only include keys with the same prefix as the target key.
|
||||
virtual Iterator* GetDynamicPrefixIterator() { return GetIterator(); }
|
||||
// arena: If not null, the arena needs to be used to allocate the Iterator.
|
||||
// When destroying the iterator, the caller will not call "delete"
|
||||
// but Iterator::~Iterator() directly. The destructor needs to destroy
|
||||
// all the states but those allocated in arena.
|
||||
virtual Iterator* GetDynamicPrefixIterator(Arena* arena = nullptr) {
|
||||
return GetIterator(arena);
|
||||
}
|
||||
|
||||
// Return true if the current MemTableRep supports merge operator.
|
||||
// Default: true
|
||||
@@ -231,8 +243,14 @@ extern MemTableRepFactory* NewHashSkipListRepFactory(
|
||||
// huge pages for it to be allocated, like:
|
||||
// sysctl -w vm.nr_hugepages=20
|
||||
// See linux doc Documentation/vm/hugetlbpage.txt
|
||||
// @bucket_entries_logging_threshold: if number of entries in one bucket
|
||||
// exceeds this number, log about it.
|
||||
// @if_log_bucket_dist_when_flash: if true, log distribution of number of
|
||||
// entries when flushing.
|
||||
extern MemTableRepFactory* NewHashLinkListRepFactory(
|
||||
size_t bucket_count = 50000, size_t huge_page_tlb_size = 0);
|
||||
size_t bucket_count = 50000, size_t huge_page_tlb_size = 0,
|
||||
int bucket_entries_logging_threshold = 4096,
|
||||
bool if_log_bucket_dist_when_flash = true);
|
||||
|
||||
// This factory creates a cuckoo-hashing based mem-table representation.
|
||||
// Cuckoo-hash is a closed-hash strategy, in which all key/value pairs
|
||||
|
||||
@@ -184,8 +184,9 @@ struct ColumnFamilyOptions {
|
||||
size_t write_buffer_size;
|
||||
|
||||
// The maximum number of write buffers that are built up in memory.
|
||||
// The default is 2, so that when 1 write buffer is being flushed to
|
||||
// storage, new writes can continue to the other write buffer.
|
||||
// The default and the minimum number is 2, so that when 1 write buffer
|
||||
// is being flushed to storage, new writes can continue to the other
|
||||
// write buffer.
|
||||
// Default: 2
|
||||
int max_write_buffer_number;
|
||||
|
||||
@@ -547,12 +548,9 @@ struct ColumnFamilyOptions {
|
||||
|
||||
// Control locality of bloom filter probes to improve cache miss rate.
|
||||
// This option only applies to memtable prefix bloom and plaintable
|
||||
// prefix bloom. It essentially limits the max number of cache lines each
|
||||
// bloom filter check can touch.
|
||||
// This optimization is turned off when set to 0. The number should never
|
||||
// be greater than number of probes. This option can boost performance
|
||||
// for in-memory workload but should use with care since it can cause
|
||||
// higher false positive rate.
|
||||
// prefix bloom. It essentially limits every bloom checking to one cache line.
|
||||
// This optimization is turned off when set to 0, and positive number to turn
|
||||
// it on.
|
||||
// Default: 0
|
||||
uint32_t bloom_locality;
|
||||
|
||||
@@ -598,6 +596,10 @@ struct DBOptions {
|
||||
// Default: false
|
||||
bool create_if_missing;
|
||||
|
||||
// If true, missing column families will be automatically created.
|
||||
// Default: false
|
||||
bool create_missing_column_families;
|
||||
|
||||
// If true, an error is raised if the database already exists.
|
||||
// Default: false
|
||||
bool error_if_exists;
|
||||
|
||||
@@ -125,6 +125,7 @@ enum Tickers {
|
||||
NUMBER_SUPERVERSION_ACQUIRES,
|
||||
NUMBER_SUPERVERSION_RELEASES,
|
||||
NUMBER_SUPERVERSION_CLEANUPS,
|
||||
NUMBER_BLOCK_NOT_COMPRESSED,
|
||||
TICKER_ENUM_MAX
|
||||
};
|
||||
|
||||
@@ -183,6 +184,7 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
|
||||
{NUMBER_SUPERVERSION_ACQUIRES, "rocksdb.number.superversion_acquires"},
|
||||
{NUMBER_SUPERVERSION_RELEASES, "rocksdb.number.superversion_releases"},
|
||||
{NUMBER_SUPERVERSION_CLEANUPS, "rocksdb.number.superversion_cleanups"},
|
||||
{NUMBER_BLOCK_NOT_COMPRESSED, "rocksdb.number.block.not_compressed"},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+59
-2
@@ -74,6 +74,12 @@ struct BlockBasedTableOptions {
|
||||
|
||||
IndexType index_type = kBinarySearch;
|
||||
|
||||
// Influence the behavior when kHashSearch is used.
|
||||
// if false, stores a precise prefix to block range mapping
|
||||
// if true, does not store prefix and allows prefix hash collision
|
||||
// (less memory consumption)
|
||||
bool hash_index_allow_collision = true;
|
||||
|
||||
// Use the specified checksum type. Newly created table files will be
|
||||
// protected with this checksum type. Old table files will still be readable,
|
||||
// even though they have different checksum type.
|
||||
@@ -91,6 +97,30 @@ extern TableFactory* NewBlockBasedTableFactory(
|
||||
const BlockBasedTableOptions& table_options = BlockBasedTableOptions());
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
enum EncodingType : char {
|
||||
// Always write full keys without any special encoding.
|
||||
kPlain,
|
||||
// Find opportunity to write the same prefix once for multiple rows.
|
||||
// In some cases, when a key follows a previous key with the same prefix,
|
||||
// instead of writing out the full key, it just writes out the size of the
|
||||
// shared prefix, as well as other bytes, to save some bytes.
|
||||
//
|
||||
// When using this option, the user is required to use the same prefix
|
||||
// extractor to make sure the same prefix will be extracted from the same key.
|
||||
// The Name() value of the prefix extractor will be stored in the file. When
|
||||
// reopening the file, the name of the options.prefix_extractor given will be
|
||||
// bitwise compared to the prefix extractors stored in the file. An error
|
||||
// will be returned if the two don't match.
|
||||
kPrefix,
|
||||
};
|
||||
|
||||
// Table Properties that are specific to plain table properties.
|
||||
struct PlainTablePropertyNames {
|
||||
static const std::string kPrefixExtractorName;
|
||||
static const std::string kEncodingType;
|
||||
};
|
||||
|
||||
// -- Plain Table with prefix-only seek
|
||||
// For this factory, you need to set Options.prefix_extrator properly to make it
|
||||
// work. Look-up will starts with prefix hash lookup for key prefix. Inside the
|
||||
@@ -107,11 +137,22 @@ extern TableFactory* NewBlockBasedTableFactory(
|
||||
// in the hash table
|
||||
// @index_sparseness: inside each prefix, need to build one index record for how
|
||||
// many keys for binary search inside each hash bucket.
|
||||
// For encoding type kPrefix, the value will be used when
|
||||
// writing to determine an interval to rewrite the full key.
|
||||
// It will also be used as a suggestion and satisfied when
|
||||
// possible.
|
||||
// @huge_page_tlb_size: if <=0, allocate hash indexes and blooms from malloc.
|
||||
// Otherwise from huge page TLB. The user needs to reserve
|
||||
// huge pages for it to be allocated, like:
|
||||
// sysctl -w vm.nr_hugepages=20
|
||||
// See linux doc Documentation/vm/hugetlbpage.txt
|
||||
// @encoding_type: how to encode the keys. See enum EncodingType above for
|
||||
// the choices. The value will determine how to encode keys
|
||||
// when writing to a new SST file. This value will be stored
|
||||
// inside the SST file which will be used when reading from the
|
||||
// file, which makes it possible for users to choose different
|
||||
// encoding type when reopening a DB. Files with different
|
||||
// encoding types can co-exist in the same DB and can be read.
|
||||
|
||||
const uint32_t kPlainTableVariableLength = 0;
|
||||
extern TableFactory* NewPlainTableFactory(uint32_t user_key_len =
|
||||
@@ -119,7 +160,8 @@ extern TableFactory* NewPlainTableFactory(uint32_t user_key_len =
|
||||
int bloom_bits_per_prefix = 10,
|
||||
double hash_table_ratio = 0.75,
|
||||
size_t index_sparseness = 16,
|
||||
size_t huge_page_tlb_size = 0);
|
||||
size_t huge_page_tlb_size = 0,
|
||||
EncodingType encoding_type = kPlain);
|
||||
|
||||
// -- Plain Table
|
||||
// This factory of plain table ignores Options.prefix_extractor and assumes no
|
||||
@@ -141,7 +183,7 @@ extern TableFactory* NewPlainTableFactory(uint32_t user_key_len =
|
||||
extern TableFactory* NewTotalOrderPlainTableFactory(
|
||||
uint32_t user_key_len = kPlainTableVariableLength,
|
||||
int bloom_bits_per_key = 0, size_t index_sparseness = 16,
|
||||
size_t huge_page_tlb_size = 0);
|
||||
size_t huge_page_tlb_size = 0, bool full_scan_mode = false);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -203,4 +245,19 @@ class TableFactory {
|
||||
WritableFile* file, CompressionType compression_type) const = 0;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Create a special table factory that can open both of block based table format
|
||||
// and plain table, based on setting inside the SST files. It should be used to
|
||||
// convert a DB from one table format to another.
|
||||
// @table_factory_to_write: the table factory used when writing to new files.
|
||||
// @block_based_table_factory: block based table factory to use. If NULL, use
|
||||
// a default one.
|
||||
// @plain_table_factory: plain table factory to use. If NULL, use a default one.
|
||||
extern TableFactory* NewAdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write = nullptr,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory = nullptr,
|
||||
std::shared_ptr<TableFactory> plain_table_factory = nullptr);
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Also update Makefile if you change these
|
||||
#define ROCKSDB_MAJOR 3
|
||||
#define ROCKSDB_MINOR 1
|
||||
#define ROCKSDB_MINOR 2
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB org.rocksdb.Options org.rocksdb.WriteBatch org.rocksdb.WriteBatchInternal org.rocksdb.WriteBatchTest org.rocksdb.WriteOptions org.rocksdb.BackupableDB org.rocksdb.BackupableDBOptions org.rocksdb.Statistics org.rocksdb.RocksIterator org.rocksdb.VectorMemTableConfig org.rocksdb.SkipListMemTableConfig org.rocksdb.HashLinkedListMemTableConfig org.rocksdb.HashSkipListMemTableConfig org.rocksdb.PlainTableConfig org.rocksdb.ReadOptions org.rocksdb.Filter org.rocksdb.BloomFilter
|
||||
NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB org.rocksdb.Options org.rocksdb.WriteBatch org.rocksdb.WriteBatchInternal org.rocksdb.WriteBatchTest org.rocksdb.WriteOptions org.rocksdb.BackupableDB org.rocksdb.BackupableDBOptions org.rocksdb.Statistics org.rocksdb.RocksIterator org.rocksdb.VectorMemTableConfig org.rocksdb.SkipListMemTableConfig org.rocksdb.HashLinkedListMemTableConfig org.rocksdb.HashSkipListMemTableConfig org.rocksdb.PlainTableConfig org.rocksdb.ReadOptions org.rocksdb.Filter org.rocksdb.BloomFilter org.rocksdb.RestoreOptions org.rocksdb.RestoreBackupableDB org.rocksdb.RocksEnv
|
||||
|
||||
NATIVE_INCLUDE = ./include
|
||||
ROCKSDB_JAR = rocksdbjni.jar
|
||||
|
||||
|
||||
@@ -25,11 +25,14 @@ public class BackupableDB extends RocksDB {
|
||||
public static BackupableDB open(
|
||||
Options opt, BackupableDBOptions bopt, String db_path)
|
||||
throws RocksDBException {
|
||||
// since BackupableDB c++ will handle the life cycle of
|
||||
// the returned RocksDB of RocksDB.open(), here we store
|
||||
// it as a BackupableDB member variable to avoid GC.
|
||||
BackupableDB bdb = new BackupableDB(RocksDB.open(opt, db_path));
|
||||
bdb.open(bdb.db_.nativeHandle_, bopt.nativeHandle_);
|
||||
|
||||
RocksDB db = RocksDB.open(opt, db_path);
|
||||
BackupableDB bdb = new BackupableDB();
|
||||
bdb.open(db.nativeHandle_, bopt.nativeHandle_);
|
||||
|
||||
// Prevent the RocksDB object from attempting to delete
|
||||
// the underly C++ DB object.
|
||||
db.disOwnNativeHandle();
|
||||
|
||||
return bdb;
|
||||
}
|
||||
@@ -64,9 +67,8 @@ public class BackupableDB extends RocksDB {
|
||||
* A protected construction that will be used in the static factory
|
||||
* method BackupableDB.open().
|
||||
*/
|
||||
protected BackupableDB(RocksDB db) {
|
||||
protected BackupableDB() {
|
||||
super();
|
||||
db_ = db;
|
||||
}
|
||||
|
||||
@Override protected void finalize() {
|
||||
@@ -75,6 +77,4 @@ public class BackupableDB extends RocksDB {
|
||||
|
||||
protected native void open(long rocksDBHandle, long backupDBOptionsHandle);
|
||||
protected native void createNewBackup(long handle, boolean flag);
|
||||
|
||||
private final RocksDB db_;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,39 @@ package org.rocksdb;
|
||||
*
|
||||
* Note that dispose() must be called before an Options instance
|
||||
* become out-of-scope to release the allocated memory in c++.
|
||||
*
|
||||
* @param path Where to keep the backup files. Has to be different than dbname.
|
||||
Best to set this to dbname_ + "/backups"
|
||||
* @param shareTableFiles If share_table_files == true, backup will assume that
|
||||
* table files with same name have the same contents. This enables
|
||||
* incremental backups and avoids unnecessary data copies. If
|
||||
* share_table_files == false, each backup will be on its own and will not
|
||||
* share any data with other backups. default: true
|
||||
* @param sync If sync == true, we can guarantee you'll get consistent backup
|
||||
* even on a machine crash/reboot. Backup process is slower with sync
|
||||
* enabled. If sync == false, we don't guarantee anything on machine reboot.
|
||||
* However, chances are some of the backups are consistent. Default: true
|
||||
* @param destroyOldData If true, it will delete whatever backups there are
|
||||
* already. Default: false
|
||||
* @param backupLogFiles If false, we won't backup log files. This option can be
|
||||
* useful for backing up in-memory databases where log file are persisted,
|
||||
* but table files are in memory. Default: true
|
||||
* @param backupRateLimit Max bytes that can be transferred in a second during
|
||||
* backup. If 0 or negative, then go as fast as you can. Default: 0
|
||||
* @param restoreRateLimit Max bytes that can be transferred in a second during
|
||||
* restore. If 0 or negative, then go as fast as you can. Default: 0
|
||||
*/
|
||||
public class BackupableDBOptions extends RocksObject {
|
||||
public BackupableDBOptions(String path) {
|
||||
public BackupableDBOptions(String path, boolean shareTableFiles, boolean sync,
|
||||
boolean destroyOldData, boolean backupLogFiles, long backupRateLimit,
|
||||
long restoreRateLimit) {
|
||||
super();
|
||||
newBackupableDBOptions(path);
|
||||
|
||||
backupRateLimit = (backupRateLimit <= 0) ? 0 : backupRateLimit;
|
||||
restoreRateLimit = (restoreRateLimit <= 0) ? 0 : restoreRateLimit;
|
||||
|
||||
newBackupableDBOptions(path, shareTableFiles, sync, destroyOldData,
|
||||
backupLogFiles, backupRateLimit, restoreRateLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,13 +60,14 @@ public class BackupableDBOptions extends RocksObject {
|
||||
* Release the memory allocated for the current instance
|
||||
* in the c++ side.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose(nativeHandle_);
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
private native void newBackupableDBOptions(String path);
|
||||
private native void newBackupableDBOptions(String path,
|
||||
boolean shareTableFiles, boolean sync, boolean destroyOldData,
|
||||
boolean backupLogFiles, long backupRateLimit, long restoreRateLimit);
|
||||
private native String backupDir(long handle);
|
||||
private native void dispose(long handle);
|
||||
private native void disposeInternal(long handle);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,10 @@ public abstract class Filter extends RocksObject {
|
||||
* RocksDB instances referencing the filter are closed.
|
||||
* Otherwise an undefined behavior will occur.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose0(nativeHandle_);
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
private native void dispose0(long handle);
|
||||
private native void disposeInternal(long handle);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public class Options extends RocksObject {
|
||||
super();
|
||||
cacheSize_ = DEFAULT_CACHE_SIZE;
|
||||
newOptions();
|
||||
env_ = RocksEnv.getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,24 @@ public class Options extends RocksObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified object to interact with the environment,
|
||||
* e.g. to read/write files, schedule background work, etc.
|
||||
* Default: RocksEnv.getDefault()
|
||||
*/
|
||||
public Options setEnv(RocksEnv env) {
|
||||
assert(isInitialized());
|
||||
setEnv(nativeHandle_, env.nativeHandle_);
|
||||
env_ = env;
|
||||
return this;
|
||||
}
|
||||
private native void setEnv(long optHandle, long envHandle);
|
||||
|
||||
public RocksEnv getEnv() {
|
||||
return env_;
|
||||
}
|
||||
private native long getEnvHandle(long handle);
|
||||
|
||||
/**
|
||||
* Return true if the create_if_missing flag is set to true.
|
||||
* If true, the database will be created if it is missing.
|
||||
@@ -2311,10 +2330,9 @@ public class Options extends RocksObject {
|
||||
* Release the memory allocated for the current instance
|
||||
* in the c++ side.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose0();
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
static final int DEFAULT_PLAIN_TABLE_BLOOM_BITS_PER_KEY = 10;
|
||||
@@ -2322,7 +2340,7 @@ public class Options extends RocksObject {
|
||||
static final int DEFAULT_PLAIN_TABLE_INDEX_SPARSENESS = 16;
|
||||
|
||||
private native void newOptions();
|
||||
private native void dispose0();
|
||||
private native void disposeInternal(long handle);
|
||||
private native void setCreateIfMissing(long handle, boolean flag);
|
||||
private native boolean createIfMissing(long handle);
|
||||
private native void setWriteBufferSize(long handle, long writeBufferSize);
|
||||
@@ -2352,4 +2370,5 @@ public class Options extends RocksObject {
|
||||
|
||||
long cacheSize_;
|
||||
Filter filter_;
|
||||
RocksEnv env_;
|
||||
}
|
||||
|
||||
@@ -18,19 +18,6 @@ public class ReadOptions extends RocksObject {
|
||||
}
|
||||
private native void newReadOptions();
|
||||
|
||||
/**
|
||||
* Release the memory allocated for the current instance
|
||||
* in the c++ side.
|
||||
*
|
||||
* Calling other methods after dispose() leads to undefined behavior.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose(nativeHandle_);
|
||||
}
|
||||
}
|
||||
private native void dispose(long handle);
|
||||
|
||||
/**
|
||||
* If true, all data read from underlying storage will be
|
||||
* verified against corresponding checksums.
|
||||
@@ -127,4 +114,12 @@ public class ReadOptions extends RocksObject {
|
||||
}
|
||||
private native void setTailing(
|
||||
long handle, boolean tailing);
|
||||
|
||||
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
private native void disposeInternal(long handle);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
/**
|
||||
* This class is used to access information about backups and restore from them.
|
||||
*
|
||||
* Note that dispose() must be called before this instance become out-of-scope
|
||||
* to release the allocated memory in c++.
|
||||
*
|
||||
* @param options Instance of BackupableDBOptions.
|
||||
*/
|
||||
public class RestoreBackupableDB extends RocksObject {
|
||||
public RestoreBackupableDB(BackupableDBOptions options) {
|
||||
super();
|
||||
nativeHandle_ = newRestoreBackupableDB(options.nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore from backup with backup_id
|
||||
* IMPORTANT -- if options_.share_table_files == true and you restore DB
|
||||
* from some backup that is not the latest, and you start creating new
|
||||
* backups from the new DB, they will probably fail.
|
||||
*
|
||||
* Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
|
||||
* If you add new data to the DB and try creating a new backup now, the
|
||||
* database will diverge from backups 4 and 5 and the new backup will fail.
|
||||
* If you want to create new backup, you will first have to delete backups 4
|
||||
* and 5.
|
||||
*/
|
||||
public void restoreDBFromBackup(long backupId, String dbDir, String walDir,
|
||||
RestoreOptions restoreOptions) throws RocksDBException {
|
||||
restoreDBFromBackup0(nativeHandle_, backupId, dbDir, walDir,
|
||||
restoreOptions.nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore from the latest backup.
|
||||
*/
|
||||
public void restoreDBFromLatestBackup(String dbDir, String walDir,
|
||||
RestoreOptions restoreOptions) throws RocksDBException {
|
||||
restoreDBFromLatestBackup0(nativeHandle_, dbDir, walDir,
|
||||
restoreOptions.nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes old backups, keeping latest numBackupsToKeep alive.
|
||||
*
|
||||
* @param Number of latest backups to keep
|
||||
*/
|
||||
public void purgeOldBackups(int numBackupsToKeep) throws RocksDBException {
|
||||
purgeOldBackups0(nativeHandle_, numBackupsToKeep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a specific backup.
|
||||
*
|
||||
* @param ID of backup to delete.
|
||||
*/
|
||||
public void deleteBackup(long backupId) throws RocksDBException {
|
||||
deleteBackup0(nativeHandle_, backupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the memory allocated for the current instance
|
||||
* in the c++ side.
|
||||
*/
|
||||
@Override public synchronized void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
dispose(nativeHandle_);
|
||||
}
|
||||
|
||||
private native long newRestoreBackupableDB(long options);
|
||||
private native void restoreDBFromBackup0(long nativeHandle, long backupId,
|
||||
String dbDir, String walDir, long restoreOptions) throws RocksDBException;
|
||||
private native void restoreDBFromLatestBackup0(long nativeHandle,
|
||||
String dbDir, String walDir, long restoreOptions) throws RocksDBException;
|
||||
private native void purgeOldBackups0(long nativeHandle, int numBackupsToKeep);
|
||||
private native void deleteBackup0(long nativeHandle, long backupId);
|
||||
private native void dispose(long nativeHandle);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
/**
|
||||
* RestoreOptions to control the behavior of restore.
|
||||
*
|
||||
* Note that dispose() must be called before this instance become out-of-scope
|
||||
* to release the allocated memory in c++.
|
||||
*
|
||||
* @param If true, restore won't overwrite the existing log files in wal_dir. It
|
||||
* will also move all log files from archive directory to wal_dir. Use this
|
||||
* option in combination with BackupableDBOptions::backup_log_files = false
|
||||
* for persisting in-memory databases.
|
||||
* Default: false
|
||||
*/
|
||||
public class RestoreOptions extends RocksObject {
|
||||
public RestoreOptions(boolean keepLogFiles) {
|
||||
super();
|
||||
nativeHandle_ = newRestoreOptions(keepLogFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the memory allocated for the current instance
|
||||
* in the c++ side.
|
||||
*/
|
||||
@Override public synchronized void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
dispose(nativeHandle_);
|
||||
}
|
||||
|
||||
private native long newRestoreOptions(boolean keepLogFiles);
|
||||
private native void dispose(long handle);
|
||||
}
|
||||
@@ -114,11 +114,9 @@ public class RocksDB extends RocksObject {
|
||||
return db;
|
||||
}
|
||||
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose(nativeHandle_);
|
||||
nativeHandle_ = 0;
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -315,10 +313,6 @@ public class RocksDB extends RocksObject {
|
||||
return new RocksIterator(iterator0(nativeHandle_));
|
||||
}
|
||||
|
||||
@Override protected void finalize() {
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
@@ -370,7 +364,7 @@ public class RocksDB extends RocksObject {
|
||||
long handle, long writeOptHandle,
|
||||
byte[] key, int keyLen) throws RocksDBException;
|
||||
protected native long iterator0(long optHandle);
|
||||
protected native void dispose(long handle);
|
||||
private native void disposeInternal(long handle);
|
||||
|
||||
protected Filter filter_;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
/**
|
||||
* A RocksEnv is an interface used by the rocksdb implementation to access
|
||||
* operating system functionality like the filesystem etc.
|
||||
*
|
||||
* All Env implementations are safe for concurrent access from
|
||||
* multiple threads without any external synchronization.
|
||||
*/
|
||||
public class RocksEnv extends RocksObject {
|
||||
public static final int FLUSH_POOL = 0;
|
||||
public static final int COMPACTION_POOL = 1;
|
||||
|
||||
static {
|
||||
default_env_ = new RocksEnv(getDefaultEnvInternal());
|
||||
}
|
||||
private static native long getDefaultEnvInternal();
|
||||
|
||||
/**
|
||||
* Returns the default environment suitable for the current operating
|
||||
* system.
|
||||
*
|
||||
* The result of getDefault() is a singleton whose ownership belongs
|
||||
* to rocksdb c++. As a result, the returned RocksEnv will not
|
||||
* have the ownership of its c++ resource, and calling its dispose()
|
||||
* will be no-op.
|
||||
*/
|
||||
public static RocksEnv getDefault() {
|
||||
return default_env_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of background worker threads of the flush pool
|
||||
* for this environment.
|
||||
* default number: 1
|
||||
*/
|
||||
public RocksEnv setBackgroundThreads(int num) {
|
||||
return setBackgroundThreads(num, FLUSH_POOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of background worker threads of the specified thread
|
||||
* pool for this environment.
|
||||
*
|
||||
* @param num the number of threads
|
||||
* @param poolID the id to specified a thread pool. Should be either
|
||||
* FLUSH_POOL or COMPACTION_POOL.
|
||||
* Default number: 1
|
||||
*/
|
||||
public RocksEnv setBackgroundThreads(int num, int poolID) {
|
||||
setBackgroundThreads(nativeHandle_, num, poolID);
|
||||
return this;
|
||||
}
|
||||
private native void setBackgroundThreads(
|
||||
long handle, int num, int priority);
|
||||
|
||||
/**
|
||||
* Returns the length of the queue associated with the specified
|
||||
* thread pool.
|
||||
*
|
||||
* @param poolID the id to specified a thread pool. Should be either
|
||||
* FLUSH_POOL or COMPACTION_POOL.
|
||||
*/
|
||||
public int getThreadPoolQueueLen(int poolID) {
|
||||
return getThreadPoolQueueLen(nativeHandle_, poolID);
|
||||
}
|
||||
private native int getThreadPoolQueueLen(long handle, int poolID);
|
||||
|
||||
/**
|
||||
* Package-private constructor that uses the specified native handle
|
||||
* to construct a RocksEnv. Note that the ownership of the input handle
|
||||
* belongs to the caller, and the newly created RocksEnv will not take
|
||||
* the ownership of the input handle. As a result, calling dispose()
|
||||
* of the created RocksEnv will be no-op.
|
||||
*/
|
||||
RocksEnv(long handle) {
|
||||
super();
|
||||
nativeHandle_ = handle;
|
||||
disOwnNativeHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper function of dispose() which all subclasses of RocksObject
|
||||
* must implement to release their associated C++ resource.
|
||||
*/
|
||||
protected void disposeInternal() {
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
private native void disposeInternal(long handle);
|
||||
|
||||
/**
|
||||
* The static default RocksEnv. The ownership of its native handle
|
||||
* belongs to rocksdb c++ and is not able to be released on the Java
|
||||
* side.
|
||||
*/
|
||||
static RocksEnv default_env_;
|
||||
}
|
||||
@@ -118,15 +118,13 @@ public class RocksIterator extends RocksObject {
|
||||
/**
|
||||
* Deletes underlying C++ iterator pointer.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if(isInitialized()) {
|
||||
dispose(nativeHandle_);
|
||||
nativeHandle_ = 0;
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
private native boolean isValid0(long handle);
|
||||
private native void dispose(long handle);
|
||||
private native void disposeInternal(long handle);
|
||||
private native void seekToFirst0(long handle);
|
||||
private native void seekToLast0(long handle);
|
||||
private native void next0(long handle);
|
||||
|
||||
@@ -16,12 +16,48 @@ package org.rocksdb;
|
||||
public abstract class RocksObject {
|
||||
protected RocksObject() {
|
||||
nativeHandle_ = 0;
|
||||
owningHandle_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the c++ object pointed by the native handle.
|
||||
*
|
||||
* Note that once an instance of RocksObject has been disposed,
|
||||
* calling its function will lead undefined behavior.
|
||||
*/
|
||||
public abstract void dispose();
|
||||
public final synchronized void dispose() {
|
||||
if (isOwningNativeHandle() && isInitialized()) {
|
||||
disposeInternal();
|
||||
}
|
||||
nativeHandle_ = 0;
|
||||
disOwnNativeHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper function of dispose() which all subclasses of RocksObject
|
||||
* must implement to release their associated C++ resource.
|
||||
*/
|
||||
protected abstract void disposeInternal();
|
||||
|
||||
/**
|
||||
* Revoke ownership of the native object.
|
||||
*
|
||||
* This will prevent the object from attempting to delete the underlying
|
||||
* native object in its finalizer. This must be used when another object
|
||||
* takes over ownership of the native object or both will attempt to delete
|
||||
* the underlying object when garbage collected.
|
||||
*
|
||||
* When disOwnNativeHandle is called, dispose() will simply set nativeHandle_
|
||||
* to 0 without releasing its associated C++ resource. As a result,
|
||||
* incorrectly use this function may cause memory leak.
|
||||
*/
|
||||
protected void disOwnNativeHandle() {
|
||||
owningHandle_ = false;
|
||||
}
|
||||
|
||||
protected boolean isOwningNativeHandle() {
|
||||
return owningHandle_;
|
||||
}
|
||||
|
||||
protected boolean isInitialized() {
|
||||
return (nativeHandle_ != 0);
|
||||
@@ -32,4 +68,5 @@ public abstract class RocksObject {
|
||||
}
|
||||
|
||||
protected long nativeHandle_;
|
||||
private boolean owningHandle_;
|
||||
}
|
||||
|
||||
@@ -86,10 +86,9 @@ public class WriteBatch extends RocksObject {
|
||||
/**
|
||||
* Delete the c++ side pointer.
|
||||
*/
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose0();
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
private native void newWriteBatch(int reserved_bytes);
|
||||
@@ -99,7 +98,7 @@ public class WriteBatch extends RocksObject {
|
||||
byte[] value, int valueLen);
|
||||
private native void remove(byte[] key, int keyLen);
|
||||
private native void putLogData(byte[] blob, int blobLen);
|
||||
private native void dispose0();
|
||||
private native void disposeInternal(long handle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,10 +17,9 @@ public class WriteOptions extends RocksObject {
|
||||
newWriteOptions();
|
||||
}
|
||||
|
||||
@Override public synchronized void dispose() {
|
||||
if (isInitialized()) {
|
||||
dispose0(nativeHandle_);
|
||||
}
|
||||
@Override protected void disposeInternal() {
|
||||
assert(isInitialized());
|
||||
disposeInternal(nativeHandle_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,5 +95,5 @@ public class WriteOptions extends RocksObject {
|
||||
private native boolean sync(long handle);
|
||||
private native void setDisableWAL(long handle, boolean flag);
|
||||
private native boolean disableWAL(long handle);
|
||||
private native void dispose0(long handle);
|
||||
private native void disposeInternal(long handle);
|
||||
}
|
||||
|
||||
@@ -526,6 +526,8 @@ public class DbBenchmark {
|
||||
(Integer)flags_.get(Flag.max_write_buffer_number));
|
||||
options.setMaxBackgroundCompactions(
|
||||
(Integer)flags_.get(Flag.max_background_compactions));
|
||||
options.getEnv().setBackgroundThreads(
|
||||
(Integer)flags_.get(Flag.max_background_compactions));
|
||||
options.setMaxBackgroundFlushes(
|
||||
(Integer)flags_.get(Flag.max_background_flushes));
|
||||
options.setCacheSize(
|
||||
@@ -534,8 +536,6 @@ public class DbBenchmark {
|
||||
(Long)flags_.get(Flag.block_size));
|
||||
options.setMaxOpenFiles(
|
||||
(Integer)flags_.get(Flag.open_files));
|
||||
options.setCreateIfMissing(
|
||||
!(Boolean)flags_.get(Flag.use_existing_db));
|
||||
options.setTableCacheRemoveScanCountLimit(
|
||||
(Integer)flags_.get(Flag.cache_remove_scan_count_limit));
|
||||
options.setDisableDataSync(
|
||||
@@ -939,7 +939,7 @@ public class DbBenchmark {
|
||||
"\tflag and also specify a benchmark that wants a fresh database,\n" +
|
||||
"\tthat benchmark will fail.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
num(1000000,
|
||||
@@ -1028,7 +1028,7 @@ public class DbBenchmark {
|
||||
use_plain_table(false,
|
||||
"Use plain-table sst format.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
cache_size(-1L,
|
||||
@@ -1085,7 +1085,7 @@ public class DbBenchmark {
|
||||
},
|
||||
histogram(false,"Print histogram of operation timings.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
min_write_buffer_number_to_merge(
|
||||
@@ -1203,12 +1203,12 @@ public class DbBenchmark {
|
||||
verify_checksum(false,"Verify checksum for every block read\n" +
|
||||
"\tfrom storage.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
statistics(false,"Database statistics.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
writes(-1,"Number of write operations to do. If negative, do\n" +
|
||||
@@ -1219,23 +1219,23 @@ public class DbBenchmark {
|
||||
},
|
||||
sync(false,"Sync all writes to disk.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
disable_data_sync(false,"If true, do not wait until data is\n" +
|
||||
"\tsynced to disk.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
use_fsync(false,"If true, issue fsync instead of fdatasync.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
disable_wal(false,"If true, do not write WAL for write.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
wal_dir("", "If not empty, use the given dir for WAL.") {
|
||||
@@ -1312,7 +1312,7 @@ public class DbBenchmark {
|
||||
disable_seek_compaction(false,"Option to disable compaction\n" +
|
||||
"\ttriggered by read.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
delete_obsolete_files_period_micros(0,"Option to delete\n" +
|
||||
@@ -1393,12 +1393,12 @@ public class DbBenchmark {
|
||||
},
|
||||
readonly(false,"Run read only benchmarks.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
disable_auto_compactions(false,"Do not auto trigger compactions.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
source_compaction_factor(1,"Cap the size of data in level-K for\n" +
|
||||
@@ -1423,26 +1423,26 @@ public class DbBenchmark {
|
||||
bufferedio(rocksdb::EnvOptions().use_os_buffer,
|
||||
"Allow buffered io using OS buffers.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
*/
|
||||
mmap_read(false,
|
||||
"Allow reads to occur via mmap-ing files.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
mmap_write(false,
|
||||
"Allow writes to occur via mmap-ing files.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
advise_random_on_open(defaultOptions_.adviseRandomOnOpen(),
|
||||
"Advise random access on table file open.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
compaction_fadvice("NORMAL",
|
||||
@@ -1454,13 +1454,13 @@ public class DbBenchmark {
|
||||
use_tailing_iterator(false,
|
||||
"Use tailing iterator to access a series of keys instead of get.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
use_adaptive_mutex(defaultOptions_.useAdaptiveMutex(),
|
||||
"Use adaptive mutex.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
bytes_per_sync(defaultOptions_.bytesPerSync(),
|
||||
@@ -1474,7 +1474,7 @@ public class DbBenchmark {
|
||||
filter_deletes(false," On true, deletes use bloom-filter and drop\n" +
|
||||
"\tthe delete if key not present.") {
|
||||
@Override public Object parseValue(String value) {
|
||||
return Boolean.parseBoolean(value);
|
||||
return parseBoolean(value);
|
||||
}
|
||||
},
|
||||
max_successive_merges(0,"Maximum number of successive merge\n" +
|
||||
@@ -1495,8 +1495,6 @@ public class DbBenchmark {
|
||||
desc_ = desc;
|
||||
}
|
||||
|
||||
protected abstract Object parseValue(String value);
|
||||
|
||||
public Object getDefaultValue() {
|
||||
return defaultValue_;
|
||||
}
|
||||
@@ -1505,6 +1503,17 @@ public class DbBenchmark {
|
||||
return desc_;
|
||||
}
|
||||
|
||||
public boolean parseBoolean(String value) {
|
||||
if (value.equals("1")) {
|
||||
return true;
|
||||
} else if (value.equals("0")) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
protected abstract Object parseValue(String value);
|
||||
|
||||
private final Object defaultValue_;
|
||||
private final String desc_;
|
||||
}
|
||||
|
||||
@@ -18,15 +18,37 @@ public class BackupableDBTest {
|
||||
Options opt = new Options();
|
||||
opt.setCreateIfMissing(true);
|
||||
|
||||
BackupableDBOptions bopt = new BackupableDBOptions(backup_path);
|
||||
BackupableDBOptions bopt = new BackupableDBOptions(backup_path, false,
|
||||
true, false, true, 0, 0);
|
||||
BackupableDB bdb = null;
|
||||
|
||||
try {
|
||||
bdb = BackupableDB.open(opt, bopt, db_path);
|
||||
bdb.put("hello".getBytes(), "BackupableDB".getBytes());
|
||||
|
||||
bdb.put("abc".getBytes(), "def".getBytes());
|
||||
bdb.put("ghi".getBytes(), "jkl".getBytes());
|
||||
bdb.createNewBackup(true);
|
||||
byte[] value = bdb.get("hello".getBytes());
|
||||
assert(new String(value).equals("BackupableDB"));
|
||||
|
||||
// delete record after backup
|
||||
bdb.remove("abc".getBytes());
|
||||
byte[] value = bdb.get("abc".getBytes());
|
||||
assert(value == null);
|
||||
bdb.close();
|
||||
|
||||
// restore from backup
|
||||
RestoreOptions ropt = new RestoreOptions(false);
|
||||
RestoreBackupableDB rdb = new RestoreBackupableDB(bopt);
|
||||
rdb.restoreDBFromLatestBackup(db_path, db_path,
|
||||
ropt);
|
||||
rdb.dispose();
|
||||
ropt.dispose();
|
||||
|
||||
// verify that backed up data contains deleted record
|
||||
bdb = BackupableDB.open(opt, bopt, db_path);
|
||||
value = bdb.get("abc".getBytes());
|
||||
assert(new String(value).equals("def"));
|
||||
|
||||
System.out.println("Backup and restore test passed");
|
||||
} catch (RocksDBException e) {
|
||||
System.err.format("[ERROR]: %s%n", e);
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// This file implements the "bridge" between Java and C++ and enables
|
||||
// calling c++ rocksdb::DB methods from Java side.
|
||||
// calling c++ rocksdb::BackupableDB and rocksdb::BackupableDBOptions methods
|
||||
// from Java side.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -51,9 +52,18 @@ void Java_org_rocksdb_BackupableDB_createNewBackup(
|
||||
* Signature: (Ljava/lang/String;)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDBOptions_newBackupableDBOptions(
|
||||
JNIEnv* env, jobject jobj, jstring jpath) {
|
||||
JNIEnv* env, jobject jobj, jstring jpath, jboolean jshare_table_files,
|
||||
jboolean jsync, jboolean jdestroy_old_data, jboolean jbackup_log_files,
|
||||
jlong jbackup_rate_limit, jlong jrestore_rate_limit) {
|
||||
jbackup_rate_limit = (jbackup_rate_limit <= 0) ? 0 : jbackup_rate_limit;
|
||||
jrestore_rate_limit = (jrestore_rate_limit <= 0) ? 0 : jrestore_rate_limit;
|
||||
|
||||
const char* cpath = env->GetStringUTFChars(jpath, 0);
|
||||
auto bopt = new rocksdb::BackupableDBOptions(cpath);
|
||||
|
||||
auto bopt = new rocksdb::BackupableDBOptions(cpath, nullptr,
|
||||
jshare_table_files, nullptr, jsync, jdestroy_old_data, jbackup_log_files,
|
||||
jbackup_rate_limit, jrestore_rate_limit);
|
||||
|
||||
env->ReleaseStringUTFChars(jpath, cpath);
|
||||
|
||||
rocksdb::BackupableDBOptionsJni::setHandle(env, jobj, bopt);
|
||||
@@ -72,10 +82,10 @@ jstring Java_org_rocksdb_BackupableDBOptions_backupDir(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDBOptions
|
||||
* Method: dispose
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDBOptions_dispose(
|
||||
void Java_org_rocksdb_BackupableDBOptions_disposeInternal(
|
||||
JNIEnv* env, jobject jopt, jlong jhandle) {
|
||||
auto bopt = reinterpret_cast<rocksdb::BackupableDBOptions*>(jhandle);
|
||||
assert(bopt);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// This file implements the "bridge" between Java and C++ and enables
|
||||
// calling c++ rocksdb::Env methods from Java side.
|
||||
|
||||
#include "include/org_rocksdb_RocksEnv.h"
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksEnv
|
||||
* Method: getDefaultEnvInternal
|
||||
* Signature: ()J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RocksEnv_getDefaultEnvInternal(
|
||||
JNIEnv* env, jclass jclass) {
|
||||
return reinterpret_cast<jlong>(rocksdb::Env::Default());
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksEnv
|
||||
* Method: setBackgroundThreads
|
||||
* Signature: (JII)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksEnv_setBackgroundThreads(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle,
|
||||
jint num, jint priority) {
|
||||
auto* rocks_env = reinterpret_cast<rocksdb::Env*>(jhandle);
|
||||
switch (priority) {
|
||||
case org_rocksdb_RocksEnv_FLUSH_POOL:
|
||||
rocks_env->SetBackgroundThreads(num, rocksdb::Env::Priority::LOW);
|
||||
break;
|
||||
case org_rocksdb_RocksEnv_COMPACTION_POOL:
|
||||
rocks_env->SetBackgroundThreads(num, rocksdb::Env::Priority::HIGH);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksEnv
|
||||
* Method: getThreadPoolQueueLen
|
||||
* Signature: (JI)I
|
||||
*/
|
||||
jint Java_org_rocksdb_RocksEnv_getThreadPoolQueueLen(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jint pool_id) {
|
||||
auto* rocks_env = reinterpret_cast<rocksdb::Env*>(jhandle);
|
||||
switch (pool_id) {
|
||||
case org_rocksdb_RocksEnv_FLUSH_POOL:
|
||||
return rocks_env->GetThreadPoolQueueLen(rocksdb::Env::Priority::LOW);
|
||||
case org_rocksdb_RocksEnv_COMPACTION_POOL:
|
||||
return rocks_env->GetThreadPoolQueueLen(rocksdb::Env::Priority::HIGH);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksEnv
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksEnv_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
delete reinterpret_cast<rocksdb::Env*>(jhandle);
|
||||
}
|
||||
@@ -29,13 +29,10 @@ void Java_org_rocksdb_BloomFilter_createNewFilter0(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Filter
|
||||
* Method: dispose0
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_Filter_dispose0(
|
||||
void Java_org_rocksdb_Filter_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong handle) {
|
||||
auto fp = reinterpret_cast<rocksdb::FilterPolicy*>(handle);
|
||||
delete fp;
|
||||
|
||||
rocksdb::FilterJni::setHandle(env, jobj, nullptr);
|
||||
delete reinterpret_cast<rocksdb::FilterPolicy*>(handle);
|
||||
}
|
||||
|
||||
@@ -135,10 +135,10 @@ void Java_org_rocksdb_RocksIterator_status0(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksIterator
|
||||
* Method: dispose
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksIterator_dispose(
|
||||
void Java_org_rocksdb_RocksIterator_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong handle) {
|
||||
auto it = reinterpret_cast<rocksdb::Iterator*>(handle);
|
||||
delete it;
|
||||
|
||||
@@ -35,14 +35,12 @@ void Java_org_rocksdb_Options_newOptions(JNIEnv* env, jobject jobj) {
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Options
|
||||
* Method: dispose0
|
||||
* Signature: ()V
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_Options_dispose0(JNIEnv* env, jobject jobj) {
|
||||
rocksdb::Options* op = rocksdb::OptionsJni::getHandle(env, jobj);
|
||||
delete op;
|
||||
|
||||
rocksdb::OptionsJni::setHandle(env, jobj, nullptr);
|
||||
void Java_org_rocksdb_Options_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong handle) {
|
||||
delete reinterpret_cast<rocksdb::Options*>(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1665,10 +1663,10 @@ void Java_org_rocksdb_WriteOptions_newWriteOptions(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_WriteOptions
|
||||
* Method: dispose0
|
||||
* Method: disposeInternal
|
||||
* Signature: ()V
|
||||
*/
|
||||
void Java_org_rocksdb_WriteOptions_dispose0(
|
||||
void Java_org_rocksdb_WriteOptions_disposeInternal(
|
||||
JNIEnv* env, jobject jwrite_options, jlong jhandle) {
|
||||
auto write_options = reinterpret_cast<rocksdb::WriteOptions*>(jhandle);
|
||||
delete write_options;
|
||||
@@ -1732,10 +1730,10 @@ void Java_org_rocksdb_ReadOptions_newReadOptions(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_ReadOptions
|
||||
* Method: dispose
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_ReadOptions_dispose(
|
||||
void Java_org_rocksdb_ReadOptions_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
delete reinterpret_cast<rocksdb::ReadOptions*>(jhandle);
|
||||
rocksdb::ReadOptionsJni::setHandle(env, jobj, nullptr);
|
||||
|
||||
@@ -217,6 +217,7 @@ class HistogramDataJni {
|
||||
return mid;
|
||||
}
|
||||
};
|
||||
|
||||
class BackupableDBOptionsJni {
|
||||
public:
|
||||
// Get the java class id of org.rocksdb.BackupableDBOptions.
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// This file implements the "bridge" between Java and C++ and enables
|
||||
// calling c++ rocksdb::RestoreBackupableDB and rocksdb::RestoreOptions methods
|
||||
// from Java side.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "include/org_rocksdb_RestoreOptions.h"
|
||||
#include "include/org_rocksdb_RestoreBackupableDB.h"
|
||||
#include "rocksjni/portal.h"
|
||||
#include "utilities/backupable_db.h"
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreOptions
|
||||
* Method: newRestoreOptions
|
||||
* Signature: (Z)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RestoreOptions_newRestoreOptions(JNIEnv* env,
|
||||
jobject jobj, jboolean keep_log_files) {
|
||||
auto ropt = new rocksdb::RestoreOptions(keep_log_files);
|
||||
return reinterpret_cast<jlong>(ropt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreOptions
|
||||
* Method: dispose
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreOptions_dispose(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
auto ropt = reinterpret_cast<rocksdb::RestoreOptions*>(jhandle);
|
||||
assert(ropt);
|
||||
delete ropt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: newRestoreBackupableDB
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RestoreBackupableDB_newRestoreBackupableDB(JNIEnv* env,
|
||||
jobject jobj, jlong jopt_handle) {
|
||||
auto opt = reinterpret_cast<rocksdb::BackupableDBOptions*>(jopt_handle);
|
||||
auto rdb = new rocksdb::RestoreBackupableDB(rocksdb::Env::Default(), *opt);
|
||||
return reinterpret_cast<jlong>(rdb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: restoreDBFromBackup0
|
||||
* Signature: (JJLjava/lang/String;Ljava/lang/String;J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_restoreDBFromBackup0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jlong jbackup_id, jstring jdb_dir,
|
||||
jstring jwal_dir, jlong jopt_handle) {
|
||||
auto opt = reinterpret_cast<rocksdb::RestoreOptions*>(jopt_handle);
|
||||
|
||||
const char* cdb_dir = env->GetStringUTFChars(jdb_dir, 0);
|
||||
const char* cwal_dir = env->GetStringUTFChars(jwal_dir, 0);
|
||||
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s =
|
||||
rdb->RestoreDBFromBackup(jbackup_id, cdb_dir, cwal_dir, *opt);
|
||||
|
||||
env->ReleaseStringUTFChars(jdb_dir, cdb_dir);
|
||||
env->ReleaseStringUTFChars(jwal_dir, cwal_dir);
|
||||
|
||||
if(!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: restoreDBFromLatestBackup0
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_restoreDBFromLatestBackup0(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jstring jdb_dir, jstring jwal_dir,
|
||||
jlong jopt_handle) {
|
||||
auto opt = reinterpret_cast<rocksdb::RestoreOptions*>(jopt_handle);
|
||||
|
||||
const char* cdb_dir = env->GetStringUTFChars(jdb_dir, 0);
|
||||
const char* cwal_dir = env->GetStringUTFChars(jwal_dir, 0);
|
||||
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s =
|
||||
rdb->RestoreDBFromLatestBackup(cdb_dir, cwal_dir, *opt);
|
||||
|
||||
env->ReleaseStringUTFChars(jdb_dir, cdb_dir);
|
||||
env->ReleaseStringUTFChars(jwal_dir, cwal_dir);
|
||||
|
||||
if(!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: purgeOldBackups0
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_purgeOldBackups0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jint jnum_backups_to_keep) {
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->PurgeOldBackups(jnum_backups_to_keep);
|
||||
|
||||
if(!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: deleteBackup0
|
||||
* Signature: (JJ)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_deleteBackup0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jlong jbackup_id) {
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->DeleteBackup(jbackup_id);
|
||||
|
||||
if(!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: dispose
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_dispose(JNIEnv* env, jobject jobj,
|
||||
jlong jhandle) {
|
||||
auto ropt = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
assert(ropt);
|
||||
delete ropt;
|
||||
}
|
||||
@@ -29,10 +29,6 @@ void Java_org_rocksdb_RocksDB_open(
|
||||
JNIEnv* env, jobject jdb, jlong jopt_handle,
|
||||
jlong jcache_size, jstring jdb_path) {
|
||||
auto opt = reinterpret_cast<rocksdb::Options*>(jopt_handle);
|
||||
// TODO(yhchiang): should be removed once Java binding for Env is ready.
|
||||
if (opt->max_background_compactions > 1) {
|
||||
opt->env->SetBackgroundThreads(opt->max_background_compactions);
|
||||
}
|
||||
if (jcache_size > 0) {
|
||||
opt->no_block_cache = false;
|
||||
opt->block_cache = rocksdb::NewLRUCache(jcache_size);
|
||||
@@ -419,14 +415,12 @@ void Java_org_rocksdb_RocksDB_remove__JJ_3BI(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RocksDB
|
||||
* Method: dispose
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RocksDB_dispose(
|
||||
void Java_org_rocksdb_RocksDB_disposeInternal(
|
||||
JNIEnv* env, jobject java_db, jlong jhandle) {
|
||||
auto db = reinterpret_cast<rocksdb::DB*>(jhandle);
|
||||
assert(db != nullptr);
|
||||
delete db;
|
||||
delete reinterpret_cast<rocksdb::DB*>(jhandle);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -134,15 +134,12 @@ void Java_org_rocksdb_WriteBatch_putLogData(
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_WriteBatch
|
||||
* Method: dispose0
|
||||
* Signature: ()V
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_WriteBatch_dispose0(JNIEnv* env, jobject jobj) {
|
||||
rocksdb::WriteBatch* wb = rocksdb::WriteBatchJni::getHandle(env, jobj);
|
||||
assert(wb != nullptr);
|
||||
delete wb;
|
||||
|
||||
rocksdb::WriteBatchJni::setHandle(env, jobj, nullptr);
|
||||
void Java_org_rocksdb_WriteBatch_disposeInternal(
|
||||
JNIEnv* env, jobject jobj, jlong handle) {
|
||||
delete reinterpret_cast<rocksdb::WriteBatch*>(handle);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+3
-1
@@ -99,7 +99,9 @@ void RWMutex::ReadLock() { PthreadCall("read lock", pthread_rwlock_rdlock(&mu_))
|
||||
|
||||
void RWMutex::WriteLock() { PthreadCall("write lock", pthread_rwlock_wrlock(&mu_)); }
|
||||
|
||||
void RWMutex::Unlock() { PthreadCall("unlock", pthread_rwlock_unlock(&mu_)); }
|
||||
void RWMutex::ReadUnlock() { PthreadCall("read unlock", pthread_rwlock_unlock(&mu_)); }
|
||||
|
||||
void RWMutex::WriteUnlock() { PthreadCall("write unlock", pthread_rwlock_unlock(&mu_)); }
|
||||
|
||||
void InitOnce(OnceType* once, void (*initializer)()) {
|
||||
PthreadCall("once", pthread_once(once, initializer));
|
||||
|
||||
+4
-1
@@ -120,7 +120,8 @@ class RWMutex {
|
||||
|
||||
void ReadLock();
|
||||
void WriteLock();
|
||||
void Unlock();
|
||||
void ReadUnlock();
|
||||
void WriteUnlock();
|
||||
void AssertHeld() { }
|
||||
|
||||
private:
|
||||
@@ -482,6 +483,8 @@ inline bool LZ4HC_Compress(const CompressionOptions &opts, const char* input,
|
||||
|
||||
#define CACHE_LINE_SIZE 64U
|
||||
|
||||
#define PREFETCH(addr, rw, locality) __builtin_prefetch(addr, rw, locality)
|
||||
|
||||
} // namespace port
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@ void PrintStack(int first_frames_to_skip) {}
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
// It's odd that including this breaks in GCC 7 but the build doesn't break
|
||||
// if I remove it even under GCC 4.8.
|
||||
// #include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <cxxabi.h>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/adaptive_table_factory.h"
|
||||
|
||||
#include "table/format.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
AdaptiveTableFactory::AdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory)
|
||||
: table_factory_to_write_(table_factory_to_write),
|
||||
block_based_table_factory_(block_based_table_factory),
|
||||
plain_table_factory_(plain_table_factory) {
|
||||
if (!table_factory_to_write_) {
|
||||
table_factory_to_write_ = block_based_table_factory_;
|
||||
}
|
||||
if (!plain_table_factory_) {
|
||||
plain_table_factory_.reset(NewPlainTableFactory());
|
||||
}
|
||||
if (!block_based_table_factory_) {
|
||||
block_based_table_factory_.reset(NewBlockBasedTableFactory());
|
||||
}
|
||||
}
|
||||
|
||||
extern const uint64_t kPlainTableMagicNumber;
|
||||
extern const uint64_t kLegacyPlainTableMagicNumber;
|
||||
extern const uint64_t kBlockBasedTableMagicNumber;
|
||||
extern const uint64_t kLegacyBlockBasedTableMagicNumber;
|
||||
|
||||
Status AdaptiveTableFactory::NewTableReader(
|
||||
const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& icomp, unique_ptr<RandomAccessFile>&& file,
|
||||
uint64_t file_size, unique_ptr<TableReader>* table) const {
|
||||
Footer footer;
|
||||
auto s = ReadFooterFromFile(file.get(), file_size, &footer);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (footer.table_magic_number() == kPlainTableMagicNumber ||
|
||||
footer.table_magic_number() == kLegacyPlainTableMagicNumber) {
|
||||
return plain_table_factory_->NewTableReader(
|
||||
options, soptions, icomp, std::move(file), file_size, table);
|
||||
} else if (footer.table_magic_number() == kBlockBasedTableMagicNumber ||
|
||||
footer.table_magic_number() == kLegacyBlockBasedTableMagicNumber) {
|
||||
return block_based_table_factory_->NewTableReader(
|
||||
options, soptions, icomp, std::move(file), file_size, table);
|
||||
} else {
|
||||
return Status::NotSupported("Unidentified table format");
|
||||
}
|
||||
}
|
||||
|
||||
TableBuilder* AdaptiveTableFactory::NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const {
|
||||
return table_factory_to_write_->NewTableBuilder(options, internal_comparator,
|
||||
file, compression_type);
|
||||
}
|
||||
|
||||
extern TableFactory* NewAdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory) {
|
||||
return new AdaptiveTableFactory(
|
||||
table_factory_to_write, block_based_table_factory, plain_table_factory);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
struct Options;
|
||||
struct EnvOptions;
|
||||
|
||||
using std::unique_ptr;
|
||||
class Status;
|
||||
class RandomAccessFile;
|
||||
class WritableFile;
|
||||
class Table;
|
||||
class TableBuilder;
|
||||
|
||||
class AdaptiveTableFactory : public TableFactory {
|
||||
public:
|
||||
~AdaptiveTableFactory() {}
|
||||
|
||||
explicit AdaptiveTableFactory(
|
||||
std::shared_ptr<TableFactory> table_factory_to_write,
|
||||
std::shared_ptr<TableFactory> block_based_table_factory,
|
||||
std::shared_ptr<TableFactory> plain_table_factory);
|
||||
const char* Name() const override { return "AdaptiveTableFactory"; }
|
||||
Status NewTableReader(const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table) const override;
|
||||
TableBuilder* NewTableBuilder(const Options& options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
WritableFile* file,
|
||||
CompressionType compression_type) const
|
||||
override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<TableFactory> table_factory_to_write_;
|
||||
std::shared_ptr<TableFactory> block_based_table_factory_;
|
||||
std::shared_ptr<TableFactory> plain_table_factory_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
+84
-9
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "table/block_hash_index.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/format.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
@@ -97,6 +98,7 @@ class Block::Iter : public Iterator {
|
||||
Slice value_;
|
||||
Status status_;
|
||||
BlockHashIndex* hash_index_;
|
||||
BlockPrefixIndex* prefix_index_;
|
||||
|
||||
inline int Compare(const Slice& a, const Slice& b) const {
|
||||
return comparator_->Compare(a, b);
|
||||
@@ -124,14 +126,16 @@ class Block::Iter : public Iterator {
|
||||
|
||||
public:
|
||||
Iter(const Comparator* comparator, const char* data, uint32_t restarts,
|
||||
uint32_t num_restarts, BlockHashIndex* hash_index)
|
||||
uint32_t num_restarts, BlockHashIndex* hash_index,
|
||||
BlockPrefixIndex* prefix_index)
|
||||
: comparator_(comparator),
|
||||
data_(data),
|
||||
restarts_(restarts),
|
||||
num_restarts_(num_restarts),
|
||||
current_(restarts_),
|
||||
restart_index_(num_restarts_),
|
||||
hash_index_(hash_index) {
|
||||
hash_index_(hash_index),
|
||||
prefix_index_(prefix_index) {
|
||||
assert(num_restarts_ > 0);
|
||||
}
|
||||
|
||||
@@ -174,8 +178,13 @@ class Block::Iter : public Iterator {
|
||||
|
||||
virtual void Seek(const Slice& target) {
|
||||
uint32_t index = 0;
|
||||
bool ok = hash_index_ ? HashSeek(target, &index)
|
||||
: BinarySeek(target, 0, num_restarts_ - 1, &index);
|
||||
bool ok = false;
|
||||
if (prefix_index_) {
|
||||
ok = PrefixSeek(target, &index);
|
||||
} else {
|
||||
ok = hash_index_ ? HashSeek(target, &index)
|
||||
: BinarySeek(target, 0, num_restarts_ - 1, &index);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return;
|
||||
@@ -238,8 +247,9 @@ class Block::Iter : public Iterator {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search in restart array to find the first restart point
|
||||
// with a key >= target
|
||||
// with a key >= target (TODO: this comment is inaccurate)
|
||||
bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
uint32_t* index) {
|
||||
assert(left <= right);
|
||||
@@ -256,14 +266,17 @@ class Block::Iter : public Iterator {
|
||||
return false;
|
||||
}
|
||||
Slice mid_key(key_ptr, non_shared);
|
||||
if (Compare(mid_key, target) < 0) {
|
||||
int cmp = Compare(mid_key, target);
|
||||
if (cmp < 0) {
|
||||
// Key at "mid" is smaller than "target". Therefore all
|
||||
// blocks before "mid" are uninteresting.
|
||||
left = mid;
|
||||
} else {
|
||||
} else if (cmp > 0) {
|
||||
// Key at "mid" is >= "target". Therefore all blocks at or
|
||||
// after "mid" are uninteresting.
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,12 +284,56 @@ class Block::Iter : public Iterator {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Binary search in block_ids to find the first block
|
||||
// with a key >= target
|
||||
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
uint32_t left, uint32_t right,
|
||||
uint32_t* index) {
|
||||
assert(left <= right);
|
||||
|
||||
while (left <= right) {
|
||||
uint32_t mid = (left + right) / 2;
|
||||
uint32_t region_offset = GetRestartPoint(block_ids[mid]);
|
||||
uint32_t shared, non_shared, value_length;
|
||||
const char* key_ptr =
|
||||
DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
|
||||
&non_shared, &value_length);
|
||||
if (key_ptr == nullptr || (shared != 0)) {
|
||||
CorruptionError();
|
||||
return false;
|
||||
}
|
||||
Slice mid_key(key_ptr, non_shared);
|
||||
int cmp = Compare(mid_key, target);
|
||||
if (cmp < 0) {
|
||||
// Key at "target" is larger than "mid". Therefore all
|
||||
// blocks before or at "mid" are uninteresting.
|
||||
left = mid + 1;
|
||||
} else {
|
||||
// Key at "target" is <= "mid". Therefore all blocks
|
||||
// after "mid" are uninteresting.
|
||||
// If there is only one block left, we found it.
|
||||
if (left == right) break;
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if (left == right) {
|
||||
*index = block_ids[left];
|
||||
return true;
|
||||
} else {
|
||||
assert(left > right);
|
||||
// Mark iterator invalid
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HashSeek(const Slice& target, uint32_t* index) {
|
||||
assert(hash_index_);
|
||||
auto restart_index = hash_index_->GetRestartIndex(target);
|
||||
if (restart_index == nullptr) {
|
||||
current_ = restarts_;
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// the elements in restart_array[index : index + num_blocks]
|
||||
@@ -285,6 +342,20 @@ class Block::Iter : public Iterator {
|
||||
auto right = restart_index->first_index + restart_index->num_blocks - 1;
|
||||
return BinarySeek(target, left, right, index);
|
||||
}
|
||||
|
||||
bool PrefixSeek(const Slice& target, uint32_t* index) {
|
||||
assert(prefix_index_);
|
||||
uint32_t* block_ids = nullptr;
|
||||
uint32_t num_blocks = prefix_index_->GetBlocks(target, &block_ids);
|
||||
|
||||
|
||||
if (num_blocks == 0) {
|
||||
current_ = restarts_;
|
||||
return false;
|
||||
} else {
|
||||
return BinaryBlockIndexSeek(target, block_ids, 0, num_blocks - 1, index);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Iterator* Block::NewIterator(const Comparator* cmp) {
|
||||
@@ -296,7 +367,7 @@ Iterator* Block::NewIterator(const Comparator* cmp) {
|
||||
return NewEmptyIterator();
|
||||
} else {
|
||||
return new Iter(cmp, data_, restart_offset_, num_restarts,
|
||||
hash_index_.get());
|
||||
hash_index_.get(), prefix_index_.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,4 +375,8 @@ void Block::SetBlockHashIndex(BlockHashIndex* hash_index) {
|
||||
hash_index_.reset(hash_index);
|
||||
}
|
||||
|
||||
void Block::SetBlockPrefixIndex(BlockPrefixIndex* prefix_index) {
|
||||
prefix_index_.reset(prefix_index);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace rocksdb {
|
||||
struct BlockContents;
|
||||
class Comparator;
|
||||
class BlockHashIndex;
|
||||
class BlockPrefixIndex;
|
||||
|
||||
class Block {
|
||||
public:
|
||||
@@ -41,6 +42,7 @@ class Block {
|
||||
// the key that is just pass the target key.
|
||||
Iterator* NewIterator(const Comparator* comparator);
|
||||
void SetBlockHashIndex(BlockHashIndex* hash_index);
|
||||
void SetBlockPrefixIndex(BlockPrefixIndex* prefix_index);
|
||||
|
||||
private:
|
||||
const char* data_;
|
||||
@@ -50,6 +52,7 @@ class Block {
|
||||
bool cachable_;
|
||||
CompressionType compression_type_;
|
||||
std::unique_ptr<BlockHashIndex> hash_index_;
|
||||
std::unique_ptr<BlockPrefixIndex> prefix_index_;
|
||||
|
||||
// No copying allowed
|
||||
Block(const Block&);
|
||||
|
||||
@@ -476,7 +476,7 @@ void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
if (r->props.num_entries > 0) {
|
||||
assert(r->internal_comparator.Compare(key, Slice(r->last_key)) > 0);
|
||||
}
|
||||
r->index_builder->OnKeyAdded(key);
|
||||
|
||||
auto should_flush = r->flush_block_policy->Update(key, value);
|
||||
if (should_flush) {
|
||||
assert(!r->data_block.empty());
|
||||
@@ -505,6 +505,7 @@ void BlockBasedTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
r->props.raw_key_size += key.size();
|
||||
r->props.raw_value_size += value.size();
|
||||
|
||||
r->index_builder->OnKeyAdded(key);
|
||||
NotifyCollectTableCollectorsOnAdd(key, value, r->table_properties_collectors,
|
||||
r->options.info_log.get());
|
||||
}
|
||||
@@ -541,9 +542,16 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents,
|
||||
Rep* r = rep_;
|
||||
|
||||
auto type = r->compression_type;
|
||||
auto block_contents =
|
||||
CompressBlock(raw_block_contents, r->options.compression_opts, &type,
|
||||
&r->compressed_output);
|
||||
Slice block_contents;
|
||||
if (raw_block_contents.size() < kCompressionSizeLimit) {
|
||||
block_contents =
|
||||
CompressBlock(raw_block_contents, r->options.compression_opts, &type,
|
||||
&r->compressed_output);
|
||||
} else {
|
||||
RecordTick(r->options.statistics.get(), NUMBER_BLOCK_NOT_COMPRESSED);
|
||||
type = kNoCompression;
|
||||
block_contents = raw_block_contents;
|
||||
}
|
||||
WriteRawBlock(block_contents, type, handle);
|
||||
r->compressed_output.clear();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <limits>
|
||||
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
@@ -84,6 +85,10 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
// REQUIRES: Finish(), Abandon() have not been called
|
||||
void Flush();
|
||||
|
||||
// Some compression libraries fail when the raw size is bigger than int. If
|
||||
// uncompressed size is bigger than kCompressionSizeLimit, don't compress it
|
||||
const uint64_t kCompressionSizeLimit = std::numeric_limits<int>::max();
|
||||
|
||||
// No copying allowed
|
||||
BlockBasedTableBuilder(const BlockBasedTableBuilder&) = delete;
|
||||
void operator=(const BlockBasedTableBuilder&) = delete;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "table/block.h"
|
||||
#include "table/filter_block.h"
|
||||
#include "table/block_hash_index.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/format.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
@@ -194,7 +195,8 @@ class HashIndexReader : public IndexReader {
|
||||
const Footer& footer, RandomAccessFile* file, Env* env,
|
||||
const Comparator* comparator,
|
||||
const BlockHandle& index_handle,
|
||||
Iterator* meta_index_iter, IndexReader** index_reader) {
|
||||
Iterator* meta_index_iter, IndexReader** index_reader,
|
||||
bool hash_index_allow_collision) {
|
||||
Block* index_block = nullptr;
|
||||
auto s = ReadBlockFromFile(file, footer, ReadOptions(), index_handle,
|
||||
&index_block, env);
|
||||
@@ -203,12 +205,21 @@ class HashIndexReader : public IndexReader {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Note, failure to create prefix hash index does not need to be a
|
||||
// hard error. We can still fall back to the original binary search index.
|
||||
// So, Create will succeed regardless, from this point on.
|
||||
|
||||
auto new_index_reader =
|
||||
new HashIndexReader(comparator, index_block);
|
||||
*index_reader = new_index_reader;
|
||||
|
||||
// Get prefixes block
|
||||
BlockHandle prefixes_handle;
|
||||
s = FindMetaBlock(meta_index_iter, kHashIndexPrefixesBlock,
|
||||
&prefixes_handle);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
// TODO: log error
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Get index metadata block
|
||||
@@ -216,7 +227,8 @@ class HashIndexReader : public IndexReader {
|
||||
s = FindMetaBlock(meta_index_iter, kHashIndexPrefixesMetadataBlock,
|
||||
&prefixes_meta_handle);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
// TODO: log error
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Read contents for the blocks
|
||||
@@ -234,27 +246,47 @@ class HashIndexReader : public IndexReader {
|
||||
if (prefixes_contents.heap_allocated) {
|
||||
delete[] prefixes_contents.data.data();
|
||||
}
|
||||
return s;
|
||||
// TODO: log error
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
auto new_index_reader =
|
||||
new HashIndexReader(comparator, index_block, prefixes_contents);
|
||||
BlockHashIndex* hash_index = nullptr;
|
||||
s = CreateBlockHashIndex(hash_key_extractor, prefixes_contents.data,
|
||||
prefixes_meta_contents.data, &hash_index);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
if (!hash_index_allow_collision) {
|
||||
// TODO: deprecate once hash_index_allow_collision proves to be stable.
|
||||
BlockHashIndex* hash_index = nullptr;
|
||||
s = CreateBlockHashIndex(hash_key_extractor,
|
||||
prefixes_contents.data,
|
||||
prefixes_meta_contents.data,
|
||||
&hash_index);
|
||||
// TODO: log error
|
||||
if (s.ok()) {
|
||||
new_index_reader->index_block_->SetBlockHashIndex(hash_index);
|
||||
new_index_reader->OwnPrefixesContents(prefixes_contents);
|
||||
}
|
||||
} else {
|
||||
BlockPrefixIndex* prefix_index = nullptr;
|
||||
s = BlockPrefixIndex::Create(hash_key_extractor,
|
||||
prefixes_contents.data,
|
||||
prefixes_meta_contents.data,
|
||||
&prefix_index);
|
||||
// TODO: log error
|
||||
if (s.ok()) {
|
||||
new_index_reader->index_block_->SetBlockPrefixIndex(prefix_index);
|
||||
}
|
||||
}
|
||||
|
||||
new_index_reader->index_block_->SetBlockHashIndex(hash_index);
|
||||
|
||||
*index_reader = new_index_reader;
|
||||
|
||||
// release resources
|
||||
// Always release prefix meta block
|
||||
if (prefixes_meta_contents.heap_allocated) {
|
||||
delete[] prefixes_meta_contents.data.data();
|
||||
}
|
||||
return s;
|
||||
|
||||
// Release prefix content block if we don't own it.
|
||||
if (!new_index_reader->own_prefixes_contents_) {
|
||||
if (prefixes_contents.heap_allocated) {
|
||||
delete[] prefixes_contents.data.data();
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Iterator* NewIterator() override {
|
||||
@@ -264,21 +296,26 @@ class HashIndexReader : public IndexReader {
|
||||
virtual size_t size() const override { return index_block_->size(); }
|
||||
|
||||
private:
|
||||
HashIndexReader(const Comparator* comparator, Block* index_block,
|
||||
const BlockContents& prefixes_contents)
|
||||
HashIndexReader(const Comparator* comparator, Block* index_block)
|
||||
: IndexReader(comparator),
|
||||
index_block_(index_block),
|
||||
prefixes_contents_(prefixes_contents) {
|
||||
own_prefixes_contents_(false) {
|
||||
assert(index_block_ != nullptr);
|
||||
}
|
||||
|
||||
~HashIndexReader() {
|
||||
if (prefixes_contents_.heap_allocated) {
|
||||
if (own_prefixes_contents_ && prefixes_contents_.heap_allocated) {
|
||||
delete[] prefixes_contents_.data.data();
|
||||
}
|
||||
}
|
||||
|
||||
void OwnPrefixesContents(const BlockContents& prefixes_contents) {
|
||||
prefixes_contents_ = prefixes_contents;
|
||||
own_prefixes_contents_ = true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Block> index_block_;
|
||||
bool own_prefixes_contents_;
|
||||
BlockContents prefixes_contents_;
|
||||
};
|
||||
|
||||
@@ -308,6 +345,7 @@ struct BlockBasedTable::Rep {
|
||||
|
||||
std::shared_ptr<const TableProperties> table_properties;
|
||||
BlockBasedTableOptions::IndexType index_type;
|
||||
bool hash_index_allow_collision;
|
||||
// TODO(kailiu) It is very ugly to use internal key in table, since table
|
||||
// module should not be relying on db module. However to make things easier
|
||||
// and compatible with existing code, we introduce a wrapper that allows
|
||||
@@ -407,6 +445,7 @@ Status BlockBasedTable::Open(const Options& options, const EnvOptions& soptions,
|
||||
rep->file = std::move(file);
|
||||
rep->footer = footer;
|
||||
rep->index_type = table_options.index_type;
|
||||
rep->hash_index_allow_collision = table_options.hash_index_allow_collision;
|
||||
SetupCacheKeyPrefix(rep);
|
||||
unique_ptr<BlockBasedTable> new_table(new BlockBasedTable(rep));
|
||||
|
||||
@@ -914,6 +953,10 @@ class BlockBasedTable::BlockEntryIteratorState : public TwoLevelIteratorState {
|
||||
//
|
||||
// REQUIRES: this method shouldn't be called while the DB lock is held.
|
||||
bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
if (!rep_->options.filter_policy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(rep_->options.prefix_extractor != nullptr);
|
||||
auto prefix = rep_->options.prefix_extractor->Transform(
|
||||
ExtractUserKey(internal_key));
|
||||
@@ -923,10 +966,6 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
bool may_match = true;
|
||||
Status s;
|
||||
|
||||
if (!rep_->options.filter_policy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// To prevent any io operation in this method, we set `read_tier` to make
|
||||
// sure we always read index or filter only when they have already been
|
||||
// loaded to memory.
|
||||
@@ -980,10 +1019,11 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
return may_match;
|
||||
}
|
||||
|
||||
Iterator* BlockBasedTable::NewIterator(const ReadOptions& read_options) {
|
||||
return NewTwoLevelIterator(new BlockEntryIteratorState(this, read_options,
|
||||
nullptr),
|
||||
NewIndexIterator(read_options));
|
||||
Iterator* BlockBasedTable::NewIterator(const ReadOptions& read_options,
|
||||
Arena* arena) {
|
||||
return NewTwoLevelIterator(
|
||||
new BlockEntryIteratorState(this, read_options, nullptr),
|
||||
NewIndexIterator(read_options), arena);
|
||||
}
|
||||
|
||||
Status BlockBasedTable::Get(
|
||||
@@ -1092,6 +1132,15 @@ Status BlockBasedTable::CreateIndexReader(IndexReader** index_reader,
|
||||
auto comparator = &rep_->internal_comparator;
|
||||
const Footer& footer = rep_->footer;
|
||||
|
||||
if (index_type_on_file == BlockBasedTableOptions::kHashSearch &&
|
||||
rep_->options.prefix_extractor == nullptr) {
|
||||
Log(rep_->options.info_log,
|
||||
"BlockBasedTableOptions::kHashSearch requires "
|
||||
"options.prefix_extractor to be set."
|
||||
" Fall back to binary seach index.");
|
||||
index_type_on_file = BlockBasedTableOptions::kBinarySearch;
|
||||
}
|
||||
|
||||
switch (index_type_on_file) {
|
||||
case BlockBasedTableOptions::kBinarySearch: {
|
||||
return BinarySearchIndexReader::Create(
|
||||
@@ -1104,24 +1153,25 @@ Status BlockBasedTable::CreateIndexReader(IndexReader** index_reader,
|
||||
if (meta_index_iter == nullptr) {
|
||||
auto s = ReadMetaBlock(rep_, &meta_guard, &meta_iter_guard);
|
||||
if (!s.ok()) {
|
||||
return Status::Corruption("Unable to read the metaindex block");
|
||||
// we simply fall back to binary search in case there is any
|
||||
// problem with prefix hash index loading.
|
||||
Log(rep_->options.info_log,
|
||||
"Unable to read the metaindex block."
|
||||
" Fall back to binary seach index.");
|
||||
return BinarySearchIndexReader::Create(
|
||||
file, footer, footer.index_handle(), env, comparator, index_reader);
|
||||
}
|
||||
meta_index_iter = meta_iter_guard.get();
|
||||
}
|
||||
|
||||
// We need to wrap data with internal_prefix_transform to make sure it can
|
||||
// handle prefix correctly.
|
||||
if (rep_->options.prefix_extractor == nullptr) {
|
||||
return Status::InvalidArgument(
|
||||
"BlockBasedTableOptions::kHashSearch requires "
|
||||
"options.prefix_extractor to be set.");
|
||||
}
|
||||
|
||||
rep_->internal_prefix_transform.reset(
|
||||
new InternalKeySliceTransform(rep_->options.prefix_extractor.get()));
|
||||
return HashIndexReader::Create(
|
||||
rep_->internal_prefix_transform.get(), footer, file, env, comparator,
|
||||
footer.index_handle(), meta_index_iter, index_reader);
|
||||
footer.index_handle(), meta_index_iter, index_reader,
|
||||
rep_->hash_index_allow_collision);
|
||||
}
|
||||
default: {
|
||||
std::string error_message =
|
||||
|
||||
@@ -68,7 +68,7 @@ class BlockBasedTable : public TableReader {
|
||||
// Returns a new iterator over the table contents.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
Iterator* NewIterator(const ReadOptions&) override;
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr) override;
|
||||
|
||||
Status Get(const ReadOptions& readOptions, const Slice& key,
|
||||
void* handle_context,
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "table/block_prefix_index.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
inline uint32_t Hash(const Slice& s) {
|
||||
return rocksdb::Hash(s.data(), s.size(), 0);
|
||||
}
|
||||
|
||||
inline uint32_t PrefixToBucket(const Slice& prefix, uint32_t num_buckets) {
|
||||
return Hash(prefix) % num_buckets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The prefix block index is simply a bucket array, with each entry pointing to
|
||||
// the blocks that span the prefixes hashed to this bucket.
|
||||
//
|
||||
// To reduce memory footprint, if there is only one block per bucket, the entry
|
||||
// stores the block id directly. If there are more than one blocks per bucket,
|
||||
// because of hash collision or a single prefix spanning multiple blocks,
|
||||
// the entry points to an array of block ids. The block array is an array of
|
||||
// uint32_t's. The first uint32_t indicates the total number of blocks, followed
|
||||
// by the block ids.
|
||||
//
|
||||
// To differentiate the two cases, the high order bit of the entry indicates
|
||||
// whether it is a 'pointer' into a separate block array.
|
||||
// 0x7FFFFFFF is reserved for empty bucket.
|
||||
|
||||
const uint32_t kNoneBlock = 0x7FFFFFFF;
|
||||
const uint32_t kBlockArrayMask = 0x80000000;
|
||||
|
||||
inline bool IsNone(uint32_t block_id) {
|
||||
return block_id == kNoneBlock;
|
||||
}
|
||||
|
||||
inline bool IsBlockId(uint32_t block_id) {
|
||||
return (block_id & kBlockArrayMask) == 0;
|
||||
}
|
||||
|
||||
inline uint32_t DecodeIndex(uint32_t block_id) {
|
||||
uint32_t index = block_id ^ kBlockArrayMask;
|
||||
assert(index < kBlockArrayMask);
|
||||
return index;
|
||||
}
|
||||
|
||||
inline uint32_t EncodeIndex(uint32_t index) {
|
||||
assert(index < kBlockArrayMask);
|
||||
return index | kBlockArrayMask;
|
||||
}
|
||||
|
||||
|
||||
// temporary storage for prefix information during index building
|
||||
struct PrefixRecord {
|
||||
Slice prefix;
|
||||
uint32_t start_block;
|
||||
uint32_t end_block;
|
||||
uint32_t num_blocks;
|
||||
PrefixRecord* next;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlockPrefixIndex::Builder {
|
||||
public:
|
||||
explicit Builder(const SliceTransform* internal_prefix_extractor)
|
||||
: internal_prefix_extractor_(internal_prefix_extractor) {}
|
||||
|
||||
void Add(const Slice& key_prefix, uint32_t start_block,
|
||||
uint32_t num_blocks) {
|
||||
PrefixRecord* record = reinterpret_cast<PrefixRecord*>(
|
||||
arena_.AllocateAligned(sizeof(PrefixRecord)));
|
||||
record->prefix = key_prefix;
|
||||
record->start_block = start_block;
|
||||
record->end_block = start_block + num_blocks - 1;
|
||||
record->num_blocks = num_blocks;
|
||||
prefixes_.push_back(record);
|
||||
}
|
||||
|
||||
BlockPrefixIndex* Finish() {
|
||||
// For now, use roughly 1:1 prefix to bucket ratio.
|
||||
uint32_t num_buckets = prefixes_.size() + 1;
|
||||
|
||||
// Collect prefix records that hash to the same bucket, into a single
|
||||
// linklist.
|
||||
std::vector<PrefixRecord*> prefixes_per_bucket(num_buckets, nullptr);
|
||||
std::vector<uint32_t> num_blocks_per_bucket(num_buckets, 0);
|
||||
for (PrefixRecord* current : prefixes_) {
|
||||
uint32_t bucket = PrefixToBucket(current->prefix, num_buckets);
|
||||
// merge the prefix block span if the first block of this prefix is
|
||||
// connected to the last block of the previous prefix.
|
||||
PrefixRecord* prev = prefixes_per_bucket[bucket];
|
||||
if (prev) {
|
||||
assert(current->start_block >= prev->end_block);
|
||||
auto distance = current->start_block - prev->end_block;
|
||||
if (distance <= 1) {
|
||||
prev->end_block = current->end_block;
|
||||
prev->num_blocks = prev->end_block - prev->start_block + 1;
|
||||
num_blocks_per_bucket[bucket] += (current->num_blocks + distance - 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
current->next = prev;
|
||||
prefixes_per_bucket[bucket] = current;
|
||||
num_blocks_per_bucket[bucket] += current->num_blocks;
|
||||
}
|
||||
|
||||
// Calculate the block array buffer size
|
||||
uint32_t total_block_array_entries = 0;
|
||||
for (uint32_t i = 0; i < num_buckets; i++) {
|
||||
uint32_t num_blocks = num_blocks_per_bucket[i];
|
||||
if (num_blocks > 1) {
|
||||
total_block_array_entries += (num_blocks + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the final prefix block index
|
||||
uint32_t* block_array_buffer = new uint32_t[total_block_array_entries];
|
||||
uint32_t* buckets = new uint32_t[num_buckets];
|
||||
uint32_t offset = 0;
|
||||
for (uint32_t i = 0; i < num_buckets; i++) {
|
||||
uint32_t num_blocks = num_blocks_per_bucket[i];
|
||||
if (num_blocks == 0) {
|
||||
assert(prefixes_per_bucket[i] == nullptr);
|
||||
buckets[i] = kNoneBlock;
|
||||
} else if (num_blocks == 1) {
|
||||
assert(prefixes_per_bucket[i] != nullptr);
|
||||
assert(prefixes_per_bucket[i]->next == nullptr);
|
||||
buckets[i] = prefixes_per_bucket[i]->start_block;
|
||||
} else {
|
||||
assert(prefixes_per_bucket[i] != nullptr);
|
||||
buckets[i] = EncodeIndex(offset);
|
||||
block_array_buffer[offset] = num_blocks;
|
||||
uint32_t* last_block = &block_array_buffer[offset + num_blocks];
|
||||
auto current = prefixes_per_bucket[i];
|
||||
// populate block ids from largest to smallest
|
||||
while (current != nullptr) {
|
||||
for (uint32_t i = 0; i < current->num_blocks; i++) {
|
||||
*last_block = current->end_block - i;
|
||||
last_block--;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
assert(last_block == &block_array_buffer[offset]);
|
||||
offset += (num_blocks + 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert(offset == total_block_array_entries);
|
||||
|
||||
return new BlockPrefixIndex(internal_prefix_extractor_, num_buckets,
|
||||
buckets, total_block_array_entries,
|
||||
block_array_buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
const SliceTransform* internal_prefix_extractor_;
|
||||
|
||||
std::vector<PrefixRecord*> prefixes_;
|
||||
Arena arena_;
|
||||
};
|
||||
|
||||
|
||||
Status BlockPrefixIndex::Create(const SliceTransform* internal_prefix_extractor,
|
||||
const Slice& prefixes, const Slice& prefix_meta,
|
||||
BlockPrefixIndex** prefix_index) {
|
||||
uint64_t pos = 0;
|
||||
auto meta_pos = prefix_meta;
|
||||
Status s;
|
||||
Builder builder(internal_prefix_extractor);
|
||||
|
||||
while (!meta_pos.empty()) {
|
||||
uint32_t prefix_size = 0;
|
||||
uint32_t entry_index = 0;
|
||||
uint32_t num_blocks = 0;
|
||||
if (!GetVarint32(&meta_pos, &prefix_size) ||
|
||||
!GetVarint32(&meta_pos, &entry_index) ||
|
||||
!GetVarint32(&meta_pos, &num_blocks)) {
|
||||
s = Status::Corruption(
|
||||
"Corrupted prefix meta block: unable to read from it.");
|
||||
break;
|
||||
}
|
||||
if (pos + prefix_size > prefixes.size()) {
|
||||
s = Status::Corruption(
|
||||
"Corrupted prefix meta block: size inconsistency.");
|
||||
break;
|
||||
}
|
||||
Slice prefix(prefixes.data() + pos, prefix_size);
|
||||
builder.Add(prefix, entry_index, num_blocks);
|
||||
|
||||
pos += prefix_size;
|
||||
}
|
||||
|
||||
if (s.ok() && pos != prefixes.size()) {
|
||||
s = Status::Corruption("Corrupted prefix meta block");
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
*prefix_index = builder.Finish();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
const uint32_t BlockPrefixIndex::GetBlocks(const Slice& key,
|
||||
uint32_t** blocks) {
|
||||
Slice prefix = internal_prefix_extractor_->Transform(key);
|
||||
|
||||
uint32_t bucket = PrefixToBucket(prefix, num_buckets_);
|
||||
uint32_t block_id = buckets_[bucket];
|
||||
|
||||
if (IsNone(block_id)) {
|
||||
return 0;
|
||||
} else if (IsBlockId(block_id)) {
|
||||
*blocks = &buckets_[bucket];
|
||||
return 1;
|
||||
} else {
|
||||
uint32_t index = DecodeIndex(block_id);
|
||||
assert(index < num_block_array_buffer_entries_);
|
||||
*blocks = &block_array_buffer_[index+1];
|
||||
uint32_t num_blocks = block_array_buffer_[index];
|
||||
assert(num_blocks > 1);
|
||||
assert(index + num_blocks < num_block_array_buffer_entries_);
|
||||
return num_blocks;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Comparator;
|
||||
class Iterator;
|
||||
class Slice;
|
||||
class SliceTransform;
|
||||
|
||||
// Build a hash-based index to speed up the lookup for "index block".
|
||||
// BlockHashIndex accepts a key and, if found, returns its restart index within
|
||||
// that index block.
|
||||
class BlockPrefixIndex {
|
||||
public:
|
||||
|
||||
// Maps a key to a list of data blocks that could potentially contain
|
||||
// the key, based on the prefix.
|
||||
// Returns the total number of relevant blocks, 0 means the key does
|
||||
// not exist.
|
||||
const uint32_t GetBlocks(const Slice& key, uint32_t** blocks);
|
||||
|
||||
size_t ApproximateMemoryUsage() const {
|
||||
return sizeof(BlockPrefixIndex) +
|
||||
(num_block_array_buffer_entries_ + num_buckets_) * sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// Create hash index by reading from the metadata blocks.
|
||||
// @params prefixes: a sequence of prefixes.
|
||||
// @params prefix_meta: contains the "metadata" to of the prefixes.
|
||||
static Status Create(const SliceTransform* hash_key_extractor,
|
||||
const Slice& prefixes, const Slice& prefix_meta,
|
||||
BlockPrefixIndex** prefix_index);
|
||||
|
||||
~BlockPrefixIndex() {
|
||||
delete[] buckets_;
|
||||
delete[] block_array_buffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
class Builder;
|
||||
friend Builder;
|
||||
|
||||
BlockPrefixIndex(const SliceTransform* internal_prefix_extractor,
|
||||
uint32_t num_buckets,
|
||||
uint32_t* buckets,
|
||||
uint32_t num_block_array_buffer_entries,
|
||||
uint32_t* block_array_buffer)
|
||||
: internal_prefix_extractor_(internal_prefix_extractor),
|
||||
num_buckets_(num_buckets),
|
||||
num_block_array_buffer_entries_(num_block_array_buffer_entries),
|
||||
buckets_(buckets),
|
||||
block_array_buffer_(block_array_buffer) {}
|
||||
|
||||
const SliceTransform* internal_prefix_extractor_;
|
||||
uint32_t num_buckets_;
|
||||
uint32_t num_block_array_buffer_entries_;
|
||||
uint32_t* buckets_;
|
||||
uint32_t* block_array_buffer_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -8,6 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -65,8 +67,26 @@ Iterator* NewEmptyIterator() {
|
||||
return new EmptyIterator(Status::OK());
|
||||
}
|
||||
|
||||
Iterator* NewEmptyIterator(Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return NewEmptyIterator();
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(EmptyIterator));
|
||||
return new (mem) EmptyIterator(Status::OK());
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* NewErrorIterator(const Status& status) {
|
||||
return new EmptyIterator(status);
|
||||
}
|
||||
|
||||
Iterator* NewErrorIterator(const Status& status, Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return NewErrorIterator(status);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(EmptyIterator));
|
||||
return new (mem) EmptyIterator(status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A internal wrapper class with an interface similar to Iterator that
|
||||
@@ -20,14 +23,7 @@ class IteratorWrapper {
|
||||
explicit IteratorWrapper(Iterator* iter): iter_(nullptr) {
|
||||
Set(iter);
|
||||
}
|
||||
IteratorWrapper(const IteratorWrapper&) {
|
||||
// Iterator wrapper exclusively owns iter_ so it cannot be copied.
|
||||
// Didn't delete the function because vector<IteratorWrapper> requires
|
||||
// this function to compile.
|
||||
assert(false);
|
||||
}
|
||||
void operator=(const IteratorWrapper&) = delete;
|
||||
~IteratorWrapper() { delete iter_; }
|
||||
~IteratorWrapper() {}
|
||||
Iterator* iter() const { return iter_; }
|
||||
|
||||
// Takes ownership of "iter" and will delete it when destroyed, or
|
||||
@@ -42,6 +38,14 @@ class IteratorWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteIter(bool is_arena_mode) {
|
||||
if (!is_arena_mode) {
|
||||
delete iter_;
|
||||
} else {
|
||||
iter_->~Iterator();
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator interface methods
|
||||
bool Valid() const { return valid_; }
|
||||
Slice key() const { assert(Valid()); return key_; }
|
||||
@@ -67,4 +71,11 @@ class IteratorWrapper {
|
||||
Slice key_;
|
||||
};
|
||||
|
||||
class Arena;
|
||||
// Return an empty iterator (yields nothing) allocated from arena.
|
||||
extern Iterator* NewEmptyIterator(Arena* arena);
|
||||
|
||||
// Return an empty iterator with the specified status, allocated arena.
|
||||
extern Iterator* NewErrorIterator(const Status& status, Arena* arena);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+60
-8
@@ -17,13 +17,13 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "table/iter_heap.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/perf_context_imp.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace {
|
||||
|
||||
typedef std::priority_queue<
|
||||
IteratorWrapper*,
|
||||
std::vector<IteratorWrapper*>,
|
||||
@@ -43,13 +43,16 @@ MaxIterHeap NewMaxIterHeap(const Comparator* comparator) {
|
||||
MinIterHeap NewMinIterHeap(const Comparator* comparator) {
|
||||
return MinIterHeap(MinIteratorComparator(comparator));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const size_t kNumIterReserve = 4;
|
||||
|
||||
class MergingIterator : public Iterator {
|
||||
public:
|
||||
MergingIterator(const Comparator* comparator, Iterator** children, int n)
|
||||
: comparator_(comparator),
|
||||
MergingIterator(const Comparator* comparator, Iterator** children, int n,
|
||||
bool is_arena_mode)
|
||||
: is_arena_mode_(is_arena_mode),
|
||||
comparator_(comparator),
|
||||
current_(nullptr),
|
||||
use_heap_(true),
|
||||
direction_(kForward),
|
||||
@@ -66,7 +69,20 @@ class MergingIterator : public Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~MergingIterator() { }
|
||||
virtual void AddIterator(Iterator* iter) {
|
||||
assert(direction_ == kForward);
|
||||
children_.emplace_back(iter);
|
||||
auto new_wrapper = children_.back();
|
||||
if (new_wrapper.Valid()) {
|
||||
minHeap_.push(&new_wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~MergingIterator() {
|
||||
for (auto& child : children_) {
|
||||
child.DeleteIter(is_arena_mode_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool Valid() const {
|
||||
return (current_ != nullptr);
|
||||
@@ -242,6 +258,7 @@ class MergingIterator : public Iterator {
|
||||
void FindLargest();
|
||||
void ClearHeaps();
|
||||
|
||||
bool is_arena_mode_;
|
||||
const Comparator* comparator_;
|
||||
autovector<IteratorWrapper, kNumIterReserve> children_;
|
||||
IteratorWrapper* current_;
|
||||
@@ -288,16 +305,51 @@ void MergingIterator::ClearHeaps() {
|
||||
maxHeap_ = NewMaxIterHeap(comparator_);
|
||||
minHeap_ = NewMinIterHeap(comparator_);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Iterator* NewMergingIterator(const Comparator* cmp, Iterator** list, int n) {
|
||||
Iterator* NewMergingIterator(const Comparator* cmp, Iterator** list, int n,
|
||||
Arena* arena) {
|
||||
assert(n >= 0);
|
||||
if (n == 0) {
|
||||
return NewEmptyIterator();
|
||||
return NewEmptyIterator(arena);
|
||||
} else if (n == 1) {
|
||||
return list[0];
|
||||
} else {
|
||||
return new MergingIterator(cmp, list, n);
|
||||
if (arena == nullptr) {
|
||||
return new MergingIterator(cmp, list, n, false);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(MergingIterator));
|
||||
return new (mem) MergingIterator(cmp, list, n, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MergeIteratorBuilder::MergeIteratorBuilder(const Comparator* comparator,
|
||||
Arena* a)
|
||||
: first_iter(nullptr), use_merging_iter(false), arena(a) {
|
||||
|
||||
auto mem = arena->AllocateAligned(sizeof(MergingIterator));
|
||||
merge_iter = new (mem) MergingIterator(comparator, nullptr, 0, true);
|
||||
}
|
||||
|
||||
void MergeIteratorBuilder::AddIterator(Iterator* iter) {
|
||||
if (!use_merging_iter && first_iter != nullptr) {
|
||||
merge_iter->AddIterator(first_iter);
|
||||
use_merging_iter = true;
|
||||
}
|
||||
if (use_merging_iter) {
|
||||
merge_iter->AddIterator(iter);
|
||||
} else {
|
||||
first_iter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* MergeIteratorBuilder::Finish() {
|
||||
if (!use_merging_iter) {
|
||||
return first_iter;
|
||||
} else {
|
||||
auto ret = merge_iter;
|
||||
merge_iter = nullptr;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-1
@@ -9,11 +9,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Comparator;
|
||||
class Iterator;
|
||||
class Env;
|
||||
class Arena;
|
||||
|
||||
// Return an iterator that provided the union of the data in
|
||||
// children[0,n-1]. Takes ownership of the child iterators and
|
||||
@@ -24,6 +27,34 @@ class Env;
|
||||
//
|
||||
// REQUIRES: n >= 0
|
||||
extern Iterator* NewMergingIterator(const Comparator* comparator,
|
||||
Iterator** children, int n);
|
||||
Iterator** children, int n,
|
||||
Arena* arena = nullptr);
|
||||
|
||||
class MergingIterator;
|
||||
|
||||
// A builder class to build a merging iterator by adding iterators one by one.
|
||||
class MergeIteratorBuilder {
|
||||
public:
|
||||
// comparator: the comparator used in merging comparator
|
||||
// arena: where the merging iterator needs to be allocated from.
|
||||
explicit MergeIteratorBuilder(const Comparator* comparator, Arena* arena);
|
||||
~MergeIteratorBuilder() {}
|
||||
|
||||
// Add iter to the merging iterator.
|
||||
void AddIterator(Iterator* iter);
|
||||
|
||||
// Get arena used to build the merging iterator. It is called one a child
|
||||
// iterator needs to be allocated.
|
||||
Arena* GetArena() { return arena; }
|
||||
|
||||
// Return the result merging iterator.
|
||||
Iterator* Finish();
|
||||
|
||||
private:
|
||||
MergingIterator* merge_iter;
|
||||
Iterator* first_iter;
|
||||
bool use_merging_iter;
|
||||
Arena* arena;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/plain_table_builder.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "table/block_builder.h"
|
||||
@@ -52,10 +54,14 @@ Status WriteBlock(
|
||||
extern const uint64_t kPlainTableMagicNumber = 0x8242229663bf9564ull;
|
||||
extern const uint64_t kLegacyPlainTableMagicNumber = 0x4f3418eb7a8f13b8ull;
|
||||
|
||||
PlainTableBuilder::PlainTableBuilder(const Options& options,
|
||||
WritableFile* file,
|
||||
uint32_t user_key_len) :
|
||||
options_(options), file_(file), user_key_len_(user_key_len) {
|
||||
PlainTableBuilder::PlainTableBuilder(const Options& options, WritableFile* file,
|
||||
uint32_t user_key_len,
|
||||
EncodingType encoding_type,
|
||||
size_t index_sparseness)
|
||||
: options_(options),
|
||||
file_(file),
|
||||
encoder_(encoding_type, user_key_len, options.prefix_extractor.get(),
|
||||
index_sparseness) {
|
||||
properties_.fixed_key_len = user_key_len;
|
||||
|
||||
// for plain table, we put all the data in a big chuck.
|
||||
@@ -64,7 +70,20 @@ PlainTableBuilder::PlainTableBuilder(const Options& options,
|
||||
// filter block.
|
||||
properties_.index_size = 0;
|
||||
properties_.filter_size = 0;
|
||||
properties_.format_version = 0;
|
||||
// To support roll-back to previous version, now still use version 0 for
|
||||
// plain encoding.
|
||||
properties_.format_version = (encoding_type == kPlain) ? 0 : 1;
|
||||
|
||||
if (options_.prefix_extractor) {
|
||||
properties_.user_collected_properties
|
||||
[PlainTablePropertyNames::kPrefixExtractorName] =
|
||||
options_.prefix_extractor->Name();
|
||||
}
|
||||
|
||||
std::string val;
|
||||
PutFixed32(&val, static_cast<uint32_t>(encoder_.GetEncodingType()));
|
||||
properties_.user_collected_properties
|
||||
[PlainTablePropertyNames::kEncodingType] = val;
|
||||
|
||||
for (auto& collector_factories :
|
||||
options.table_properties_collector_factories) {
|
||||
@@ -77,51 +96,25 @@ PlainTableBuilder::~PlainTableBuilder() {
|
||||
}
|
||||
|
||||
void PlainTableBuilder::Add(const Slice& key, const Slice& value) {
|
||||
size_t user_key_size = key.size() - 8;
|
||||
assert(user_key_len_ == 0 || user_key_size == user_key_len_);
|
||||
// temp buffer for metadata bytes between key and value.
|
||||
char meta_bytes_buf[6];
|
||||
size_t meta_bytes_buf_size = 0;
|
||||
|
||||
if (!IsFixedLength()) {
|
||||
// Write key length
|
||||
char key_size_buf[5]; // tmp buffer for key size as varint32
|
||||
char* ptr = EncodeVarint32(key_size_buf, user_key_size);
|
||||
assert(ptr <= key_size_buf + sizeof(key_size_buf));
|
||||
auto len = ptr - key_size_buf;
|
||||
file_->Append(Slice(key_size_buf, len));
|
||||
offset_ += len;
|
||||
}
|
||||
|
||||
// Write key
|
||||
ParsedInternalKey parsed_key;
|
||||
if (!ParseInternalKey(key, &parsed_key)) {
|
||||
status_ = Status::Corruption(Slice());
|
||||
return;
|
||||
}
|
||||
// For value size as varint32 (up to 5 bytes).
|
||||
// If the row is of value type with seqId 0, flush the special flag together
|
||||
// in this buffer to safe one file append call, which takes 1 byte.
|
||||
char value_size_buf[6];
|
||||
size_t value_size_buf_size = 0;
|
||||
if (parsed_key.sequence == 0 && parsed_key.type == kTypeValue) {
|
||||
file_->Append(Slice(key.data(), user_key_size));
|
||||
offset_ += user_key_size;
|
||||
value_size_buf[0] = PlainTableFactory::kValueTypeSeqId0;
|
||||
value_size_buf_size = 1;
|
||||
} else {
|
||||
file_->Append(key);
|
||||
offset_ += key.size();
|
||||
}
|
||||
// Write out the key
|
||||
encoder_.AppendKey(key, file_, &offset_, meta_bytes_buf,
|
||||
&meta_bytes_buf_size);
|
||||
|
||||
// Write value length
|
||||
int value_size = value.size();
|
||||
char* end_ptr =
|
||||
EncodeVarint32(value_size_buf + value_size_buf_size, value_size);
|
||||
assert(end_ptr <= value_size_buf + sizeof(value_size_buf));
|
||||
value_size_buf_size = end_ptr - value_size_buf;
|
||||
file_->Append(Slice(value_size_buf, value_size_buf_size));
|
||||
EncodeVarint32(meta_bytes_buf + meta_bytes_buf_size, value_size);
|
||||
assert(end_ptr <= meta_bytes_buf + sizeof(meta_bytes_buf));
|
||||
meta_bytes_buf_size = end_ptr - meta_bytes_buf;
|
||||
file_->Append(Slice(meta_bytes_buf, meta_bytes_buf_size));
|
||||
|
||||
// Write value
|
||||
file_->Append(value);
|
||||
offset_ += value_size + value_size_buf_size;
|
||||
offset_ += value_size + meta_bytes_buf_size;
|
||||
|
||||
properties_.num_entries++;
|
||||
properties_.raw_key_size += key.size();
|
||||
@@ -150,6 +143,8 @@ Status PlainTableBuilder::Finish() {
|
||||
// -- Add basic properties
|
||||
property_block_builder.AddTableProperty(properties_);
|
||||
|
||||
property_block_builder.Add(properties_.user_collected_properties);
|
||||
|
||||
// -- Add user collected properties
|
||||
NotifyCollectTableCollectorsOnFinish(table_properties_collectors_,
|
||||
options_.info_log.get(),
|
||||
|
||||
+11
-14
@@ -1,9 +1,7 @@
|
||||
// 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.
|
||||
//
|
||||
// IndexedTable is a simple table format for UNIT TEST ONLY. It is not built
|
||||
// as production quality.
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -12,6 +10,8 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "table/plain_table_key_coding.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -22,14 +22,15 @@ class WritableFile;
|
||||
class TableBuilder;
|
||||
|
||||
class PlainTableBuilder: public TableBuilder {
|
||||
public:
|
||||
public:
|
||||
// Create a builder that will store the contents of the table it is
|
||||
// building in *file. Does not close the file. It is up to the
|
||||
// caller to close the file after calling Finish(). The output file
|
||||
// will be part of level specified by 'level'. A value of -1 means
|
||||
// that the caller does not know which level the output file will reside.
|
||||
PlainTableBuilder(const Options& options, WritableFile* file,
|
||||
uint32_t user_key_size);
|
||||
uint32_t user_key_size, EncodingType encoding_type,
|
||||
size_t index_sparseness);
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
~PlainTableBuilder();
|
||||
@@ -61,7 +62,7 @@ public:
|
||||
// Finish() call, returns the size of the final generated file.
|
||||
uint64_t FileSize() const override;
|
||||
|
||||
private:
|
||||
private:
|
||||
Options options_;
|
||||
std::vector<std::unique_ptr<TablePropertiesCollector>>
|
||||
table_properties_collectors_;
|
||||
@@ -69,14 +70,10 @@ private:
|
||||
uint64_t offset_ = 0;
|
||||
Status status_;
|
||||
TableProperties properties_;
|
||||
PlainTableKeyEncoder encoder_;
|
||||
|
||||
const size_t user_key_len_;
|
||||
bool closed_ = false; // Either Finish() or Abandon() has been called.
|
||||
|
||||
bool IsFixedLength() const {
|
||||
return user_key_len_ > 0;
|
||||
}
|
||||
|
||||
// No copying allowed
|
||||
PlainTableBuilder(const PlainTableBuilder&) = delete;
|
||||
void operator=(const PlainTableBuilder&) = delete;
|
||||
|
||||
@@ -23,32 +23,42 @@ Status PlainTableFactory::NewTableReader(const Options& options,
|
||||
return PlainTableReader::Open(options, soptions, icomp, std::move(file),
|
||||
file_size, table, bloom_bits_per_key_,
|
||||
hash_table_ratio_, index_sparseness_,
|
||||
huge_page_tlb_size_);
|
||||
huge_page_tlb_size_, full_scan_mode_);
|
||||
}
|
||||
|
||||
TableBuilder* PlainTableFactory::NewTableBuilder(
|
||||
const Options& options, const InternalKeyComparator& internal_comparator,
|
||||
WritableFile* file, CompressionType compression_type) const {
|
||||
return new PlainTableBuilder(options, file, user_key_len_);
|
||||
return new PlainTableBuilder(options, file, user_key_len_, encoding_type_,
|
||||
index_sparseness_);
|
||||
}
|
||||
|
||||
extern TableFactory* NewPlainTableFactory(uint32_t user_key_len,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
size_t huge_page_tlb_size) {
|
||||
size_t huge_page_tlb_size,
|
||||
EncodingType encoding_type) {
|
||||
return new PlainTableFactory(user_key_len, bloom_bits_per_key,
|
||||
hash_table_ratio, index_sparseness,
|
||||
huge_page_tlb_size);
|
||||
huge_page_tlb_size, encoding_type);
|
||||
}
|
||||
|
||||
extern TableFactory* NewTotalOrderPlainTableFactory(uint32_t user_key_len,
|
||||
int bloom_bits_per_key,
|
||||
size_t index_sparseness,
|
||||
size_t huge_page_tlb_size) {
|
||||
size_t huge_page_tlb_size,
|
||||
bool full_scan_mode) {
|
||||
return new PlainTableFactory(user_key_len, bloom_bits_per_key, 0,
|
||||
index_sparseness, huge_page_tlb_size);
|
||||
index_sparseness, huge_page_tlb_size, kPlain,
|
||||
full_scan_mode);
|
||||
}
|
||||
|
||||
const std::string PlainTablePropertyNames::kPrefixExtractorName =
|
||||
"rocksdb.prefix.extractor.name";
|
||||
|
||||
const std::string PlainTablePropertyNames::kEncodingType =
|
||||
"rocksdb.plain.table.encoding.type";
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
@@ -27,20 +28,103 @@ class TableBuilder;
|
||||
// parameter of the factory class. Output file format:
|
||||
// +-------------+-----------------+
|
||||
// | version | user_key_length |
|
||||
// +------------++------------------------------+ <= key1 offset
|
||||
// | [key_size] | key1 | value_size | |
|
||||
// +------------++------------+-----------------+ <= key1 offset
|
||||
// | encoded key1 | value_size | |
|
||||
// +------------+-------------+-------------+ |
|
||||
// | value1 |
|
||||
// | |
|
||||
// +----------------------------------------+---+ <= key2 offset
|
||||
// | [key_size] | key2 | value_size | |
|
||||
// +--------------------------+-------------+---+ <= key2 offset
|
||||
// | encoded key2 | value_size | |
|
||||
// +------------+-------------+-------------+ |
|
||||
// | value2 |
|
||||
// | |
|
||||
// | ...... |
|
||||
// +-----------------+--------------------------+
|
||||
// If user_key_length = kPlainTableVariableLength, it means the key is variable
|
||||
// length, there will be an extra field for key size encoded before every key.
|
||||
//
|
||||
// When the key encoding type is kPlain. Key part is encoded as:
|
||||
// +------------+--------------------+
|
||||
// | [key_size] | internal key |
|
||||
// +------------+--------------------+
|
||||
// for the case of user_key_len = kPlainTableVariableLength case,
|
||||
// and simply:
|
||||
// +----------------------+
|
||||
// | internal key |
|
||||
// +----------------------+
|
||||
// for user_key_len != kPlainTableVariableLength case.
|
||||
//
|
||||
// If key encoding type is kPrefix. Keys are encoding in this format.
|
||||
// There are three ways to encode a key:
|
||||
// (1) Full Key
|
||||
// +---------------+---------------+-------------------+
|
||||
// | Full Key Flag | Full Key Size | Full Internal Key |
|
||||
// +---------------+---------------+-------------------+
|
||||
// which simply encodes a full key
|
||||
//
|
||||
// (2) A key shared the same prefix as the previous key, which is encoded as
|
||||
// format of (1).
|
||||
// +-------------+-------------+-------------+-------------+------------+
|
||||
// | Prefix Flag | Prefix Size | Suffix Flag | Suffix Size | Key Suffix |
|
||||
// +-------------+-------------+-------------+-------------+------------+
|
||||
// where key is the suffix part of the key, including the internal bytes.
|
||||
// the actual key will be constructed by concatenating prefix part of the
|
||||
// previous key, with the suffix part of the key here, with sizes given here.
|
||||
//
|
||||
// (3) A key shared the same prefix as the previous key, which is encoded as
|
||||
// the format of (2).
|
||||
// +-----------------+-----------------+------------------------+
|
||||
// | Key Suffix Flag | Key Suffix Size | Suffix of Internal Key |
|
||||
// +-----------------+-----------------+------------------------+
|
||||
// The key will be constructed by concatenating previous key's prefix (which is
|
||||
// also a prefix which the last key encoded in the format of (1)) and the
|
||||
// key given here.
|
||||
//
|
||||
// For example, we for following keys (prefix and suffix are separated by
|
||||
// spaces):
|
||||
// 0000 0001
|
||||
// 0000 00021
|
||||
// 0000 0002
|
||||
// 00011 00
|
||||
// 0002 0001
|
||||
// Will be encoded like this:
|
||||
// FK 8 00000001
|
||||
// PF 4 SF 5 00021
|
||||
// SF 4 0002
|
||||
// FK 7 0001100
|
||||
// FK 8 00020001
|
||||
// (where FK means full key flag, PF means prefix flag and SF means suffix flag)
|
||||
//
|
||||
// All those "key flag + key size" shown above are in this format:
|
||||
// The 8 bits of the first byte:
|
||||
// +----+----+----+----+----+----+----+----+
|
||||
// | Type | Size |
|
||||
// +----+----+----+----+----+----+----+----+
|
||||
// Type indicates: full key, prefix, or suffix.
|
||||
// The last 6 bits are for size. If the size bits are not all 1, it means the
|
||||
// size of the key. Otherwise, varint32 is read after this byte. This varint
|
||||
// value + 0x3F (the value of all 1) will be the key size.
|
||||
//
|
||||
// For example, full key with length 16 will be encoded as (binary):
|
||||
// 00 010000
|
||||
// (00 means full key)
|
||||
// and a prefix with 100 bytes will be encoded as:
|
||||
// 01 111111 00100101
|
||||
// (63) (37)
|
||||
// (01 means key suffix)
|
||||
//
|
||||
// All the internal keys above (including kPlain and kPrefix) are encoded in
|
||||
// this format:
|
||||
// There are two types:
|
||||
// (1) normal internal key format
|
||||
// +----------- ...... -------------+----+---+---+---+---+---+---+---+
|
||||
// | user key |type| sequence ID |
|
||||
// +----------- ..... --------------+----+---+---+---+---+---+---+---+
|
||||
// (2) Special case for keys whose sequence ID is 0 and is value type
|
||||
// +----------- ...... -------------+----+
|
||||
// | user key |0x80|
|
||||
// +----------- ..... --------------+----+
|
||||
// To save 7 bytes for the special case where sequence ID = 0.
|
||||
//
|
||||
//
|
||||
class PlainTableFactory : public TableFactory {
|
||||
public:
|
||||
~PlainTableFactory() {}
|
||||
@@ -63,12 +147,16 @@ class PlainTableFactory : public TableFactory {
|
||||
int bloom_bits_per_key = 0,
|
||||
double hash_table_ratio = 0.75,
|
||||
size_t index_sparseness = 16,
|
||||
size_t huge_page_tlb_size = 0)
|
||||
size_t huge_page_tlb_size = 0,
|
||||
EncodingType encoding_type = kPlain,
|
||||
bool full_scan_mode = false)
|
||||
: user_key_len_(user_key_len),
|
||||
bloom_bits_per_key_(bloom_bits_per_key),
|
||||
hash_table_ratio_(hash_table_ratio),
|
||||
index_sparseness_(index_sparseness),
|
||||
huge_page_tlb_size_(huge_page_tlb_size) {}
|
||||
huge_page_tlb_size_(huge_page_tlb_size),
|
||||
encoding_type_(encoding_type),
|
||||
full_scan_mode_(full_scan_mode) {}
|
||||
const char* Name() const override { return "PlainTable"; }
|
||||
Status NewTableReader(const Options& options, const EnvOptions& soptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
@@ -88,6 +176,8 @@ class PlainTableFactory : public TableFactory {
|
||||
double hash_table_ratio_;
|
||||
size_t index_sparseness_;
|
||||
size_t huge_page_tlb_size_;
|
||||
EncodingType encoding_type_;
|
||||
bool full_scan_mode_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#include "table/plain_table_key_coding.h"
|
||||
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
|
||||
enum EntryType : unsigned char {
|
||||
kFullKey = 0,
|
||||
kPrefixFromPreviousKey = 1,
|
||||
kKeySuffix = 2,
|
||||
};
|
||||
|
||||
// Control byte:
|
||||
// First two bits indicate type of entry
|
||||
// Other bytes are inlined sizes. If all bits are 1 (0x03F), overflow bytes
|
||||
// are used. key_size-0x3F will be encoded as a variint32 after this bytes.
|
||||
|
||||
const unsigned char kSizeInlineLimit = 0x3F;
|
||||
|
||||
// Return 0 for error
|
||||
size_t EncodeSize(EntryType type, uint32_t key_size, char* out_buffer) {
|
||||
out_buffer[0] = type << 6;
|
||||
|
||||
if (key_size < 0x3F) {
|
||||
// size inlined
|
||||
out_buffer[0] |= static_cast<char>(key_size);
|
||||
return 1;
|
||||
} else {
|
||||
out_buffer[0] |= kSizeInlineLimit;
|
||||
char* ptr = EncodeVarint32(out_buffer + 1, key_size - kSizeInlineLimit);
|
||||
return ptr - out_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
// Return position after the size byte(s). nullptr means error
|
||||
const char* DecodeSize(const char* offset, const char* limit,
|
||||
EntryType* entry_type, size_t* key_size) {
|
||||
assert(offset < limit);
|
||||
*entry_type = static_cast<EntryType>(
|
||||
(static_cast<unsigned char>(offset[0]) & ~kSizeInlineLimit) >> 6);
|
||||
char inline_key_size = offset[0] & kSizeInlineLimit;
|
||||
if (inline_key_size < kSizeInlineLimit) {
|
||||
*key_size = inline_key_size;
|
||||
return offset + 1;
|
||||
} else {
|
||||
uint32_t extra_size;
|
||||
const char* ptr = GetVarint32Ptr(offset + 1, limit, &extra_size);
|
||||
if (ptr == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
*key_size = kSizeInlineLimit + extra_size;
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status PlainTableKeyEncoder::AppendKey(const Slice& key, WritableFile* file,
|
||||
uint64_t* offset, char* meta_bytes_buf,
|
||||
size_t* meta_bytes_buf_size) {
|
||||
ParsedInternalKey parsed_key;
|
||||
if (!ParseInternalKey(key, &parsed_key)) {
|
||||
return Status::Corruption(Slice());
|
||||
}
|
||||
|
||||
Slice key_to_write = key; // Portion of internal key to write out.
|
||||
|
||||
size_t user_key_size = fixed_user_key_len_;
|
||||
if (encoding_type_ == kPlain) {
|
||||
if (fixed_user_key_len_ == kPlainTableVariableLength) {
|
||||
user_key_size = key.size() - 8;
|
||||
// Write key length
|
||||
char key_size_buf[5]; // tmp buffer for key size as varint32
|
||||
char* ptr = EncodeVarint32(key_size_buf, user_key_size);
|
||||
assert(ptr <= key_size_buf + sizeof(key_size_buf));
|
||||
auto len = ptr - key_size_buf;
|
||||
Status s = file->Append(Slice(key_size_buf, len));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*offset += len;
|
||||
}
|
||||
} else {
|
||||
assert(encoding_type_ == kPrefix);
|
||||
char size_bytes[12];
|
||||
size_t size_bytes_pos = 0;
|
||||
|
||||
user_key_size = key.size() - 8;
|
||||
|
||||
Slice prefix =
|
||||
prefix_extractor_->Transform(Slice(key.data(), user_key_size));
|
||||
if (key_count_for_prefix == 0 || prefix != pre_prefix_.GetKey() ||
|
||||
key_count_for_prefix % index_sparseness_ == 0) {
|
||||
key_count_for_prefix = 1;
|
||||
pre_prefix_.SetKey(prefix);
|
||||
size_bytes_pos += EncodeSize(kFullKey, user_key_size, size_bytes);
|
||||
Status s = file->Append(Slice(size_bytes, size_bytes_pos));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*offset += size_bytes_pos;
|
||||
} else {
|
||||
key_count_for_prefix++;
|
||||
if (key_count_for_prefix == 2) {
|
||||
// For second key within a prefix, need to encode prefix length
|
||||
size_bytes_pos +=
|
||||
EncodeSize(kPrefixFromPreviousKey, pre_prefix_.GetKey().size(),
|
||||
size_bytes + size_bytes_pos);
|
||||
}
|
||||
size_t prefix_len = pre_prefix_.GetKey().size();
|
||||
size_bytes_pos += EncodeSize(kKeySuffix, user_key_size - prefix_len,
|
||||
size_bytes + size_bytes_pos);
|
||||
Status s = file->Append(Slice(size_bytes, size_bytes_pos));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*offset += size_bytes_pos;
|
||||
key_to_write = Slice(key.data() + prefix_len, key.size() - prefix_len);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode full key
|
||||
// For value size as varint32 (up to 5 bytes).
|
||||
// If the row is of value type with seqId 0, flush the special flag together
|
||||
// in this buffer to safe one file append call, which takes 1 byte.
|
||||
if (parsed_key.sequence == 0 && parsed_key.type == kTypeValue) {
|
||||
Status s =
|
||||
file->Append(Slice(key_to_write.data(), key_to_write.size() - 8));
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
*offset += key_to_write.size() - 8;
|
||||
meta_bytes_buf[*meta_bytes_buf_size] = PlainTableFactory::kValueTypeSeqId0;
|
||||
*meta_bytes_buf_size += 1;
|
||||
} else {
|
||||
file->Append(key_to_write);
|
||||
*offset += key_to_write.size();
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
namespace {
|
||||
Status ReadInternalKey(const char* key_ptr, const char* limit,
|
||||
uint32_t user_key_size, ParsedInternalKey* parsed_key,
|
||||
size_t* bytes_read, bool* internal_key_valid,
|
||||
Slice* internal_key) {
|
||||
if (key_ptr + user_key_size + 1 >= limit) {
|
||||
return Status::Corruption("Unexpected EOF when reading the next key");
|
||||
}
|
||||
if (*(key_ptr + user_key_size) == PlainTableFactory::kValueTypeSeqId0) {
|
||||
// Special encoding for the row with seqID=0
|
||||
parsed_key->user_key = Slice(key_ptr, user_key_size);
|
||||
parsed_key->sequence = 0;
|
||||
parsed_key->type = kTypeValue;
|
||||
*bytes_read += user_key_size + 1;
|
||||
*internal_key_valid = false;
|
||||
} else {
|
||||
if (key_ptr + user_key_size + 8 >= limit) {
|
||||
return Status::Corruption(
|
||||
"Unexpected EOF when reading internal bytes of the next key");
|
||||
}
|
||||
*internal_key_valid = true;
|
||||
*internal_key = Slice(key_ptr, user_key_size + 8);
|
||||
if (!ParseInternalKey(*internal_key, parsed_key)) {
|
||||
return Status::Corruption(
|
||||
Slice("Incorrect value type found when reading the next key"));
|
||||
}
|
||||
*bytes_read += user_key_size + 8;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status PlainTableKeyDecoder::NextPlainEncodingKey(
|
||||
const char* start, const char* limit, ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, size_t* bytes_read, bool* seekable) {
|
||||
const char* key_ptr = start;
|
||||
size_t user_key_size = 0;
|
||||
if (fixed_user_key_len_ != kPlainTableVariableLength) {
|
||||
user_key_size = fixed_user_key_len_;
|
||||
key_ptr = start;
|
||||
} else {
|
||||
uint32_t tmp_size = 0;
|
||||
key_ptr = GetVarint32Ptr(start, limit, &tmp_size);
|
||||
if (key_ptr == nullptr) {
|
||||
return Status::Corruption(
|
||||
"Unexpected EOF when reading the next key's size");
|
||||
}
|
||||
user_key_size = static_cast<size_t>(tmp_size);
|
||||
*bytes_read = key_ptr - start;
|
||||
}
|
||||
bool decoded_internal_key_valid;
|
||||
Slice decoded_internal_key;
|
||||
Status s =
|
||||
ReadInternalKey(key_ptr, limit, user_key_size, parsed_key, bytes_read,
|
||||
&decoded_internal_key_valid, &decoded_internal_key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (internal_key != nullptr) {
|
||||
if (decoded_internal_key_valid) {
|
||||
*internal_key = decoded_internal_key;
|
||||
} else {
|
||||
// Need to copy out the internal key
|
||||
cur_key_.SetInternalKey(*parsed_key);
|
||||
*internal_key = cur_key_.GetKey();
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PlainTableKeyDecoder::NextPrefixEncodingKey(
|
||||
const char* start, const char* limit, ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, size_t* bytes_read, bool* seekable) {
|
||||
const char* key_ptr = start;
|
||||
EntryType entry_type;
|
||||
|
||||
bool expect_suffix = false;
|
||||
do {
|
||||
size_t size = 0;
|
||||
bool decoded_internal_key_valid;
|
||||
const char* pos = DecodeSize(key_ptr, limit, &entry_type, &size);
|
||||
if (pos == nullptr) {
|
||||
return Status::Corruption("Unexpected EOF when reading size of the key");
|
||||
}
|
||||
*bytes_read += pos - key_ptr;
|
||||
key_ptr = pos;
|
||||
|
||||
switch (entry_type) {
|
||||
case kFullKey: {
|
||||
expect_suffix = false;
|
||||
Slice decoded_internal_key;
|
||||
Status s =
|
||||
ReadInternalKey(key_ptr, limit, size, parsed_key, bytes_read,
|
||||
&decoded_internal_key_valid, &decoded_internal_key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
saved_user_key_ = parsed_key->user_key;
|
||||
if (internal_key != nullptr) {
|
||||
if (decoded_internal_key_valid) {
|
||||
*internal_key = decoded_internal_key;
|
||||
} else {
|
||||
cur_key_.SetInternalKey(*parsed_key);
|
||||
*internal_key = cur_key_.GetKey();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kPrefixFromPreviousKey: {
|
||||
if (seekable != nullptr) {
|
||||
*seekable = false;
|
||||
}
|
||||
prefix_len_ = size;
|
||||
assert(prefix_extractor_ == nullptr ||
|
||||
prefix_extractor_->Transform(saved_user_key_).size() ==
|
||||
prefix_len_);
|
||||
// Need read another size flag for suffix
|
||||
expect_suffix = true;
|
||||
break;
|
||||
}
|
||||
case kKeySuffix: {
|
||||
expect_suffix = false;
|
||||
if (seekable != nullptr) {
|
||||
*seekable = false;
|
||||
}
|
||||
assert(prefix_len_ >= 0);
|
||||
cur_key_.Reserve(prefix_len_ + size);
|
||||
|
||||
Slice tmp_slice;
|
||||
Status s = ReadInternalKey(key_ptr, limit, size, parsed_key, bytes_read,
|
||||
&decoded_internal_key_valid, &tmp_slice);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
cur_key_.SetInternalKey(Slice(saved_user_key_.data(), prefix_len_),
|
||||
*parsed_key);
|
||||
assert(
|
||||
prefix_extractor_ == nullptr ||
|
||||
prefix_extractor_->Transform(ExtractUserKey(cur_key_.GetKey())) ==
|
||||
Slice(saved_user_key_.data(), prefix_len_));
|
||||
parsed_key->user_key = ExtractUserKey(cur_key_.GetKey());
|
||||
if (internal_key != nullptr) {
|
||||
*internal_key = cur_key_.GetKey();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return Status::Corruption("Identified size flag.");
|
||||
}
|
||||
} while (expect_suffix); // Another round if suffix is expected.
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PlainTableKeyDecoder::NextKey(const char* start, const char* limit,
|
||||
ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, size_t* bytes_read,
|
||||
bool* seekable) {
|
||||
*bytes_read = 0;
|
||||
if (seekable != nullptr) {
|
||||
*seekable = true;
|
||||
}
|
||||
if (encoding_type_ == kPlain) {
|
||||
return NextPlainEncodingKey(start, limit, parsed_key, internal_key,
|
||||
bytes_read, seekable);
|
||||
} else {
|
||||
assert(encoding_type_ == kPrefix);
|
||||
return NextPrefixEncodingKey(start, limit, parsed_key, internal_key,
|
||||
bytes_read, seekable);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#pragma once
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class WritableFile;
|
||||
struct ParsedInternalKey;
|
||||
|
||||
// Helper class to write out a key to an output file
|
||||
// Actual data format of the key is documented in plain_table_factory.h
|
||||
class PlainTableKeyEncoder {
|
||||
public:
|
||||
explicit PlainTableKeyEncoder(EncodingType encoding_type,
|
||||
uint32_t user_key_len,
|
||||
const SliceTransform* prefix_extractor,
|
||||
size_t index_sparseness)
|
||||
: encoding_type_((prefix_extractor != nullptr) ? encoding_type : kPlain),
|
||||
fixed_user_key_len_(user_key_len),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
index_sparseness_((index_sparseness > 1) ? index_sparseness : 1),
|
||||
key_count_for_prefix(0) {}
|
||||
// key: the key to write out, in the format of internal key.
|
||||
// file: the output file to write out
|
||||
// offset: offset in the file. Needs to be updated after appending bytes
|
||||
// for the key
|
||||
// meta_bytes_buf: buffer for extra meta bytes
|
||||
// meta_bytes_buf_size: offset to append extra meta bytes. Will be updated
|
||||
// if meta_bytes_buf is updated.
|
||||
Status AppendKey(const Slice& key, WritableFile* file, uint64_t* offset,
|
||||
char* meta_bytes_buf, size_t* meta_bytes_buf_size);
|
||||
|
||||
// Return actual encoding type to be picked
|
||||
EncodingType GetEncodingType() { return encoding_type_; }
|
||||
|
||||
private:
|
||||
EncodingType encoding_type_;
|
||||
uint32_t fixed_user_key_len_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
const size_t index_sparseness_;
|
||||
size_t key_count_for_prefix;
|
||||
IterKey pre_prefix_;
|
||||
};
|
||||
|
||||
// A helper class to decode keys from input buffer
|
||||
// Actual data format of the key is documented in plain_table_factory.h
|
||||
class PlainTableKeyDecoder {
|
||||
public:
|
||||
explicit PlainTableKeyDecoder(EncodingType encoding_type,
|
||||
uint32_t user_key_len,
|
||||
const SliceTransform* prefix_extractor)
|
||||
: encoding_type_(encoding_type),
|
||||
prefix_len_(0),
|
||||
fixed_user_key_len_(user_key_len),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
in_prefix_(false) {}
|
||||
// Find the next key.
|
||||
// start: char array where the key starts.
|
||||
// limit: boundary of the char array
|
||||
// parsed_key: the output of the result key
|
||||
// internal_key: if not null, fill with the output of the result key in
|
||||
// un-parsed format
|
||||
// bytes_read: how many bytes read from start. Output
|
||||
// seekable: whether key can be read from this place. Used when building
|
||||
// indexes. Output.
|
||||
Status NextKey(const char* start, const char* limit,
|
||||
ParsedInternalKey* parsed_key, Slice* internal_key,
|
||||
size_t* bytes_read, bool* seekable = nullptr);
|
||||
EncodingType encoding_type_;
|
||||
uint32_t prefix_len_;
|
||||
uint32_t fixed_user_key_len_;
|
||||
Slice saved_user_key_;
|
||||
IterKey cur_key_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
bool in_prefix_;
|
||||
|
||||
private:
|
||||
Status NextPlainEncodingKey(const char* start, const char* limit,
|
||||
ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, size_t* bytes_read,
|
||||
bool* seekable = nullptr);
|
||||
Status NextPrefixEncodingKey(const char* start, const char* limit,
|
||||
ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, size_t* bytes_read,
|
||||
bool* seekable = nullptr);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+191
-126
@@ -23,6 +23,7 @@
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "table/plain_table_key_coding.h"
|
||||
|
||||
#include "util/arena.h"
|
||||
#include "util/coding.h"
|
||||
@@ -43,6 +44,7 @@ inline uint32_t GetSliceHash(const Slice& s) {
|
||||
}
|
||||
|
||||
inline uint32_t GetBucketIdFromHash(uint32_t hash, uint32_t num_buckets) {
|
||||
assert(num_buckets >= 0);
|
||||
return hash % num_buckets;
|
||||
}
|
||||
|
||||
@@ -51,7 +53,6 @@ inline uint32_t GetBucketIdFromHash(uint32_t hash, uint32_t num_buckets) {
|
||||
inline uint32_t GetFixed32Element(const char* base, size_t offset) {
|
||||
return DecodeFixed32(base + offset * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Iterator to iterate IndexedTable
|
||||
@@ -80,10 +81,11 @@ class PlainTableIterator : public Iterator {
|
||||
|
||||
private:
|
||||
PlainTableReader* table_;
|
||||
PlainTableKeyDecoder decoder_;
|
||||
bool use_prefix_seek_;
|
||||
uint32_t offset_;
|
||||
uint32_t next_offset_;
|
||||
IterKey key_;
|
||||
Slice key_;
|
||||
Slice value_;
|
||||
Status status_;
|
||||
// No copying allowed
|
||||
@@ -92,26 +94,24 @@ class PlainTableIterator : public Iterator {
|
||||
};
|
||||
|
||||
extern const uint64_t kPlainTableMagicNumber;
|
||||
PlainTableReader::PlainTableReader(
|
||||
const Options& options, unique_ptr<RandomAccessFile>&& file,
|
||||
const EnvOptions& storage_options, const InternalKeyComparator& icomparator,
|
||||
uint64_t file_size, int bloom_bits_per_key, double hash_table_ratio,
|
||||
size_t index_sparseness, const TableProperties* table_properties,
|
||||
size_t huge_page_tlb_size)
|
||||
: options_(options),
|
||||
soptions_(storage_options),
|
||||
file_(std::move(file)),
|
||||
internal_comparator_(icomparator),
|
||||
file_size_(file_size),
|
||||
kHashTableRatio(hash_table_ratio),
|
||||
kBloomBitsPerKey(bloom_bits_per_key),
|
||||
kIndexIntervalForSamePrefixKeys(index_sparseness),
|
||||
table_properties_(nullptr),
|
||||
PlainTableReader::PlainTableReader(const Options& options,
|
||||
unique_ptr<RandomAccessFile>&& file,
|
||||
const EnvOptions& storage_options,
|
||||
const InternalKeyComparator& icomparator,
|
||||
EncodingType encoding_type,
|
||||
uint64_t file_size,
|
||||
const TableProperties* table_properties)
|
||||
: internal_comparator_(icomparator),
|
||||
encoding_type_(encoding_type),
|
||||
data_end_offset_(table_properties->data_size),
|
||||
user_key_len_(table_properties->fixed_key_len),
|
||||
huge_page_tlb_size_(huge_page_tlb_size) {
|
||||
assert(kHashTableRatio >= 0.0);
|
||||
}
|
||||
prefix_extractor_(options.prefix_extractor.get()),
|
||||
enable_bloom_(false),
|
||||
bloom_(6, nullptr),
|
||||
options_(options),
|
||||
file_(std::move(file)),
|
||||
file_size_(file_size),
|
||||
table_properties_(nullptr) {}
|
||||
|
||||
PlainTableReader::~PlainTableReader() {
|
||||
}
|
||||
@@ -124,7 +124,7 @@ Status PlainTableReader::Open(const Options& options,
|
||||
unique_ptr<TableReader>* table_reader,
|
||||
const int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness,
|
||||
size_t huge_page_tlb_size) {
|
||||
size_t huge_page_tlb_size, bool full_scan_mode) {
|
||||
assert(options.allow_mmap_reads);
|
||||
|
||||
if (file_size > kMaxFileSize) {
|
||||
@@ -138,17 +138,54 @@ Status PlainTableReader::Open(const Options& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<PlainTableReader> new_reader(new PlainTableReader(
|
||||
options, std::move(file), soptions, internal_comparator, file_size,
|
||||
bloom_bits_per_key, hash_table_ratio, index_sparseness, props,
|
||||
huge_page_tlb_size));
|
||||
assert(hash_table_ratio >= 0.0);
|
||||
auto& user_props = props->user_collected_properties;
|
||||
auto prefix_extractor_in_file =
|
||||
user_props.find(PlainTablePropertyNames::kPrefixExtractorName);
|
||||
|
||||
// -- Populate Index
|
||||
s = new_reader->PopulateIndex(props);
|
||||
if (!full_scan_mode && prefix_extractor_in_file != user_props.end()) {
|
||||
if (!options.prefix_extractor) {
|
||||
return Status::InvalidArgument(
|
||||
"Prefix extractor is missing when opening a PlainTable built "
|
||||
"using a prefix extractor");
|
||||
} else if (prefix_extractor_in_file->second.compare(
|
||||
options.prefix_extractor->Name()) != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Prefix extractor given doesn't match the one used to build "
|
||||
"PlainTable");
|
||||
}
|
||||
}
|
||||
|
||||
EncodingType encoding_type = kPlain;
|
||||
auto encoding_type_prop =
|
||||
user_props.find(PlainTablePropertyNames::kEncodingType);
|
||||
if (encoding_type_prop != user_props.end()) {
|
||||
encoding_type = static_cast<EncodingType>(
|
||||
DecodeFixed32(encoding_type_prop->second.c_str()));
|
||||
}
|
||||
|
||||
std::unique_ptr<PlainTableReader> new_reader(new PlainTableReader(
|
||||
options, std::move(file), soptions, internal_comparator, encoding_type,
|
||||
file_size, props));
|
||||
|
||||
s = new_reader->MmapDataFile();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// -- Populate Index
|
||||
if (!full_scan_mode) {
|
||||
s = new_reader->PopulateIndex(props, bloom_bits_per_key, hash_table_ratio,
|
||||
index_sparseness, huge_page_tlb_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
// Flag to indicate it is a full scan mode so that none of the indexes
|
||||
// can be used.
|
||||
new_reader->index_size_ = kFullScanModeFlag;
|
||||
}
|
||||
|
||||
*table_reader = std::move(new_reader);
|
||||
return s;
|
||||
}
|
||||
@@ -156,8 +193,14 @@ Status PlainTableReader::Open(const Options& options,
|
||||
void PlainTableReader::SetupForCompaction() {
|
||||
}
|
||||
|
||||
Iterator* PlainTableReader::NewIterator(const ReadOptions& options) {
|
||||
return new PlainTableIterator(this, options_.prefix_extractor != nullptr);
|
||||
Iterator* PlainTableReader::NewIterator(const ReadOptions& options,
|
||||
Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return new PlainTableIterator(this, prefix_extractor_ != nullptr);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(PlainTableIterator));
|
||||
return new (mem) PlainTableIterator(this, prefix_extractor_ != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
struct PlainTableReader::IndexRecord {
|
||||
@@ -217,7 +260,9 @@ class PlainTableReader::IndexRecordList {
|
||||
};
|
||||
|
||||
Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
int* num_prefixes) const {
|
||||
int* num_prefixes,
|
||||
int bloom_bits_per_key,
|
||||
size_t index_sparseness) {
|
||||
Slice prev_key_prefix_slice;
|
||||
uint32_t prev_key_prefix_hash = 0;
|
||||
uint32_t pos = data_start_offset_;
|
||||
@@ -228,17 +273,21 @@ Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
// are in order.
|
||||
|
||||
*num_prefixes = 0;
|
||||
PlainTableKeyDecoder decoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get());
|
||||
bool due_index = false;
|
||||
while (pos < data_end_offset_) {
|
||||
uint32_t key_offset = pos;
|
||||
ParsedInternalKey key;
|
||||
Slice value_slice;
|
||||
Status s = Next(&pos, &key, &value_slice);
|
||||
bool seekable = false;
|
||||
Status s = Next(&decoder, &pos, &key, nullptr, &value_slice, &seekable);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (bloom_) {
|
||||
if (enable_bloom_) {
|
||||
// total order mode and bloom filter is enabled.
|
||||
bloom_->AddHash(GetSliceHash(key.user_key));
|
||||
bloom_.AddHash(GetSliceHash(key.user_key));
|
||||
}
|
||||
Slice key_prefix_slice = GetPrefix(key);
|
||||
|
||||
@@ -250,12 +299,21 @@ Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
num_keys_per_prefix = 0;
|
||||
prev_key_prefix_slice = key_prefix_slice;
|
||||
prev_key_prefix_hash = GetSliceHash(key_prefix_slice);
|
||||
due_index = true;
|
||||
}
|
||||
|
||||
if (kIndexIntervalForSamePrefixKeys == 0 ||
|
||||
num_keys_per_prefix++ % kIndexIntervalForSamePrefixKeys == 0) {
|
||||
if (due_index) {
|
||||
if (!seekable) {
|
||||
return Status::Corruption("Key for a prefix is not seekable");
|
||||
}
|
||||
// Add an index key for every kIndexIntervalForSamePrefixKeys keys
|
||||
record_list->AddRecord(prev_key_prefix_hash, key_offset);
|
||||
due_index = false;
|
||||
}
|
||||
|
||||
num_keys_per_prefix++;
|
||||
if (index_sparseness == 0 || num_keys_per_prefix % index_sparseness == 0) {
|
||||
due_index = true;
|
||||
}
|
||||
is_first_record = false;
|
||||
}
|
||||
@@ -267,22 +325,25 @@ Status PlainTableReader::PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void PlainTableReader::AllocateIndexAndBloom(int num_prefixes) {
|
||||
if (options_.prefix_extractor.get() != nullptr) {
|
||||
uint32_t bloom_total_bits = num_prefixes * kBloomBitsPerKey;
|
||||
void PlainTableReader::AllocateIndexAndBloom(int num_prefixes,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t huge_page_tlb_size) {
|
||||
if (prefix_extractor_ != nullptr) {
|
||||
uint32_t bloom_total_bits = num_prefixes * bloom_bits_per_key;
|
||||
if (bloom_total_bits > 0) {
|
||||
bloom_.reset(new DynamicBloom(bloom_total_bits, options_.bloom_locality,
|
||||
6, nullptr, huge_page_tlb_size_,
|
||||
options_.info_log.get()));
|
||||
enable_bloom_ = true;
|
||||
bloom_.SetTotalBits(bloom_total_bits, options_.bloom_locality,
|
||||
huge_page_tlb_size, options_.info_log.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (options_.prefix_extractor.get() == nullptr || kHashTableRatio <= 0) {
|
||||
if (prefix_extractor_ == nullptr || hash_table_ratio <= 0) {
|
||||
// Fall back to pure binary search if the user fails to specify a prefix
|
||||
// extractor.
|
||||
index_size_ = 1;
|
||||
} else {
|
||||
double hash_table_size_multipier = 1.0 / kHashTableRatio;
|
||||
double hash_table_size_multipier = 1.0 / hash_table_ratio;
|
||||
index_size_ = num_prefixes * hash_table_size_multipier + 1;
|
||||
}
|
||||
}
|
||||
@@ -299,8 +360,8 @@ size_t PlainTableReader::BucketizeIndexesAndFillBloom(
|
||||
if (first || prev_hash != cur_hash) {
|
||||
prev_hash = cur_hash;
|
||||
first = false;
|
||||
if (bloom_ && !IsTotalOrderMode()) {
|
||||
bloom_->AddHash(cur_hash);
|
||||
if (enable_bloom_ && !IsTotalOrderMode()) {
|
||||
bloom_.AddHash(cur_hash);
|
||||
}
|
||||
}
|
||||
uint32_t bucket = GetBucketIdFromHash(cur_hash, index_size_);
|
||||
@@ -325,12 +386,13 @@ size_t PlainTableReader::BucketizeIndexesAndFillBloom(
|
||||
void PlainTableReader::FillIndexes(
|
||||
const size_t kSubIndexSize,
|
||||
const std::vector<IndexRecord*>& hash_to_offsets,
|
||||
const std::vector<uint32_t>& entries_per_bucket) {
|
||||
const std::vector<uint32_t>& entries_per_bucket,
|
||||
size_t huge_page_tlb_size) {
|
||||
Log(options_.info_log, "Reserving %zu bytes for plain table's sub_index",
|
||||
kSubIndexSize);
|
||||
auto total_allocate_size = sizeof(uint32_t) * index_size_ + kSubIndexSize;
|
||||
char* allocated = arena_.AllocateAligned(
|
||||
total_allocate_size, huge_page_tlb_size_, options_.info_log.get());
|
||||
total_allocate_size, huge_page_tlb_size, options_.info_log.get());
|
||||
index_ = reinterpret_cast<uint32_t*>(allocated);
|
||||
sub_index_ = allocated + sizeof(uint32_t) * index_size_;
|
||||
|
||||
@@ -371,22 +433,25 @@ void PlainTableReader::FillIndexes(
|
||||
index_size_, kSubIndexSize);
|
||||
}
|
||||
|
||||
Status PlainTableReader::PopulateIndex(TableProperties* props) {
|
||||
Status PlainTableReader::MmapDataFile() {
|
||||
// Get mmapped memory to file_data_.
|
||||
return file_->Read(0, file_size_, &file_data_, nullptr);
|
||||
}
|
||||
|
||||
Status PlainTableReader::PopulateIndex(TableProperties* props,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness,
|
||||
size_t huge_page_tlb_size) {
|
||||
assert(props != nullptr);
|
||||
table_properties_.reset(props);
|
||||
|
||||
// options.prefix_extractor is requried for a hash-based look-up.
|
||||
if (options_.prefix_extractor.get() == nullptr && kHashTableRatio != 0) {
|
||||
if (options_.prefix_extractor.get() == nullptr && hash_table_ratio != 0) {
|
||||
return Status::NotSupported(
|
||||
"PlainTable requires a prefix extractor enable prefix hash mode.");
|
||||
}
|
||||
|
||||
// Get mmapped memory to file_data_.
|
||||
Status s = file_->Read(0, file_size_, &file_data_, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
IndexRecordList record_list(kRecordsPerGroup);
|
||||
// First, read the whole file, for every kIndexIntervalForSamePrefixKeys rows
|
||||
// for a prefix (starting from the first one), generate a record of (hash,
|
||||
@@ -396,21 +461,24 @@ Status PlainTableReader::PopulateIndex(TableProperties* props) {
|
||||
|
||||
// Allocate bloom filter here for total order mode.
|
||||
if (IsTotalOrderMode()) {
|
||||
uint32_t num_bloom_bits = table_properties_->num_entries * kBloomBitsPerKey;
|
||||
uint32_t num_bloom_bits =
|
||||
table_properties_->num_entries * bloom_bits_per_key;
|
||||
if (num_bloom_bits > 0) {
|
||||
bloom_.reset(new DynamicBloom(num_bloom_bits, options_.bloom_locality, 6,
|
||||
nullptr, huge_page_tlb_size_,
|
||||
options_.info_log.get()));
|
||||
enable_bloom_ = true;
|
||||
bloom_.SetTotalBits(num_bloom_bits, options_.bloom_locality,
|
||||
huge_page_tlb_size, options_.info_log.get());
|
||||
}
|
||||
}
|
||||
|
||||
s = PopulateIndexRecordList(&record_list, &num_prefixes);
|
||||
Status s = PopulateIndexRecordList(&record_list, &num_prefixes,
|
||||
bloom_bits_per_key, index_sparseness);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// Calculated hash table and bloom filter size and allocate memory for indexes
|
||||
// and bloom filter based on the number of prefixes.
|
||||
AllocateIndexAndBloom(num_prefixes);
|
||||
AllocateIndexAndBloom(num_prefixes, bloom_bits_per_key, hash_table_ratio,
|
||||
huge_page_tlb_size);
|
||||
|
||||
// Bucketize all the index records to a temp data structure, in which for
|
||||
// each bucket, we generate a linked list of IndexRecord, in reversed order.
|
||||
@@ -419,7 +487,8 @@ Status PlainTableReader::PopulateIndex(TableProperties* props) {
|
||||
size_t sub_index_size_needed = BucketizeIndexesAndFillBloom(
|
||||
&record_list, &hash_to_offsets, &entries_per_bucket);
|
||||
// From the temp data structure, populate indexes.
|
||||
FillIndexes(sub_index_size_needed, hash_to_offsets, entries_per_bucket);
|
||||
FillIndexes(sub_index_size_needed, hash_to_offsets, entries_per_bucket,
|
||||
huge_page_tlb_size);
|
||||
|
||||
// Fill two table properties.
|
||||
// TODO(sdong): after we have the feature of storing index in file, this
|
||||
@@ -466,7 +535,11 @@ Status PlainTableReader::GetOffset(const Slice& target, const Slice& prefix,
|
||||
uint32_t mid = (high + low) / 2;
|
||||
uint32_t file_offset = GetFixed32Element(base_ptr, mid);
|
||||
size_t tmp;
|
||||
Status s = ReadKey(file_data_.data() + file_offset, &mid_key, &tmp);
|
||||
Status s = PlainTableKeyDecoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get())
|
||||
.NextKey(file_data_.data() + file_offset,
|
||||
file_data_.data() + data_end_offset_, &mid_key,
|
||||
nullptr, &tmp);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -491,7 +564,15 @@ Status PlainTableReader::GetOffset(const Slice& target, const Slice& prefix,
|
||||
ParsedInternalKey low_key;
|
||||
size_t tmp;
|
||||
uint32_t low_key_offset = GetFixed32Element(base_ptr, low);
|
||||
Status s = ReadKey(file_data_.data() + low_key_offset, &low_key, &tmp);
|
||||
Status s = PlainTableKeyDecoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get())
|
||||
.NextKey(file_data_.data() + low_key_offset,
|
||||
file_data_.data() + data_end_offset_, &low_key,
|
||||
nullptr, &tmp);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (GetPrefix(low_key) == prefix) {
|
||||
prefix_matched = true;
|
||||
*offset = low_key_offset;
|
||||
@@ -508,59 +589,17 @@ Status PlainTableReader::GetOffset(const Slice& target, const Slice& prefix,
|
||||
}
|
||||
|
||||
bool PlainTableReader::MatchBloom(uint32_t hash) const {
|
||||
return bloom_.get() == nullptr || bloom_->MayContainHash(hash);
|
||||
return !enable_bloom_ || bloom_.MayContainHash(hash);
|
||||
}
|
||||
|
||||
Slice PlainTableReader::GetPrefix(const ParsedInternalKey& target) const {
|
||||
return GetPrefixFromUserKey(target.user_key);
|
||||
}
|
||||
|
||||
Status PlainTableReader::ReadKey(const char* start, ParsedInternalKey* key,
|
||||
size_t* bytes_read) const {
|
||||
const char* key_ptr = nullptr;
|
||||
*bytes_read = 0;
|
||||
size_t user_key_size = 0;
|
||||
if (IsFixedLength()) {
|
||||
user_key_size = user_key_len_;
|
||||
key_ptr = start;
|
||||
} else {
|
||||
uint32_t tmp_size = 0;
|
||||
key_ptr =
|
||||
GetVarint32Ptr(start, file_data_.data() + data_end_offset_, &tmp_size);
|
||||
if (key_ptr == nullptr) {
|
||||
return Status::Corruption(
|
||||
"Unexpected EOF when reading the next key's size");
|
||||
}
|
||||
user_key_size = (size_t)tmp_size;
|
||||
*bytes_read = key_ptr - start;
|
||||
}
|
||||
if (key_ptr + user_key_size + 1 >= file_data_.data() + data_end_offset_) {
|
||||
return Status::Corruption("Unexpected EOF when reading the next key");
|
||||
}
|
||||
|
||||
if (*(key_ptr + user_key_size) == PlainTableFactory::kValueTypeSeqId0) {
|
||||
// Special encoding for the row with seqID=0
|
||||
key->user_key = Slice(key_ptr, user_key_size);
|
||||
key->sequence = 0;
|
||||
key->type = kTypeValue;
|
||||
*bytes_read += user_key_size + 1;
|
||||
} else {
|
||||
if (start + user_key_size + 8 >= file_data_.data() + data_end_offset_) {
|
||||
return Status::Corruption(
|
||||
"Unexpected EOF when reading internal bytes of the next key");
|
||||
}
|
||||
if (!ParseInternalKey(Slice(key_ptr, user_key_size + 8), key)) {
|
||||
return Status::Corruption(
|
||||
Slice("Incorrect value type found when reading the next key"));
|
||||
}
|
||||
*bytes_read += user_key_size + 8;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status PlainTableReader::Next(uint32_t* offset, ParsedInternalKey* key,
|
||||
Slice* value) const {
|
||||
Status PlainTableReader::Next(PlainTableKeyDecoder* decoder, uint32_t* offset,
|
||||
ParsedInternalKey* parsed_key,
|
||||
Slice* internal_key, Slice* value,
|
||||
bool* seekable) const {
|
||||
if (*offset == data_end_offset_) {
|
||||
*offset = data_end_offset_;
|
||||
return Status::OK();
|
||||
@@ -572,7 +611,9 @@ Status PlainTableReader::Next(uint32_t* offset, ParsedInternalKey* key,
|
||||
|
||||
const char* start = file_data_.data() + *offset;
|
||||
size_t bytes_for_key;
|
||||
Status s = ReadKey(start, key, &bytes_for_key);
|
||||
Status s =
|
||||
decoder->NextKey(start, file_data_.data() + data_end_offset_, parsed_key,
|
||||
internal_key, &bytes_for_key, seekable);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -592,6 +633,13 @@ Status PlainTableReader::Next(uint32_t* offset, ParsedInternalKey* key,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void PlainTableReader::Prepare(const Slice& target) {
|
||||
if (enable_bloom_) {
|
||||
uint32_t prefix_hash = GetSliceHash(GetPrefix(target));
|
||||
bloom_.Prefetch(prefix_hash);
|
||||
}
|
||||
}
|
||||
|
||||
Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target,
|
||||
void* arg,
|
||||
bool (*saver)(void*, const ParsedInternalKey&,
|
||||
@@ -601,6 +649,11 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target,
|
||||
Slice prefix_slice;
|
||||
uint32_t prefix_hash;
|
||||
if (IsTotalOrderMode()) {
|
||||
if (index_size_ == kFullScanModeFlag) {
|
||||
// Full Scan Mode
|
||||
status_ =
|
||||
Status::InvalidArgument("Get() is not allowed in full scan mode.");
|
||||
}
|
||||
// Match whole user key for bloom filter check.
|
||||
if (!MatchBloom(GetSliceHash(GetUserKey(target)))) {
|
||||
return Status::OK();
|
||||
@@ -630,8 +683,10 @@ Status PlainTableReader::Get(const ReadOptions& ro, const Slice& target,
|
||||
}
|
||||
|
||||
Slice found_value;
|
||||
PlainTableKeyDecoder decoder(encoding_type_, user_key_len_,
|
||||
options_.prefix_extractor.get());
|
||||
while (offset < data_end_offset_) {
|
||||
Status s = Next(&offset, &found_key, &found_value);
|
||||
Status s = Next(&decoder, &offset, &found_key, nullptr, &found_value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -658,7 +713,10 @@ uint64_t PlainTableReader::ApproximateOffsetOf(const Slice& key) {
|
||||
|
||||
PlainTableIterator::PlainTableIterator(PlainTableReader* table,
|
||||
bool use_prefix_seek)
|
||||
: table_(table), use_prefix_seek_(use_prefix_seek) {
|
||||
: table_(table),
|
||||
decoder_(table_->encoding_type_, table_->user_key_len_,
|
||||
table_->prefix_extractor_),
|
||||
use_prefix_seek_(use_prefix_seek) {
|
||||
next_offset_ = offset_ = table_->data_end_offset_;
|
||||
}
|
||||
|
||||
@@ -687,12 +745,21 @@ void PlainTableIterator::SeekToLast() {
|
||||
void PlainTableIterator::Seek(const Slice& target) {
|
||||
// If the user doesn't set prefix seek option and we are not able to do a
|
||||
// total Seek(). assert failure.
|
||||
if (!use_prefix_seek_ && table_->index_size_ > 1) {
|
||||
assert(false);
|
||||
status_ = Status::NotSupported(
|
||||
"PlainTable cannot issue non-prefix seek unless in total order mode.");
|
||||
offset_ = next_offset_ = table_->data_end_offset_;
|
||||
return;
|
||||
if (!use_prefix_seek_) {
|
||||
if (table_->index_size_ == PlainTableReader::kFullScanModeFlag) {
|
||||
// Full Scan Mode.
|
||||
status_ =
|
||||
Status::InvalidArgument("Seek() is not allowed in full scan mode.");
|
||||
offset_ = next_offset_ = table_->data_end_offset_;
|
||||
return;
|
||||
} else if (table_->index_size_ > 1) {
|
||||
assert(false);
|
||||
status_ = Status::NotSupported(
|
||||
"PlainTable cannot issue non-prefix seek unless in total order "
|
||||
"mode.");
|
||||
offset_ = next_offset_ = table_->data_end_offset_;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Slice prefix_slice = table_->GetPrefix(target);
|
||||
@@ -737,11 +804,9 @@ void PlainTableIterator::Next() {
|
||||
if (offset_ < table_->data_end_offset_) {
|
||||
Slice tmp_slice;
|
||||
ParsedInternalKey parsed_key;
|
||||
status_ = table_->Next(&next_offset_, &parsed_key, &value_);
|
||||
if (status_.ok()) {
|
||||
// Make a copy in this case. TODO optimize.
|
||||
key_.SetInternalKey(parsed_key);
|
||||
} else {
|
||||
status_ =
|
||||
table_->Next(&decoder_, &next_offset_, &parsed_key, &key_, &value_);
|
||||
if (!status_.ok()) {
|
||||
offset_ = next_offset_ = table_->data_end_offset_;
|
||||
}
|
||||
}
|
||||
@@ -753,7 +818,7 @@ void PlainTableIterator::Prev() {
|
||||
|
||||
Slice PlainTableIterator::key() const {
|
||||
assert(Valid());
|
||||
return key_.GetKey();
|
||||
return key_;
|
||||
}
|
||||
|
||||
Slice PlainTableIterator::value() const {
|
||||
|
||||
+45
-41
@@ -20,6 +20,7 @@
|
||||
#include "table/table_reader.h"
|
||||
#include "table/plain_table_factory.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/dynamic_bloom.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -31,8 +32,8 @@ class RandomAccessFile;
|
||||
struct ReadOptions;
|
||||
class TableCache;
|
||||
class TableReader;
|
||||
class DynamicBloom;
|
||||
class InternalKeyComparator;
|
||||
class PlainTableKeyDecoder;
|
||||
|
||||
using std::unique_ptr;
|
||||
using std::unordered_map;
|
||||
@@ -53,9 +54,12 @@ class PlainTableReader: public TableReader {
|
||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size,
|
||||
unique_ptr<TableReader>* table,
|
||||
const int bloom_bits_per_key, double hash_table_ratio,
|
||||
size_t index_sparseness, size_t huge_page_tlb_size);
|
||||
size_t index_sparseness, size_t huge_page_tlb_size,
|
||||
bool full_scan_mode);
|
||||
|
||||
Iterator* NewIterator(const ReadOptions&);
|
||||
Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr) override;
|
||||
|
||||
void Prepare(const Slice& target);
|
||||
|
||||
Status Get(const ReadOptions&, const Slice& key, void* arg,
|
||||
bool (*result_handler)(void* arg, const ParsedInternalKey& k,
|
||||
@@ -73,10 +77,8 @@ class PlainTableReader: public TableReader {
|
||||
PlainTableReader(const Options& options, unique_ptr<RandomAccessFile>&& file,
|
||||
const EnvOptions& storage_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint64_t file_size, int bloom_num_bits,
|
||||
double hash_table_ratio, size_t index_sparseness,
|
||||
const TableProperties* table_properties,
|
||||
size_t huge_page_tlb_size);
|
||||
EncodingType encoding_type, uint64_t file_size,
|
||||
const TableProperties* table_properties);
|
||||
virtual ~PlainTableReader();
|
||||
|
||||
protected:
|
||||
@@ -126,7 +128,10 @@ class PlainTableReader: public TableReader {
|
||||
// ....
|
||||
// record N file offset: fixedint32
|
||||
// <end>
|
||||
Status PopulateIndex(TableProperties* props);
|
||||
Status PopulateIndex(TableProperties* props, int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness,
|
||||
size_t huge_page_tlb_size);
|
||||
Status MmapDataFile();
|
||||
|
||||
private:
|
||||
struct IndexRecord;
|
||||
@@ -141,41 +146,35 @@ class PlainTableReader: public TableReader {
|
||||
uint32_t* index_;
|
||||
int index_size_ = 0;
|
||||
char* sub_index_;
|
||||
|
||||
Options options_;
|
||||
const EnvOptions& soptions_;
|
||||
unique_ptr<RandomAccessFile> file_;
|
||||
|
||||
const InternalKeyComparator internal_comparator_;
|
||||
EncodingType encoding_type_;
|
||||
// represents plain table's current status.
|
||||
Status status_;
|
||||
|
||||
Slice file_data_;
|
||||
uint32_t file_size_;
|
||||
|
||||
const double kHashTableRatio;
|
||||
const int kBloomBitsPerKey;
|
||||
// To speed up the search for keys with same prefix, we'll add index key for
|
||||
// every N keys, where the "N" is determined by
|
||||
// kIndexIntervalForSamePrefixKeys
|
||||
const size_t kIndexIntervalForSamePrefixKeys = 16;
|
||||
// Bloom filter is used to rule out non-existent key
|
||||
unique_ptr<DynamicBloom> bloom_;
|
||||
Arena arena_;
|
||||
|
||||
std::shared_ptr<const TableProperties> table_properties_;
|
||||
// data_start_offset_ and data_end_offset_ defines the range of the
|
||||
// sst file that stores data.
|
||||
const uint32_t data_start_offset_ = 0;
|
||||
const uint32_t data_end_offset_;
|
||||
const size_t user_key_len_;
|
||||
const size_t huge_page_tlb_size_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
|
||||
static const size_t kNumInternalBytes = 8;
|
||||
static const uint32_t kSubIndexMask = 0x80000000;
|
||||
static const size_t kOffsetLen = sizeof(uint32_t);
|
||||
static const uint64_t kMaxFileSize = 1u << 31;
|
||||
static const size_t kRecordsPerGroup = 256;
|
||||
static const int kFullScanModeFlag = -1;
|
||||
|
||||
// Bloom filter is used to rule out non-existent key
|
||||
bool enable_bloom_;
|
||||
DynamicBloom bloom_;
|
||||
Arena arena_;
|
||||
|
||||
const Options& options_;
|
||||
unique_ptr<RandomAccessFile> file_;
|
||||
uint32_t file_size_;
|
||||
std::shared_ptr<const TableProperties> table_properties_;
|
||||
|
||||
bool IsFixedLength() const {
|
||||
return user_key_len_ != kPlainTableVariableLength;
|
||||
@@ -193,10 +192,13 @@ class PlainTableReader: public TableReader {
|
||||
// If bloom_ is not null, all the keys' full-key hash will be added to the
|
||||
// bloom filter.
|
||||
Status PopulateIndexRecordList(IndexRecordList* record_list,
|
||||
int* num_prefixes) const;
|
||||
int* num_prefixes, int bloom_bits_per_key,
|
||||
size_t index_sparseness);
|
||||
|
||||
// Internal helper function to allocate memory for indexes and bloom filters
|
||||
void AllocateIndexAndBloom(int num_prefixes);
|
||||
void AllocateIndexAndBloom(int num_prefixes, int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t huge_page_tlb_size);
|
||||
|
||||
// Internal helper function to bucket index record list to hash buckets.
|
||||
// bucket_header is a vector of size hash_table_size_, with each entry
|
||||
@@ -214,16 +216,20 @@ class PlainTableReader: public TableReader {
|
||||
// indexes and counts generated by BucketizeIndexesAndFillBloom().
|
||||
void FillIndexes(const size_t kSubIndexSize,
|
||||
const std::vector<IndexRecord*>& bucket_headers,
|
||||
const std::vector<uint32_t>& entries_per_bucket);
|
||||
const std::vector<uint32_t>& entries_per_bucket,
|
||||
size_t huge_page_tlb_size);
|
||||
|
||||
// Read a plain table key from the position `start`. The read content
|
||||
// will be written to `key` and the size of read bytes will be populated
|
||||
// in `bytes_read`.
|
||||
Status ReadKey(const char* row_ptr, ParsedInternalKey* key,
|
||||
size_t* bytes_read) const;
|
||||
// Read the key and value at `offset` to parameters `key` and `value`.
|
||||
// Read the key and value at `offset` to parameters for keys, the and
|
||||
// `seekable`.
|
||||
// On success, `offset` will be updated as the offset for the next key.
|
||||
Status Next(uint32_t* offset, ParsedInternalKey* key, Slice* value) const;
|
||||
// `parsed_key` will be key in parsed format.
|
||||
// if `internal_key` is not empty, it will be filled with key with slice
|
||||
// format.
|
||||
// if `seekable` is not null, it will return whether we can directly read
|
||||
// data using this offset.
|
||||
Status Next(PlainTableKeyDecoder* decoder, uint32_t* offset,
|
||||
ParsedInternalKey* parsed_key, Slice* internal_key, Slice* value,
|
||||
bool* seekable = nullptr) const;
|
||||
// Get file offset for key target.
|
||||
// return value prefix_matched is set to true if the offset is confirmed
|
||||
// for a key with the same prefix as target.
|
||||
@@ -244,7 +250,7 @@ class PlainTableReader: public TableReader {
|
||||
|
||||
Slice GetPrefixFromUserKey(const Slice& user_key) const {
|
||||
if (!IsTotalOrderMode()) {
|
||||
return options_.prefix_extractor->Transform(user_key);
|
||||
return prefix_extractor_->Transform(user_key);
|
||||
} else {
|
||||
// Use empty slice as prefix if prefix_extractor is not set. In that case,
|
||||
// it falls back to pure binary search and total iterator seek is
|
||||
@@ -253,9 +259,7 @@ class PlainTableReader: public TableReader {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTotalOrderMode() const {
|
||||
return (options_.prefix_extractor.get() == nullptr);
|
||||
}
|
||||
bool IsTotalOrderMode() const { return (prefix_extractor_ == nullptr); }
|
||||
|
||||
// No copying allowed
|
||||
explicit PlainTableReader(const TableReader&) = delete;
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace rocksdb {
|
||||
class Iterator;
|
||||
struct ParsedInternalKey;
|
||||
class Slice;
|
||||
class Arena;
|
||||
struct ReadOptions;
|
||||
struct TableProperties;
|
||||
|
||||
@@ -28,7 +29,11 @@ class TableReader {
|
||||
// Returns a new iterator over the table contents.
|
||||
// The result of NewIterator() is initially invalid (caller must
|
||||
// call one of the Seek methods on the iterator before using it).
|
||||
virtual Iterator* NewIterator(const ReadOptions&) = 0;
|
||||
// arena: If not null, the arena needs to be used to allocate the Iterator.
|
||||
// When destroying the iterator, the caller will not call "delete"
|
||||
// but Iterator::~Iterator() directly. The destructor needs to destroy
|
||||
// all the states but those allocated in arena.
|
||||
virtual Iterator* NewIterator(const ReadOptions&, Arena* arena = nullptr) = 0;
|
||||
|
||||
// Given a key, return an approximate byte offset in the file where
|
||||
// the data for that key begins (or would begin if the key were
|
||||
@@ -44,6 +49,9 @@ class TableReader {
|
||||
|
||||
virtual std::shared_ptr<const TableProperties> GetTableProperties() const = 0;
|
||||
|
||||
// Prepare work that can be done before the real Get()
|
||||
virtual void Prepare(const Slice& target) {}
|
||||
|
||||
// Calls (*result_handler)(handle_context, ...) repeatedly, starting with
|
||||
// the entry found after a call to Seek(key), until result_handler returns
|
||||
// false, where k is the actual internal key for a row found and v as the
|
||||
|
||||
+9
-1
@@ -11,6 +11,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
@@ -1086,6 +1087,7 @@ TEST(TableTest, HashIndexTest) {
|
||||
Options options;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.index_type = BlockBasedTableOptions::kHashSearch;
|
||||
table_options.hash_index_allow_collision = true;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
@@ -1160,7 +1162,13 @@ TEST(TableTest, HashIndexTest) {
|
||||
// regular_iter->Seek(prefix);
|
||||
|
||||
ASSERT_OK(hash_iter->status());
|
||||
ASSERT_TRUE(!hash_iter->Valid());
|
||||
// Seek to non-existing prefixes should yield either invalid, or a
|
||||
// key with prefix greater than the target.
|
||||
if (hash_iter->Valid()) {
|
||||
Slice ukey = ExtractUserKey(hash_iter->key());
|
||||
Slice ukey_prefix = options.prefix_extractor->Transform(ukey);
|
||||
ASSERT_TRUE(BytewiseComparator()->Compare(prefix, ukey_prefix) < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block.h"
|
||||
#include "table/format.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -23,7 +24,10 @@ class TwoLevelIterator: public Iterator {
|
||||
explicit TwoLevelIterator(TwoLevelIteratorState* state,
|
||||
Iterator* first_level_iter);
|
||||
|
||||
virtual ~TwoLevelIterator() {}
|
||||
virtual ~TwoLevelIterator() {
|
||||
first_level_iter_.DeleteIter(false);
|
||||
second_level_iter_.DeleteIter(false);
|
||||
}
|
||||
|
||||
virtual void Seek(const Slice& target);
|
||||
virtual void SeekToFirst();
|
||||
@@ -183,8 +187,13 @@ void TwoLevelIterator::InitDataBlock() {
|
||||
} // namespace
|
||||
|
||||
Iterator* NewTwoLevelIterator(TwoLevelIteratorState* state,
|
||||
Iterator* first_level_iter) {
|
||||
return new TwoLevelIterator(state, first_level_iter);
|
||||
Iterator* first_level_iter, Arena* arena) {
|
||||
if (arena == nullptr) {
|
||||
return new TwoLevelIterator(state, first_level_iter);
|
||||
} else {
|
||||
auto mem = arena->AllocateAligned(sizeof(TwoLevelIterator));
|
||||
return new (mem) TwoLevelIterator(state, first_level_iter);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user