mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8d5f909f7 | ||
|
|
777c0011bf | ||
|
|
7e1f37eb4f | ||
|
|
f50476a021 | ||
|
|
c97db5282c | ||
|
|
0d4c848325 | ||
|
|
02028f9c19 | ||
|
|
0db501141a | ||
|
|
6c47f8f3d9 | ||
|
|
c00296e9fa | ||
|
|
39340abf23 | ||
|
|
abbd5a4b93 | ||
|
|
4bd3bc5c4f | ||
|
|
c1d8c1e7dc | ||
|
|
5e01c5a312 | ||
|
|
316f30ce02 | ||
|
|
17d42cc84a | ||
|
|
cb2d7fc7bc | ||
|
|
2c7422aa65 | ||
|
|
a0a9829f7d | ||
|
|
086bae0599 | ||
|
|
4842d1ea5f | ||
|
|
e28e2fff13 | ||
|
|
0b6123068a | ||
|
|
ee0cf2f473 | ||
|
|
3b66d4b617 | ||
|
|
421be4af42 | ||
|
|
9810a21baa | ||
|
|
f9fc1f483a | ||
|
|
600ca9a439 | ||
|
|
976212ede4 | ||
|
|
516faa0fa0 | ||
|
|
9897e42813 | ||
|
|
f132197a93 | ||
|
|
87f1325b2b | ||
|
|
b79a965f7c | ||
|
|
ad7491e11e | ||
|
|
c57b91f844 | ||
|
|
decf7e52b0 | ||
|
|
be91970d59 | ||
|
|
ddc07b40fc | ||
|
|
995fcf7573 | ||
|
|
5a59ce4149 | ||
|
|
fb768a4289 | ||
|
|
b55da012f6 | ||
|
|
991120fa10 |
No files matched your search
+4
-12
@@ -48,6 +48,7 @@ option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
option(WITH_ZSTD "build with zstd" OFF)
|
||||
if(MSVC)
|
||||
# Defaults currently different for GFLAGS.
|
||||
# We will address find_package work a little later
|
||||
@@ -108,7 +109,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${LZ4_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZSTD "build with zstd" OFF)
|
||||
if(WITH_ZSTD)
|
||||
find_package(zstd REQUIRED)
|
||||
add_definitions(-DZSTD)
|
||||
@@ -307,14 +307,6 @@ if(DISABLE_STALL_NOTIF)
|
||||
add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
set(OPTIMIZE_DEBUG_DEFAULT 0) # Debug build is unoptimized by default use -DOPTDBG=1 to optimize
|
||||
|
||||
if(DEFINED OPTDBG)
|
||||
set(OPTIMIZE_DEBUG ${OPTDBG})
|
||||
else()
|
||||
set(OPTIMIZE_DEBUG ${OPTIMIZE_DEBUG_DEFAULT})
|
||||
endif()
|
||||
|
||||
if(DEFINED USE_RTTI)
|
||||
if(USE_RTTI)
|
||||
@@ -342,8 +334,10 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
option(OPTDBG "Build optimized debug build with MSVC" OFF)
|
||||
if(MSVC)
|
||||
if((${OPTIMIZE_DEBUG} EQUAL 1))
|
||||
if(OPTDBG)
|
||||
message(STATUS "Debug optimization is enabled")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt /${RUNTIME_LIBRARY}d")
|
||||
else()
|
||||
@@ -494,7 +488,6 @@ set(SOURCES
|
||||
db/log_reader.cc
|
||||
db/log_writer.cc
|
||||
db/malloc_stats.cc
|
||||
db/managed_iterator.cc
|
||||
db/memtable.cc
|
||||
db/memtable_list.cc
|
||||
db/merge_helper.cc
|
||||
@@ -621,7 +614,6 @@ set(SOURCES
|
||||
utilities/blob_db/blob_log_reader.cc
|
||||
utilities/blob_db/blob_log_writer.cc
|
||||
utilities/blob_db/blob_log_format.cc
|
||||
utilities/blob_db/ttl_extractor.cc
|
||||
utilities/cassandra/cassandra_compaction_filter.cc
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
|
||||
+50
-5
@@ -1,23 +1,68 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### 5.15.10 (9/13/2018)
|
||||
### Bug Fixes
|
||||
* Fix RocksDB Java build and tests.
|
||||
|
||||
### 5.15.9 (9/4/2018)
|
||||
### Bug Fixes
|
||||
* Fix compilation errors on OS X clang due to '-Wsuggest-override'.
|
||||
|
||||
## 5.15.8 (8/31/2018)
|
||||
### Bug Fixes
|
||||
* Further avoid creating empty SSTs and subsequently deleting them during compaction.
|
||||
|
||||
## 5.15.7 (8/24/2018)
|
||||
### Bug Fixes
|
||||
* Avoid creating empty SSTs and subsequently deleting them in certain cases during compaction.
|
||||
|
||||
## 5.15.6 (8/21/2018)
|
||||
### Public API Change
|
||||
* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
|
||||
* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
|
||||
* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons
|
||||
|
||||
## 5.15.5 (8/16/2018)
|
||||
### Bug Fixes
|
||||
* Fix VerifyChecksum() API not preserving options
|
||||
|
||||
## 5.15.4 (8/11/2018)
|
||||
### Bug Fixes
|
||||
* Fix a bug caused by not generating OnTableFileCreated() notification for a 0-byte SST.
|
||||
|
||||
## 5.15.3 (8/10/2018)
|
||||
### Bug Fixes
|
||||
* Fix a bug in misreporting the estimated partition index size in properties block.
|
||||
|
||||
## 5.15.2 (8/9/2018)
|
||||
### Bug Fixes
|
||||
* Return correct usable_size for BlockContents.
|
||||
|
||||
## 5.15.1 (8/1/2018)
|
||||
### Bug Fixes
|
||||
* Prevent dereferencing invalid STL iterators when there are range tombstones in ingested files.
|
||||
|
||||
## 5.15.0 (7/17/2018)
|
||||
### Public API Change
|
||||
* Remove managed iterator. ReadOptions.managed is not effective anymore.
|
||||
* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default.
|
||||
* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
|
||||
* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
|
||||
* The "rocksdb.num.entries" table property no longer counts range deletion tombstones as entries.
|
||||
|
||||
### New Features
|
||||
* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatbile but not forward compatible. It is disabled by default unless format_version 3 or above is used.
|
||||
* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1.
|
||||
* Support dynamically changing `ColumnFamilyOptions::ttl` via `SetOptions()`.
|
||||
* Add a new table property, "rocksdb.num.range-deletions", which counts the number of range deletion tombstones in the table.
|
||||
* Improve the performance of iterators doing long range scans by using readahead, when using direct IO.
|
||||
* pin_top_level_index_and_filter (default true) in BlockBasedTableOptions can be used in combination with cache_index_and_filter_blocks to prefetch and pin the top-level index of partitioned index and filter blocks in cache. It has no impact when cache_index_and_filter_blocks is false.
|
||||
* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1
|
||||
|
||||
### Bug Fixes
|
||||
* fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
|
||||
* Fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
|
||||
* Check conflict at output level in CompactFiles.
|
||||
* Fix corruption in non-iterator reads when mmap is used for file reads
|
||||
* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
|
||||
* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints.
|
||||
* Fix a bug caused by not copying the block trailer with compressed SST file, direct IO, prefetcher and no compressed block cache.
|
||||
* Fix write can stuck indefinitely if enable_pipelined_write=true. The issue exists since pipelined write was introduced in 5.5.0.
|
||||
|
||||
## 5.14.0 (5/16/2018)
|
||||
### Public API Change
|
||||
|
||||
@@ -1600,7 +1600,7 @@ ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org
|
||||
BZIP2_DOWNLOAD_BASE ?= https://web.archive.org/web/20180624184835/http://www.bzip.org
|
||||
SNAPPY_VER ?= 1.1.4
|
||||
SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download
|
||||
@@ -1829,7 +1829,7 @@ $(java_libobjects): jl/%.o: %.cc
|
||||
rocksdbjava: $(java_all_libobjects)
|
||||
$(AM_V_GEN)cd java;$(MAKE) javalib;
|
||||
$(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_all_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
$(AM_V_at)cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
$(AM_V_at)cd java/target;jar -uf $(ROCKSDB_JAR) $(ROCKSDBJNILIB)
|
||||
$(AM_V_at)cd java/target/classes;jar -uf ../$(ROCKSDB_JAR) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
|
||||
@@ -106,7 +106,6 @@ cpp_library(
|
||||
"db/log_writer.cc",
|
||||
"db/logs_with_prep_tracker.cc",
|
||||
"db/malloc_stats.cc",
|
||||
"db/managed_iterator.cc",
|
||||
"db/memtable.cc",
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
@@ -236,7 +235,6 @@ cpp_library(
|
||||
"utilities/blob_db/blob_log_format.cc",
|
||||
"utilities/blob_db/blob_log_reader.cc",
|
||||
"utilities/blob_db/blob_log_writer.cc",
|
||||
"utilities/blob_db/ttl_extractor.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
|
||||
@@ -53,11 +53,13 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ -z "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/6e8e715624fd15256a7970073387793dfcf79b46/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/ef37e1faa1c29782abfac1ae65a291b9b7966f6d/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c67031f0f739ac61575a061518d6ef5038f99f90/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/60d6f124a78798b73944f5ba87c2306ae3460153/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/22c2d65676fb7c23cfa797c4f6937f38b026f3cf/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/907b498203d297947f3bb70b9466f47e100f1873/r131/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/3ee276cbacfad3074e3f07bf826ac47f06970f4e/1.3.5/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/9c910d36d6235cc40e8ff559358f1833452300ca/master/platform007/5b0f53e
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9cbf2460284c669ed19c3ccb200a71f7dd7e53c7/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/bf3d7497fe4e6d007354f0adffa16ce3003f8338/1.3/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/ff4e0b093534704d8abab678a4fd7f5ea7b094c7/2018_U5/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/b5c4a61a5c483ba24722005ae07895971a2ac707/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/92ff90349e2f43ea0a8246d8b1cf17b6869013e3/2.29.1/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/f3f697a28122e6bcd513273dd9c1ff23852fc59f/3.13.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/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_platform007.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"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# 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=1
|
||||
export PORTABLE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_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 $BZIP_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 $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
# lua not supported because it's on track for deprecation, I think
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -85,8 +85,9 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
LITE="OPT=\"-DROCKSDB_LITE -g\""
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
|
||||
# using platform007 gives us gcc-8 or higher which has that bug fixed.
|
||||
TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
|
||||
|
||||
@@ -53,6 +53,45 @@ function get_lib_base()
|
||||
log_variable $__res_var
|
||||
}
|
||||
|
||||
###########################################################
|
||||
# platform007 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 7.x platform007
|
||||
get_lib_base glibc 2.26 platform007
|
||||
get_lib_base snappy LATEST platform007
|
||||
get_lib_base zlib LATEST platform007
|
||||
get_lib_base bzip2 LATEST platform007
|
||||
get_lib_base lz4 LATEST platform007
|
||||
get_lib_base zstd LATEST platform007
|
||||
get_lib_base gflags LATEST platform007
|
||||
get_lib_base jemalloc LATEST platform007
|
||||
get_lib_base numa LATEST platform007
|
||||
get_lib_base libunwind LATEST platform007
|
||||
get_lib_base tbb LATEST platform007
|
||||
|
||||
get_lib_base kernel-headers fb platform007
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform007
|
||||
get_lib_base lua 5.3.4 platform007
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
###########################################################
|
||||
# 5.x dependencies #
|
||||
###########################################################
|
||||
|
||||
+3
-5
@@ -230,11 +230,9 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
// Output to event logger and fire events.
|
||||
if (!s.ok() || meta->fd.GetFileSize() > 0) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
}
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1075,7 +1075,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (status.ok() && sub_compact->builder == nullptr &&
|
||||
sub_compact->outputs.size() == 0) {
|
||||
sub_compact->outputs.size() == 0 &&
|
||||
!range_del_agg->IsEmpty()) {
|
||||
// handle subcompaction containing only range deletions
|
||||
status = OpenCompactionOutputFile(sub_compact);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,22 @@ private:
|
||||
std::vector<std::atomic<int>> compaction_completed_;
|
||||
};
|
||||
|
||||
class SstStatsCollector : public EventListener {
|
||||
public:
|
||||
SstStatsCollector() : num_ssts_creation_started_(0) {}
|
||||
|
||||
void OnTableFileCreationStarted(const TableFileCreationBriefInfo& /* info */) override {
|
||||
++num_ssts_creation_started_;
|
||||
}
|
||||
|
||||
int num_ssts_creation_started() {
|
||||
return num_ssts_creation_started_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int> num_ssts_creation_started_;
|
||||
};
|
||||
|
||||
static const int kCDTValueSize = 1000;
|
||||
static const int kCDTKeysPerBuffer = 4;
|
||||
static const int kCDTNumLevels = 8;
|
||||
@@ -3376,14 +3392,13 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel());
|
||||
|
||||
// Delete previously written keys.
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
// Overwrite previous keys with smaller, but predictable, values.
|
||||
ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j)));
|
||||
}
|
||||
Flush();
|
||||
@@ -3396,7 +3411,7 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
env_->addon_time_.fetch_add(36 * 60 * 60); // 36 hours
|
||||
ASSERT_EQ("0,2,0,2", FilesPerLevel());
|
||||
|
||||
// Just do a siimple write + flush so that the Ttl expired files get
|
||||
// Just do a simple write + flush so that the Ttl expired files get
|
||||
// compacted.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
@@ -3410,6 +3425,57 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
// All non-L0 files are deleted, as they contained only deleted data.
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Test dynamically changing ttl.
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_EQ("0,0,0,2", FilesPerLevel());
|
||||
|
||||
// Delete previously written keys.
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(Delete(Key(i * kNumKeysPerFile + j)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("2,0,0,2", FilesPerLevel());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,2,0,2", FilesPerLevel());
|
||||
|
||||
// Move time forward by 12 hours, and make sure that compaction still doesn't
|
||||
// trigger as ttl is set to 24 hours.
|
||||
env_->addon_time_.fetch_add(12 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("1,2,0,2", FilesPerLevel());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
ASSERT_TRUE(compaction->compaction_reason() == CompactionReason::kTtl);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Dynamically change ttl to 10 hours.
|
||||
// This should trigger a ttl compaction, as 12 hours have already passed.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"ttl", "36000"}}));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// All non-L0 files are deleted, as they contained only deleted data.
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
@@ -3766,6 +3832,30 @@ TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
|
||||
bg_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionHasEmptyOutput) {
|
||||
Options options = CurrentOptions();
|
||||
SstStatsCollector* collector = new SstStatsCollector();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(collector);
|
||||
Reopen(options);
|
||||
|
||||
// Make sure the L0 files overlap to prevent trivial move.
|
||||
ASSERT_OK(Put("a", "val"));
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("a"));
|
||||
ASSERT_OK(Delete("b"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Expect one file creation to start for each flush, and zero for compaction
|
||||
// since no keys are written.
|
||||
ASSERT_EQ(2, collector->num_ssts_creation_started());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
|
||||
+18
-37
@@ -41,7 +41,6 @@
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/malloc_stats.h"
|
||||
#include "db/managed_iterator.h"
|
||||
#include "db/memtable.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/merge_context.h"
|
||||
@@ -1578,6 +1577,10 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
}
|
||||
Iterator* result = nullptr;
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
@@ -1595,22 +1598,7 @@ Iterator* DBImpl::NewIterator(const ReadOptions& read_options,
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (read_options.managed) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
result = NewErrorIterator(Status::InvalidArgument(
|
||||
"Managed Iterators not supported in RocksDBLite."));
|
||||
#else
|
||||
if ((read_options.tailing) || (read_options.snapshot != nullptr) ||
|
||||
(is_snapshot_supported_)) {
|
||||
result = new ManagedIterator(this, read_options, cfd);
|
||||
} else {
|
||||
// Managed iter not supported
|
||||
result = NewErrorIterator(Status::InvalidArgument(
|
||||
"Managed Iterators not supported without snapshots."));
|
||||
}
|
||||
#endif
|
||||
} else if (read_options.tailing) {
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
result = nullptr;
|
||||
@@ -1705,6 +1693,9 @@ Status DBImpl::NewIterators(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
if (read_options.managed) {
|
||||
return Status::NotSupported("Managed iterator is not supported anymore.");
|
||||
}
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators.");
|
||||
@@ -1712,23 +1703,7 @@ Status DBImpl::NewIterators(
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
if (read_options.managed) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
return Status::InvalidArgument(
|
||||
"Managed interator not supported in RocksDB lite");
|
||||
#else
|
||||
if ((!read_options.tailing) && (read_options.snapshot == nullptr) &&
|
||||
(!is_snapshot_supported_)) {
|
||||
return Status::InvalidArgument(
|
||||
"Managed interator not supported without snapshots");
|
||||
}
|
||||
for (auto cfh : column_families) {
|
||||
auto cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
auto iter = new ManagedIterator(this, read_options, cfd);
|
||||
iterators->push_back(iter);
|
||||
}
|
||||
#endif
|
||||
} else if (read_options.tailing) {
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
return Status::InvalidArgument(
|
||||
"Tailing interator not supported in RocksDB lite");
|
||||
@@ -2937,6 +2912,8 @@ Status DBImpl::IngestExternalFile(
|
||||
status = ingestion_job.Prepare(external_files, super_version);
|
||||
CleanupSuperVersion(super_version);
|
||||
if (!status.ok()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ReleaseFileNumberFromPendingOutputs(pending_output_elem);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -3030,8 +3007,6 @@ Status DBImpl::IngestExternalFile(
|
||||
|
||||
Status DBImpl::VerifyChecksum() {
|
||||
Status s;
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
std::vector<ColumnFamilyData*> cfd_list;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -3049,13 +3024,19 @@ Status DBImpl::VerifyChecksum() {
|
||||
for (auto& sv : sv_list) {
|
||||
VersionStorageInfo* vstorage = sv->current->storage_info();
|
||||
ColumnFamilyData* cfd = sv->current->cfd();
|
||||
Options opts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
opts = Options(BuildDBOptions(immutable_db_options_,
|
||||
mutable_db_options_), cfd->GetLatestCFOptions());
|
||||
}
|
||||
for (int i = 0; i < vstorage->num_non_empty_levels() && s.ok(); i++) {
|
||||
for (size_t j = 0; j < vstorage->LevelFilesBrief(i).num_files && s.ok();
|
||||
j++) {
|
||||
const auto& fd = vstorage->LevelFilesBrief(i).files[j].fd;
|
||||
std::string fname = TableFileName(cfd->ioptions()->cf_paths,
|
||||
fd.GetNumber(), fd.GetPathId());
|
||||
s = rocksdb::VerifySstFileChecksum(options, env_options, fname);
|
||||
s = rocksdb::VerifySstFileChecksum(opts, env_options_, fname);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
|
||||
+24
-67
@@ -184,73 +184,6 @@ TEST_P(DBIteratorTest, NonBlockingIteration) {
|
||||
kSkipMmapReads));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBIteratorTest, ManagedNonBlockingIteration) {
|
||||
do {
|
||||
ReadOptions non_blocking_opts, regular_opts;
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
non_blocking_opts.read_tier = kBlockCacheTier;
|
||||
non_blocking_opts.managed = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// write one kv to the database.
|
||||
ASSERT_OK(Put(1, "a", "b"));
|
||||
|
||||
// scan using non-blocking iterator. We should find it because
|
||||
// it is in memtable.
|
||||
Iterator* iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
int count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
delete iter;
|
||||
|
||||
// flush memtable to storage. Now, the key should not be in the
|
||||
// memtable neither in the block cache.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// verify that a non-blocking iterator does not find any
|
||||
// kvs. Neither does it do any IOs to storage.
|
||||
int64_t numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
int64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
delete iter;
|
||||
|
||||
// read in the specified block via a regular get
|
||||
ASSERT_EQ(Get(1, "a"), "b");
|
||||
|
||||
// verify that we can find it via a non-blocking scan
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
iter = NewIterator(non_blocking_opts, handles_[1]);
|
||||
count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
count++;
|
||||
}
|
||||
ASSERT_EQ(count, 1);
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
delete iter;
|
||||
|
||||
// This test verifies block cache behaviors, which is not used by plain
|
||||
// table format.
|
||||
// Exclude kHashCuckoo as it does not support iteration currently
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipNoSeekToLast | kSkipHashCuckoo |
|
||||
kSkipMmapReads));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekBeforePrev) {
|
||||
ASSERT_OK(Put("a", "b"));
|
||||
ASSERT_OK(Put("c", "d"));
|
||||
@@ -2480,6 +2413,30 @@ TEST_P(DBIteratorTest, NonBlockingIterationBugRepro) {
|
||||
EXPECT_TRUE(iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, SeekBackwardAfterOutOfUpperBound) {
|
||||
Put("a", "");
|
||||
Put("b", "");
|
||||
Flush();
|
||||
|
||||
ReadOptions ropt;
|
||||
Slice ub = "b";
|
||||
ropt.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> it(dbfull()->NewIterator(ropt));
|
||||
it->SeekForPrev("a");
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ("a", it->key().ToString());
|
||||
it->Next();
|
||||
ASSERT_FALSE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
it->SeekForPrev("a");
|
||||
ASSERT_OK(it->status());
|
||||
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_EQ("a", it->key().ToString());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBIteratorTestInstance, DBIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
|
||||
@@ -479,275 +479,6 @@ TEST_F(DBTestTailingIterator, TailingIteratorGap) {
|
||||
ASSERT_EQ("40", it->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSingle) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
// add a record and check that iter can see it
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "mirko");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorKeepAdding) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
std::string value(1024, 'a');
|
||||
|
||||
const int num_records = 10000;
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%016d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
|
||||
iter->Seek(key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(key), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToNext) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = 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_F(DBTestTailingIterator, ManagedTailingIteratorDeletes) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
|
||||
// write a single record, read it using the iterator, then delete it
|
||||
ASSERT_OK(Put(1, "0test", "test"));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0test");
|
||||
ASSERT_OK(Delete(1, "0test"));
|
||||
|
||||
// write many more records
|
||||
const int num_records = 10000;
|
||||
std::string value(1024, 'A');
|
||||
|
||||
for (int i = 0; i < num_records; ++i) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "1%015d", i);
|
||||
|
||||
Slice key(buf, 16);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// force a flush to make sure that no records are read from memtable
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// skip "0test"
|
||||
iter->Next();
|
||||
|
||||
// make sure we can read all new records using the existing iterator
|
||||
int count = 0;
|
||||
for (; iter->Valid(); iter->Next(), ++count) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(count, num_records);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorPrefixSeek) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
||||
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
||||
ASSERT_OK(Put(1, "0101", "test"));
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "0202", "test"));
|
||||
|
||||
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
||||
iter->Seek("0102");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
|
||||
iter->Seek("0202");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), "0202");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorIncomplete) {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::string key = "key";
|
||||
std::string value = "value";
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
// we either see the entry or it's not in cache
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
iter->SeekToFirst();
|
||||
// should still be true after compaction
|
||||
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ManagedTailingIteratorSeekToSame) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 1000;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
read_options.managed = true;
|
||||
|
||||
const int NROWS = 10000;
|
||||
// Write rows with keys 00000, 00002, 00004 etc.
|
||||
for (int i = 0; i < NROWS; ++i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%05d", 2 * i);
|
||||
std::string key(buf);
|
||||
std::string value("value");
|
||||
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// Seek to 00001. We expect to find 00002.
|
||||
std::string start_key = "00001";
|
||||
iter->Seek(start_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
|
||||
std::string found = iter->key().ToString();
|
||||
ASSERT_EQ("00002", found);
|
||||
|
||||
// Now seek to the same key. The iterator should remain in the same
|
||||
// position.
|
||||
iter->Seek(found);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(found, iter->key().ToString());
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, ForwardIteratorVersionProperty) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 1000;
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
Put("foo", "bar");
|
||||
|
||||
uint64_t v1, v2, v3, v4;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->Seek("foo");
|
||||
std::string prop_value;
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v1 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
Put("foo1", "bar1");
|
||||
Flush();
|
||||
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v2 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
iter->Seek("f");
|
||||
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v3 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
|
||||
ASSERT_EQ(v1, v2);
|
||||
ASSERT_GT(v3, v2);
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->Seek("foo");
|
||||
std::string prop_value;
|
||||
ASSERT_OK(iter->GetProperty("rocksdb.iterator.super-version-number",
|
||||
&prop_value));
|
||||
v4 = static_cast<uint64_t>(std::atoi(prop_value.c_str()));
|
||||
}
|
||||
ASSERT_EQ(v3, v4);
|
||||
}
|
||||
|
||||
TEST_F(DBTestTailingIterator, SeekWithUpperBoundBug) {
|
||||
ReadOptions read_options;
|
||||
read_options.tailing = true;
|
||||
|
||||
@@ -1822,7 +1822,7 @@ TEST_P(DBTestUniversalCompaction, FinalSortedRunCompactFilesConflict) {
|
||||
"CompactFilesImpl:1"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread compact_files_thread([&]() {
|
||||
port::Thread compact_files_thread([&]() {
|
||||
ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), default_cfh,
|
||||
{first_sst_filename}, num_levels_ - 1));
|
||||
});
|
||||
|
||||
@@ -346,6 +346,12 @@ class IterKey {
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
|
||||
// The bool will be picked up by the next calls to SetKey
|
||||
void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; }
|
||||
|
||||
// Returns the key in whichever format that was provided to KeyIter
|
||||
Slice GetKey() const { return Slice(key_, key_size_); }
|
||||
|
||||
Slice GetInternalKey() const {
|
||||
assert(!IsUserKey());
|
||||
return Slice(key_, key_size_);
|
||||
@@ -395,6 +401,11 @@ class IterKey {
|
||||
key_size_ = total_size;
|
||||
}
|
||||
|
||||
Slice SetKey(const Slice& key, bool copy = true) {
|
||||
// is_user_key_ expected to be set already via SetIsUserKey
|
||||
return SetKeyImpl(key, copy);
|
||||
}
|
||||
|
||||
Slice SetUserKey(const Slice& key, bool copy = true) {
|
||||
is_user_key_ = true;
|
||||
return SetKeyImpl(key, copy);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -1292,6 +1293,39 @@ TEST_F(ExternalSSTFileTest, PickedLevelBug) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, IngestNonExistingFile) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Status s = db_->IngestExternalFile({"non_existing_file"},
|
||||
IngestExternalFileOptions());
|
||||
ASSERT_NOK(s);
|
||||
|
||||
// Verify file deletion is not impacted (verify a bug fix)
|
||||
ASSERT_OK(Put(Key(1), Key(1)));
|
||||
ASSERT_OK(Put(Key(9), Key(9)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(Key(1), Key(1)));
|
||||
ASSERT_OK(Put(Key(9), Key(9)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// After full compaction, there should be only 1 file.
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(dbname_, &files);
|
||||
int num_sst_files = 0;
|
||||
for (auto& f : files) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(f, &number, &type) && type == kTableFile) {
|
||||
num_sst_files++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(1, num_sst_files);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = false;
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/managed_iterator.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "table/merging_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
namespace {
|
||||
// Helper class that locks a mutex on construction and unlocks the mutex when
|
||||
// the destructor of the MutexLock object is invoked.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// void MyClass::MyMethod() {
|
||||
// MILock l(&mu_); // mu_ is an instance variable
|
||||
// ... some complex code, possibly with multiple return paths ...
|
||||
// }
|
||||
|
||||
class MILock {
|
||||
public:
|
||||
explicit MILock(std::mutex* mu, ManagedIterator* mi) : mu_(mu), mi_(mi) {
|
||||
this->mu_->lock();
|
||||
}
|
||||
~MILock() {
|
||||
this->mu_->unlock();
|
||||
}
|
||||
ManagedIterator* GetManagedIterator() { return mi_; }
|
||||
|
||||
private:
|
||||
std::mutex* const mu_;
|
||||
ManagedIterator* mi_;
|
||||
// No copying allowed
|
||||
MILock(const MILock&) = delete;
|
||||
void operator=(const MILock&) = delete;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
//
|
||||
// Synchronization between modifiers, releasers, creators
|
||||
// If iterator operation, wait till (!in_use), set in_use, do op, reset in_use
|
||||
// if modifying mutable_iter, atomically exchange in_use:
|
||||
// return if in_use set / otherwise set in use,
|
||||
// atomically replace new iter with old , reset in use
|
||||
// The releaser is the new operation and it holds a lock for a very short time
|
||||
// The existing non-const iterator operations are supposed to be single
|
||||
// threaded and hold the lock for the duration of the operation
|
||||
// The existing const iterator operations use the cached key/values
|
||||
// and don't do any locking.
|
||||
ManagedIterator::ManagedIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd)
|
||||
: db_(db),
|
||||
read_options_(read_options),
|
||||
cfd_(cfd),
|
||||
svnum_(cfd->GetSuperVersionNumber()),
|
||||
mutable_iter_(nullptr),
|
||||
valid_(false),
|
||||
snapshot_created_(false),
|
||||
release_supported_(true) {
|
||||
read_options_.managed = false;
|
||||
if ((!read_options_.tailing) && (read_options_.snapshot == nullptr)) {
|
||||
assert(nullptr != (read_options_.snapshot = db_->GetSnapshot()));
|
||||
snapshot_created_ = true;
|
||||
}
|
||||
cfh_.SetCFD(cfd);
|
||||
mutable_iter_ = unique_ptr<Iterator>(db->NewIterator(read_options_, &cfh_));
|
||||
}
|
||||
|
||||
ManagedIterator::~ManagedIterator() {
|
||||
Lock();
|
||||
if (snapshot_created_) {
|
||||
db_->ReleaseSnapshot(read_options_.snapshot);
|
||||
snapshot_created_ = false;
|
||||
read_options_.snapshot = nullptr;
|
||||
}
|
||||
UnLock();
|
||||
}
|
||||
|
||||
bool ManagedIterator::Valid() const { return valid_; }
|
||||
|
||||
void ManagedIterator::SeekToLast() {
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
mutable_iter_->SeekToLast();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekToFirst() {
|
||||
MILock l(&in_use_, this);
|
||||
SeekInternal(Slice(), true);
|
||||
}
|
||||
|
||||
void ManagedIterator::Seek(const Slice& user_key) {
|
||||
MILock l(&in_use_, this);
|
||||
SeekInternal(user_key, false);
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekForPrev(const Slice& user_key) {
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
mutable_iter_->SeekForPrev(user_key);
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::SeekInternal(const Slice& user_key, bool seek_to_first) {
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator();
|
||||
}
|
||||
assert(mutable_iter_ != nullptr);
|
||||
if (seek_to_first) {
|
||||
mutable_iter_->SeekToFirst();
|
||||
} else {
|
||||
mutable_iter_->Seek(user_key);
|
||||
}
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::Prev() {
|
||||
if (!valid_) {
|
||||
status_ = Status::InvalidArgument("Iterator value invalid");
|
||||
return;
|
||||
}
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator(true);
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutable_iter_->Prev();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
void ManagedIterator::Next() {
|
||||
if (!valid_) {
|
||||
status_ = Status::InvalidArgument("Iterator value invalid");
|
||||
return;
|
||||
}
|
||||
MILock l(&in_use_, this);
|
||||
if (NeedToRebuild()) {
|
||||
RebuildIterator(true);
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mutable_iter_->Next();
|
||||
UpdateCurrent();
|
||||
}
|
||||
|
||||
Slice ManagedIterator::key() const {
|
||||
assert(valid_);
|
||||
return cached_key_.GetUserKey();
|
||||
}
|
||||
|
||||
Slice ManagedIterator::value() const {
|
||||
assert(valid_);
|
||||
return cached_value_.GetUserKey();
|
||||
}
|
||||
|
||||
Status ManagedIterator::status() const { return status_; }
|
||||
|
||||
void ManagedIterator::RebuildIterator(bool reseek) {
|
||||
std::string current_key;
|
||||
if (reseek) {
|
||||
current_key = key().ToString();
|
||||
}
|
||||
|
||||
svnum_ = cfd_->GetSuperVersionNumber();
|
||||
mutable_iter_ = unique_ptr<Iterator>(db_->NewIterator(read_options_, &cfh_));
|
||||
|
||||
if (reseek) {
|
||||
Slice old_key(current_key.data(), current_key.size());
|
||||
SeekInternal(old_key, false);
|
||||
UpdateCurrent();
|
||||
if (!valid_ || key().compare(old_key) != 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::Incomplete(
|
||||
"Next/Prev failed because current key has "
|
||||
"been removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManagedIterator::UpdateCurrent() {
|
||||
assert(mutable_iter_ != nullptr);
|
||||
|
||||
valid_ = mutable_iter_->Valid();
|
||||
status_ = mutable_iter_->status();
|
||||
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
cached_key_.SetUserKey(mutable_iter_->key());
|
||||
cached_value_.SetUserKey(mutable_iter_->value());
|
||||
}
|
||||
|
||||
void ManagedIterator::ReleaseIter(bool only_old) {
|
||||
if ((mutable_iter_ == nullptr) || (!release_supported_)) {
|
||||
return;
|
||||
}
|
||||
if (svnum_ != cfd_->GetSuperVersionNumber() || !only_old) {
|
||||
if (!TryLock()) { // Don't release iter if in use
|
||||
return;
|
||||
}
|
||||
mutable_iter_ = nullptr; // in_use for a very short time
|
||||
UnLock();
|
||||
}
|
||||
}
|
||||
|
||||
bool ManagedIterator::NeedToRebuild() {
|
||||
if ((mutable_iter_ == nullptr) || (status_.IsIncomplete()) ||
|
||||
(!only_drop_old_ && (svnum_ != cfd_->GetSuperVersionNumber()))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ManagedIterator::Lock() {
|
||||
in_use_.lock();
|
||||
return;
|
||||
}
|
||||
|
||||
bool ManagedIterator::TryLock() { return in_use_.try_lock(); }
|
||||
|
||||
void ManagedIterator::UnLock() {
|
||||
in_use_.unlock();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#pragma once
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBImpl;
|
||||
struct SuperVersion;
|
||||
class ColumnFamilyData;
|
||||
|
||||
/**
|
||||
* ManagedIterator is a special type of iterator that supports freeing the
|
||||
* underlying iterator and still being able to access the current key/value
|
||||
* pair. This is done by copying the key/value pair so that clients can
|
||||
* continue to access the data without getting a SIGSEGV.
|
||||
* The underlying iterator can be freed manually through the call to
|
||||
* ReleaseIter or automatically (as needed on space pressure or age.)
|
||||
* The iterator is recreated using the saved original arguments.
|
||||
*/
|
||||
class ManagedIterator : public Iterator {
|
||||
public:
|
||||
ManagedIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd);
|
||||
virtual ~ManagedIterator();
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
virtual void Prev() override;
|
||||
virtual bool Valid() const override;
|
||||
void SeekToFirst() override;
|
||||
virtual void Seek(const Slice& target) override;
|
||||
virtual void SeekForPrev(const Slice& target) override;
|
||||
virtual void Next() override;
|
||||
virtual Slice key() const override;
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
void ReleaseIter(bool only_old);
|
||||
void SetDropOld(bool only_old) {
|
||||
only_drop_old_ = read_options_.tailing || only_old;
|
||||
}
|
||||
|
||||
private:
|
||||
void RebuildIterator(bool reseek = false);
|
||||
void UpdateCurrent();
|
||||
void SeekInternal(const Slice& user_key, bool seek_to_first);
|
||||
bool NeedToRebuild();
|
||||
void Lock();
|
||||
bool TryLock();
|
||||
void UnLock();
|
||||
DBImpl* const db_;
|
||||
ReadOptions read_options_;
|
||||
ColumnFamilyData* const cfd_;
|
||||
ColumnFamilyHandleInternal cfh_;
|
||||
|
||||
uint64_t svnum_;
|
||||
std::unique_ptr<Iterator> mutable_iter_;
|
||||
// internal iterator status
|
||||
Status status_;
|
||||
bool valid_;
|
||||
|
||||
IterKey cached_key_;
|
||||
IterKey cached_value_;
|
||||
|
||||
bool only_drop_old_ = true;
|
||||
bool snapshot_created_;
|
||||
bool release_supported_;
|
||||
std::mutex in_use_; // is managed iterator in use
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // !ROCKSDB_LITE
|
||||
+1
-1
@@ -697,7 +697,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->merge_in_progress) = true;
|
||||
merge_context->PushOperand(
|
||||
v, s->inplace_update_support == false /* operand_pinned */);
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperands())) {
|
||||
if (merge_operator->ShouldMerge(merge_context->GetOperandsDirectionBackward())) {
|
||||
*(s->status) = MergeHelper::TimedFullMerge(
|
||||
merge_operator, s->key->user_key(), nullptr,
|
||||
merge_context->GetOperands(), s->value, s->logger, s->statistics,
|
||||
|
||||
+16
-1
@@ -74,8 +74,13 @@ class MergeContext {
|
||||
return (*operand_list_)[index];
|
||||
}
|
||||
|
||||
// Return all the operands.
|
||||
// Same as GetOperandsDirectionForward
|
||||
const std::vector<Slice>& GetOperands() {
|
||||
return GetOperandsDirectionForward();
|
||||
}
|
||||
|
||||
// Return all the operands in the order as they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionForward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
@@ -84,6 +89,16 @@ class MergeContext {
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
// Return all the operands in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
const std::vector<Slice>& GetOperandsDirectionBackward() {
|
||||
if (!operand_list_) {
|
||||
return empty_operand_list;
|
||||
}
|
||||
|
||||
SetDirectionBackward();
|
||||
return *operand_list_;
|
||||
}
|
||||
|
||||
private:
|
||||
void Initialize() {
|
||||
if (!operand_list_) {
|
||||
|
||||
+16
-13
@@ -50,7 +50,7 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
: rep_(TombstoneStartKeyComparator(ucmp)), ucmp_(ucmp) {}
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) {
|
||||
RangeDelPositioningMode mode) override {
|
||||
(void)mode;
|
||||
assert(mode == RangeDelPositioningMode::kFullScan);
|
||||
for (const auto& tombstone : rep_) {
|
||||
@@ -65,7 +65,7 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end) {
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end) override {
|
||||
for (const auto& tombstone : rep_) {
|
||||
if (ucmp_->Compare(start, tombstone.end_key_) < 0 &&
|
||||
ucmp_->Compare(tombstone.start_key_, end) <= 0 &&
|
||||
@@ -76,13 +76,13 @@ class UncollapsedRangeDelMap : public RangeDelMap {
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddTombstone(RangeTombstone tombstone) { rep_.emplace(tombstone); }
|
||||
void AddTombstone(RangeTombstone tombstone) override { rep_.emplace(tombstone); }
|
||||
|
||||
size_t Size() const { return rep_.size(); }
|
||||
size_t Size() const override { return rep_.size(); }
|
||||
|
||||
void InvalidatePosition() {} // no-op
|
||||
void InvalidatePosition() override {} // no-op
|
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() {
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() override {
|
||||
return std::unique_ptr<RangeDelIterator>(new Iterator(this->rep_));
|
||||
}
|
||||
};
|
||||
@@ -155,6 +155,9 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
|
||||
RangeTombstone Tombstone() const override {
|
||||
assert(Valid());
|
||||
assert(std::next(iter_) != rep_.end());
|
||||
assert(iter_->second != 0);
|
||||
RangeTombstone tombstone;
|
||||
tombstone.start_key_ = iter_->first;
|
||||
tombstone.end_key_ = std::next(iter_)->first;
|
||||
@@ -173,7 +176,7 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) {
|
||||
RangeDelPositioningMode mode) override {
|
||||
if (iter_ == rep_.end() &&
|
||||
(mode == RangeDelPositioningMode::kForwardTraversal ||
|
||||
mode == RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
@@ -224,15 +227,15 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
return parsed.sequence < iter_->second;
|
||||
}
|
||||
|
||||
bool IsRangeOverlapped(const Slice&, const Slice&) {
|
||||
bool IsRangeOverlapped(const Slice&, const Slice&) override {
|
||||
// Unimplemented because the only client of this method, file ingestion,
|
||||
// uses uncollapsed maps.
|
||||
fprintf(stderr, "CollapsedRangeDelMap::IsRangeOverlapped unimplemented");
|
||||
abort();
|
||||
}
|
||||
|
||||
void AddTombstone(RangeTombstone t) {
|
||||
if (ucmp_->Compare(t.start_key_, t.end_key_) >= 0) {
|
||||
void AddTombstone(RangeTombstone t) override {
|
||||
if (ucmp_->Compare(t.start_key_, t.end_key_) >= 0 || t.seq_ == 0) {
|
||||
// The tombstone covers no keys. Nothing to do.
|
||||
return;
|
||||
}
|
||||
@@ -341,11 +344,11 @@ class CollapsedRangeDelMap : public RangeDelMap {
|
||||
}
|
||||
}
|
||||
|
||||
size_t Size() const { return rep_.size() - 1; }
|
||||
size_t Size() const override { return rep_.empty() ? 0 : rep_.size() - 1; }
|
||||
|
||||
void InvalidatePosition() { iter_ = rep_.end(); }
|
||||
void InvalidatePosition() override { iter_ = rep_.end(); }
|
||||
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() {
|
||||
std::unique_ptr<RangeDelIterator> NewIterator() override {
|
||||
return std::unique_ptr<RangeDelIterator>(new Iterator(this->rep_));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ class DisableGCSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
virtual ~DisableGCSnapshotChecker() {}
|
||||
virtual bool IsInSnapshot(SequenceNumber /*sequence*/,
|
||||
SequenceNumber /*snapshot_sequence*/) const {
|
||||
SequenceNumber /*snapshot_sequence*/) const override {
|
||||
// By returning false, we prevent all the values from being GCed
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ class SnapshotList {
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
// No copy-construct.
|
||||
SnapshotList(const SnapshotList&) = delete;
|
||||
|
||||
bool empty() const { return list_.next_ == &list_; }
|
||||
SnapshotImpl* oldest() const { assert(!empty()); return list_.next_; }
|
||||
SnapshotImpl* newest() const { assert(!empty()); return list_.prev_; }
|
||||
|
||||
+23
-18
@@ -1665,8 +1665,8 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
ComputeFilesMarkedForCompaction();
|
||||
ComputeBottommostFilesMarkedForCompaction();
|
||||
if (immutable_cf_options.ttl > 0) {
|
||||
ComputeExpiredTtlFiles(immutable_cf_options);
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
ComputeExpiredTtlFiles(immutable_cf_options, mutable_cf_options.ttl);
|
||||
}
|
||||
EstimateCompactionBytesNeeded(mutable_cf_options);
|
||||
}
|
||||
@@ -1695,8 +1695,8 @@ void VersionStorageInfo::ComputeFilesMarkedForCompaction() {
|
||||
}
|
||||
|
||||
void VersionStorageInfo::ComputeExpiredTtlFiles(
|
||||
const ImmutableCFOptions& ioptions) {
|
||||
assert(ioptions.ttl > 0);
|
||||
const ImmutableCFOptions& ioptions, const uint64_t ttl) {
|
||||
assert(ttl > 0);
|
||||
|
||||
expired_ttl_files_.clear();
|
||||
|
||||
@@ -1713,8 +1713,7 @@ void VersionStorageInfo::ComputeExpiredTtlFiles(
|
||||
f->fd.table_reader->GetTableProperties() != nullptr) {
|
||||
auto creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time > 0 &&
|
||||
creation_time < (current_time - ioptions.ttl)) {
|
||||
if (creation_time > 0 && creation_time < (current_time - ttl)) {
|
||||
expired_ttl_files_.emplace_back(level, f);
|
||||
}
|
||||
}
|
||||
@@ -2995,25 +2994,31 @@ Status VersionSet::ProcessManifestWrites(
|
||||
delete first_writer.cfd;
|
||||
}
|
||||
} else {
|
||||
uint64_t max_log_number_in_batch = 0;
|
||||
// Each version in versions corresponds to a column family.
|
||||
// For each column family, update its log number indicating that logs
|
||||
// with number smaller than this should be ignored.
|
||||
for (const auto version : versions) {
|
||||
uint64_t max_log_number_in_batch = 0;
|
||||
uint32_t cf_id = version->cfd_->GetID();
|
||||
for (const auto& e : batch_edits) {
|
||||
if (e->has_log_number_ && e->column_family_ == cf_id) {
|
||||
max_log_number_in_batch =
|
||||
std::max(max_log_number_in_batch, e->log_number_);
|
||||
}
|
||||
}
|
||||
if (max_log_number_in_batch != 0) {
|
||||
assert(version->cfd_->GetLogNumber() <= max_log_number_in_batch);
|
||||
version->cfd_->SetLogNumber(max_log_number_in_batch);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t last_min_log_number_to_keep = 0;
|
||||
for (auto& e : batch_edits) {
|
||||
if (e->has_log_number_) {
|
||||
max_log_number_in_batch =
|
||||
std::max(max_log_number_in_batch, e->log_number_);
|
||||
}
|
||||
if (e->has_min_log_number_to_keep_) {
|
||||
last_min_log_number_to_keep =
|
||||
std::max(last_min_log_number_to_keep, e->min_log_number_to_keep_);
|
||||
}
|
||||
}
|
||||
if (max_log_number_in_batch != 0) {
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
assert(cfd->GetLogNumber() <= max_log_number_in_batch);
|
||||
cfd->SetLogNumber(max_log_number_in_batch);
|
||||
}
|
||||
}
|
||||
|
||||
if (last_min_log_number_to_keep != 0) {
|
||||
// Should only be set in 2PC mode.
|
||||
|
||||
+2
-1
@@ -137,7 +137,8 @@ class VersionStorageInfo {
|
||||
|
||||
// This computes ttl_expired_files_ and is called by
|
||||
// ComputeCompactionScore()
|
||||
void ComputeExpiredTtlFiles(const ImmutableCFOptions& ioptions);
|
||||
void ComputeExpiredTtlFiles(const ImmutableCFOptions& ioptions,
|
||||
const uint64_t ttl);
|
||||
|
||||
// This computes bottommost_files_marked_for_compaction_ and is called by
|
||||
// ComputeCompactionScore() or UpdateOldestSnapshot().
|
||||
|
||||
+46
-7
@@ -263,6 +263,17 @@ void WriteThread::CreateMissingNewerLinks(Writer* head) {
|
||||
}
|
||||
}
|
||||
|
||||
WriteThread::Writer* WriteThread::FindNextLeader(Writer* from,
|
||||
Writer* boundary) {
|
||||
assert(from != nullptr && from != boundary);
|
||||
Writer* current = from;
|
||||
while (current->link_older != boundary) {
|
||||
current = current->link_older;
|
||||
assert(current != nullptr);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void WriteThread::CompleteLeader(WriteGroup& write_group) {
|
||||
assert(write_group.size > 0);
|
||||
Writer* leader = write_group.leader;
|
||||
@@ -558,21 +569,49 @@ void WriteThread::ExitAsBatchGroupLeader(WriteGroup& write_group,
|
||||
if (!leader->ShouldWriteToMemtable()) {
|
||||
CompleteLeader(write_group);
|
||||
}
|
||||
|
||||
Writer* next_leader = nullptr;
|
||||
|
||||
// Look for next leader before we call LinkGroup. If there isn't
|
||||
// pending writers, place a dummy writer at the tail of the queue
|
||||
// so we know the boundary of the current write group.
|
||||
Writer dummy;
|
||||
Writer* expected = last_writer;
|
||||
bool has_dummy = newest_writer_.compare_exchange_strong(expected, &dummy);
|
||||
if (!has_dummy) {
|
||||
// We find at least one pending writer when we insert dummy. We search
|
||||
// for next leader from there.
|
||||
next_leader = FindNextLeader(expected, last_writer);
|
||||
assert(next_leader != nullptr && next_leader != last_writer);
|
||||
}
|
||||
|
||||
// Link the ramaining of the group to memtable writer list.
|
||||
//
|
||||
// We have to link our group to memtable writer queue before wake up the
|
||||
// next leader or set newest_writer_ to null, otherwise the next leader
|
||||
// can run ahead of us and link to memtable writer queue before we do.
|
||||
if (write_group.size > 0) {
|
||||
if (LinkGroup(write_group, &newest_memtable_writer_)) {
|
||||
// The leader can now be different from current writer.
|
||||
SetState(write_group.leader, STATE_MEMTABLE_WRITER_LEADER);
|
||||
}
|
||||
}
|
||||
// Reset newest_writer_ and wake up the next leader.
|
||||
Writer* newest_writer = last_writer;
|
||||
if (!newest_writer_.compare_exchange_strong(newest_writer, nullptr)) {
|
||||
Writer* next_leader = newest_writer;
|
||||
while (next_leader->link_older != last_writer) {
|
||||
next_leader = next_leader->link_older;
|
||||
assert(next_leader != nullptr);
|
||||
|
||||
// If we have inserted dummy in the queue, remove it now and check if there
|
||||
// are pending writer join the queue since we insert the dummy. If so,
|
||||
// look for next leader again.
|
||||
if (has_dummy) {
|
||||
assert(next_leader == nullptr);
|
||||
expected = &dummy;
|
||||
bool has_pending_writer =
|
||||
!newest_writer_.compare_exchange_strong(expected, nullptr);
|
||||
if (has_pending_writer) {
|
||||
next_leader = FindNextLeader(expected, &dummy);
|
||||
assert(next_leader != nullptr && next_leader != &dummy);
|
||||
}
|
||||
}
|
||||
|
||||
if (next_leader != nullptr) {
|
||||
next_leader->link_older = nullptr;
|
||||
SetState(next_leader, STATE_GROUP_LEADER);
|
||||
}
|
||||
|
||||
@@ -392,6 +392,10 @@ class WriteThread {
|
||||
// concurrently with itself.
|
||||
void CreateMissingNewerLinks(Writer* head);
|
||||
|
||||
// Starting from a pending writer, follow link_older to search for next
|
||||
// leader, until we hit boundary.
|
||||
Writer* FindNextLeader(Writer* pending_writer, Writer* boundary);
|
||||
|
||||
// Set the leader in write_group to completed state and remove it from the
|
||||
// write group.
|
||||
void CompleteLeader(WriteGroup& write_group);
|
||||
|
||||
@@ -596,6 +596,8 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Enabled only for level compaction for now.
|
||||
//
|
||||
// Default: 0 (disabled)
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint64_t ttl = 0;
|
||||
|
||||
// Create ColumnFamilyOptions with default values for all fields
|
||||
|
||||
@@ -1195,6 +1195,7 @@ extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_read_tier(
|
||||
rocksdb_readoptions_t*, int);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_tailing(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
// The functionality that this option controlled has been removed.
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_managed(
|
||||
rocksdb_readoptions_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_readoptions_set_readahead_size(
|
||||
|
||||
@@ -195,6 +195,11 @@ class MergeOperator {
|
||||
// during a point lookup, thereby helping in limiting the number of levels to
|
||||
// read from.
|
||||
// Doesn't help with iterators.
|
||||
//
|
||||
// Note: the merge operands are passed to this function in the reversed order
|
||||
// relative to how they were merged (passed to FullMerge or FullMergeV2)
|
||||
// for performance reasons, see also:
|
||||
// https://github.com/facebook/rocksdb/issues/3865
|
||||
virtual bool ShouldMerge(const std::vector<Slice>& /*operands*/) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1064,11 +1064,8 @@ struct ReadOptions {
|
||||
// Not supported in ROCKSDB_LITE mode!
|
||||
bool tailing;
|
||||
|
||||
// Specify to create a managed iterator -- a special iterator that
|
||||
// uses less resources by having the ability to free its underlying
|
||||
// resources on request.
|
||||
// Default: false
|
||||
// Not supported in ROCKSDB_LITE mode!
|
||||
// This options is not used anymore. It was to turn on a functionality that
|
||||
// has been removed.
|
||||
bool managed;
|
||||
|
||||
// Enable a total order seek regardless of index format (e.g. hash index)
|
||||
|
||||
@@ -210,12 +210,20 @@ class LDBCommand {
|
||||
bool ParseStringOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, std::string* value);
|
||||
|
||||
/**
|
||||
* Returns the value of the specified option as a boolean.
|
||||
* default_val is used if the option is not found in options.
|
||||
* Throws an exception if the value of the option is not
|
||||
* "true" or "false" (case insensitive).
|
||||
*/
|
||||
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, bool default_val);
|
||||
|
||||
Options options_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
LDBOptions ldb_options_;
|
||||
|
||||
private:
|
||||
friend class WALDumperCommand;
|
||||
/**
|
||||
* Interpret command line options and flags to determine if the key
|
||||
* should be input/output in hex.
|
||||
@@ -230,15 +238,6 @@ class LDBCommand {
|
||||
bool IsValueHex(const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
/**
|
||||
* Returns the value of the specified option as a boolean.
|
||||
* default_val is used if the option is not found in options.
|
||||
* Throws an exception if the value of the option is not
|
||||
* "true" or "false" (case insensitive).
|
||||
*/
|
||||
bool ParseBooleanOption(const std::map<std::string, std::string>& options,
|
||||
const std::string& option, bool default_val);
|
||||
|
||||
/**
|
||||
* Converts val to a boolean.
|
||||
* val must be either true or false (case insensitive).
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 5
|
||||
#define ROCKSDB_MINOR 14
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 15
|
||||
#define ROCKSDB_PATCH 10
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
+51
-23
@@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.4)
|
||||
set(JNI_NATIVE_SOURCES
|
||||
rocksjni/backupablejni.cc
|
||||
rocksjni/backupenginejni.cc
|
||||
rocksjni/cassandra_compactionfilterjni.cc
|
||||
rocksjni/cassandra_value_operator.cc
|
||||
rocksjni/checkpoint.cc
|
||||
rocksjni/clock_cache.cc
|
||||
rocksjni/columnfamilyhandle.cc
|
||||
@@ -25,14 +27,14 @@ set(JNI_NATIVE_SOURCES
|
||||
rocksjni/memtablejni.cc
|
||||
rocksjni/merge_operator.cc
|
||||
rocksjni/native_comparator_wrapper_test.cc
|
||||
rocksjni/optimistic_transaction_db.cc
|
||||
rocksjni/optimistic_transaction_options.cc
|
||||
rocksjni/options.cc
|
||||
rocksjni/options_util.cc
|
||||
rocksjni/ratelimiterjni.cc
|
||||
rocksjni/remove_emptyvalue_compactionfilterjni.cc
|
||||
rocksjni/rocks_callback_object.cc
|
||||
rocksjni/cassandra_compactionfilterjni.cc
|
||||
rocksjni/cassandra_value_operator.cc
|
||||
rocksjni/restorejni.cc
|
||||
rocksjni/rocks_callback_object.cc
|
||||
rocksjni/rocksdb_exception_test.cc
|
||||
rocksjni/rocksjni.cc
|
||||
rocksjni/slice.cc
|
||||
@@ -42,28 +44,33 @@ set(JNI_NATIVE_SOURCES
|
||||
rocksjni/statistics.cc
|
||||
rocksjni/statisticsjni.cc
|
||||
rocksjni/table.cc
|
||||
rocksjni/transaction.cc
|
||||
rocksjni/transaction_db.cc
|
||||
rocksjni/transaction_db_options.cc
|
||||
rocksjni/transaction_log.cc
|
||||
rocksjni/transaction_notifier.cc
|
||||
rocksjni/transaction_notifier_jnicallback.cc
|
||||
rocksjni/transaction_options.cc
|
||||
rocksjni/ttl.cc
|
||||
rocksjni/write_batch.cc
|
||||
rocksjni/writebatchhandlerjnicallback.cc
|
||||
rocksjni/write_batch_test.cc
|
||||
rocksjni/write_batch_with_index.cc
|
||||
rocksjni/writebatchhandlerjnicallback.cc
|
||||
)
|
||||
|
||||
set(NATIVE_JAVA_CLASSES
|
||||
org.rocksdb.AbstractCompactionFilter
|
||||
org.rocksdb.AbstractCompactionFilterFactory
|
||||
org.rocksdb.AbstractComparator
|
||||
org.rocksdb.AbstractImmutableNativeReference
|
||||
org.rocksdb.AbstractNativeReference
|
||||
org.rocksdb.AbstractRocksIterator
|
||||
org.rocksdb.AbstractSlice
|
||||
org.rocksdb.AbstractWriteBatch
|
||||
org.rocksdb.AbstractTransactionNotifier
|
||||
org.rocksdb.BackupableDBOptions
|
||||
org.rocksdb.BackupEngine
|
||||
org.rocksdb.BackupEngineTest
|
||||
org.rocksdb.BlockBasedTableConfig
|
||||
org.rocksdb.BloomFilter
|
||||
org.rocksdb.Cache
|
||||
org.rocksdb.CassandraCompactionFilter
|
||||
org.rocksdb.CassandraValueMergeOperator
|
||||
org.rocksdb.Checkpoint
|
||||
@@ -88,10 +95,10 @@ set(NATIVE_JAVA_CLASSES
|
||||
org.rocksdb.Logger
|
||||
org.rocksdb.LRUCache
|
||||
org.rocksdb.MemTableConfig
|
||||
org.rocksdb.MergeOperator
|
||||
org.rocksdb.NativeComparatorWrapper
|
||||
org.rocksdb.NativeComparatorWrapperTest.NativeStringComparatorWrapper
|
||||
org.rocksdb.NativeLibraryLoader
|
||||
org.rocksdb.OptimisticTransactionDB
|
||||
org.rocksdb.OptimisticTransactionOptions
|
||||
org.rocksdb.Options
|
||||
org.rocksdb.OptionsUtil
|
||||
org.rocksdb.PlainTableConfig
|
||||
@@ -101,7 +108,6 @@ set(NATIVE_JAVA_CLASSES
|
||||
org.rocksdb.RestoreOptions
|
||||
org.rocksdb.RocksCallbackObject
|
||||
org.rocksdb.RocksDB
|
||||
org.rocksdb.RocksDBExceptionTest
|
||||
org.rocksdb.RocksEnv
|
||||
org.rocksdb.RocksIterator
|
||||
org.rocksdb.RocksIteratorInterface
|
||||
@@ -111,24 +117,29 @@ set(NATIVE_JAVA_CLASSES
|
||||
org.rocksdb.SkipListMemTableConfig
|
||||
org.rocksdb.Slice
|
||||
org.rocksdb.Snapshot
|
||||
org.rocksdb.SnapshotTest
|
||||
org.rocksdb.SstFileManager
|
||||
org.rocksdb.SstFileWriter
|
||||
org.rocksdb.Statistics
|
||||
org.rocksdb.StringAppendOperator
|
||||
org.rocksdb.TableFormatConfig
|
||||
org.rocksdb.Transaction
|
||||
org.rocksdb.TransactionDB
|
||||
org.rocksdb.TransactionDBOptions
|
||||
org.rocksdb.TransactionLogIterator
|
||||
org.rocksdb.TransactionOptions
|
||||
org.rocksdb.TtlDB
|
||||
org.rocksdb.VectorMemTableConfig
|
||||
org.rocksdb.WBWIRocksIterator
|
||||
org.rocksdb.WriteBatch
|
||||
org.rocksdb.WriteBatch.Handler
|
||||
org.rocksdb.WriteBatchTest
|
||||
org.rocksdb.WriteBatchTestInternalHelper
|
||||
org.rocksdb.WriteBatchInterface
|
||||
org.rocksdb.WriteBatchWithIndex
|
||||
org.rocksdb.WriteOptions
|
||||
org.rocksdb.util.CapturingWriteBatchHandler
|
||||
org.rocksdb.util.WriteBatchGetter
|
||||
org.rocksdb.NativeComparatorWrapperTest
|
||||
org.rocksdb.RocksDBExceptionTest
|
||||
org.rocksdb.SnapshotTest
|
||||
org.rocksdb.WriteBatchTest
|
||||
org.rocksdb.WriteBatchTestInternalHelper
|
||||
)
|
||||
|
||||
include(FindJava)
|
||||
@@ -150,13 +161,14 @@ set(JAVA_TESTCLASSPATH ${JAVA_JUNIT_JAR} ${JAVA_HAMCR_JAR} ${JAVA_MOCKITO_JAR} $
|
||||
add_jar(
|
||||
rocksdbjni_classes
|
||||
SOURCES
|
||||
src/main/java/org/rocksdb/AbstractCompactionFilter.java
|
||||
src/main/java/org/rocksdb/AbstractCompactionFilterFactory.java
|
||||
src/main/java/org/rocksdb/AbstractCompactionFilter.java
|
||||
src/main/java/org/rocksdb/AbstractComparator.java
|
||||
src/main/java/org/rocksdb/AbstractImmutableNativeReference.java
|
||||
src/main/java/org/rocksdb/AbstractNativeReference.java
|
||||
src/main/java/org/rocksdb/AbstractRocksIterator.java
|
||||
src/main/java/org/rocksdb/AbstractSlice.java
|
||||
src/main/java/org/rocksdb/AbstractTransactionNotifier.java
|
||||
src/main/java/org/rocksdb/AbstractWriteBatch.java
|
||||
src/main/java/org/rocksdb/AccessHint.java
|
||||
src/main/java/org/rocksdb/AdvancedColumnFamilyOptionsInterface.java
|
||||
@@ -175,8 +187,8 @@ add_jar(
|
||||
src/main/java/org/rocksdb/ClockCache.java
|
||||
src/main/java/org/rocksdb/ColumnFamilyDescriptor.java
|
||||
src/main/java/org/rocksdb/ColumnFamilyHandle.java
|
||||
src/main/java/org/rocksdb/ColumnFamilyOptions.java
|
||||
src/main/java/org/rocksdb/ColumnFamilyOptionsInterface.java
|
||||
src/main/java/org/rocksdb/ColumnFamilyOptions.java
|
||||
src/main/java/org/rocksdb/CompactionOptionsFIFO.java
|
||||
src/main/java/org/rocksdb/CompactionOptionsUniversal.java
|
||||
src/main/java/org/rocksdb/CompactionPriority.java
|
||||
@@ -187,8 +199,8 @@ add_jar(
|
||||
src/main/java/org/rocksdb/ComparatorType.java
|
||||
src/main/java/org/rocksdb/CompressionOptions.java
|
||||
src/main/java/org/rocksdb/CompressionType.java
|
||||
src/main/java/org/rocksdb/DBOptions.java
|
||||
src/main/java/org/rocksdb/DBOptionsInterface.java
|
||||
src/main/java/org/rocksdb/DBOptions.java
|
||||
src/main/java/org/rocksdb/DbPath.java
|
||||
src/main/java/org/rocksdb/DirectComparator.java
|
||||
src/main/java/org/rocksdb/DirectSlice.java
|
||||
@@ -209,10 +221,12 @@ add_jar(
|
||||
src/main/java/org/rocksdb/LRUCache.java
|
||||
src/main/java/org/rocksdb/MemTableConfig.java
|
||||
src/main/java/org/rocksdb/MergeOperator.java
|
||||
src/main/java/org/rocksdb/MutableColumnFamilyOptions.java
|
||||
src/main/java/org/rocksdb/MutableColumnFamilyOptionsInterface.java
|
||||
src/main/java/org/rocksdb/MutableColumnFamilyOptions.java
|
||||
src/main/java/org/rocksdb/NativeComparatorWrapper.java
|
||||
src/main/java/org/rocksdb/NativeLibraryLoader.java
|
||||
src/main/java/org/rocksdb/OptimisticTransactionDB.java
|
||||
src/main/java/org/rocksdb/OptimisticTransactionOptions.java
|
||||
src/main/java/org/rocksdb/Options.java
|
||||
src/main/java/org/rocksdb/OptionsUtil.java
|
||||
src/main/java/org/rocksdb/PlainTableConfig.java
|
||||
@@ -223,11 +237,11 @@ add_jar(
|
||||
src/main/java/org/rocksdb/RemoveEmptyValueCompactionFilter.java
|
||||
src/main/java/org/rocksdb/RestoreOptions.java
|
||||
src/main/java/org/rocksdb/RocksCallbackObject.java
|
||||
src/main/java/org/rocksdb/RocksDB.java
|
||||
src/main/java/org/rocksdb/RocksDBException.java
|
||||
src/main/java/org/rocksdb/RocksDB.java
|
||||
src/main/java/org/rocksdb/RocksEnv.java
|
||||
src/main/java/org/rocksdb/RocksIterator.java
|
||||
src/main/java/org/rocksdb/RocksIteratorInterface.java
|
||||
src/main/java/org/rocksdb/RocksIterator.java
|
||||
src/main/java/org/rocksdb/RocksMemEnv.java
|
||||
src/main/java/org/rocksdb/RocksMutableObject.java
|
||||
src/main/java/org/rocksdb/RocksObject.java
|
||||
@@ -236,22 +250,36 @@ add_jar(
|
||||
src/main/java/org/rocksdb/Snapshot.java
|
||||
src/main/java/org/rocksdb/SstFileManager.java
|
||||
src/main/java/org/rocksdb/SstFileWriter.java
|
||||
src/main/java/org/rocksdb/StatisticsCollectorCallback.java
|
||||
src/main/java/org/rocksdb/StatisticsCollector.java
|
||||
src/main/java/org/rocksdb/Statistics.java
|
||||
src/main/java/org/rocksdb/StatsCollectorInput.java
|
||||
src/main/java/org/rocksdb/StatsLevel.java
|
||||
src/main/java/org/rocksdb/Status.java
|
||||
src/main/java/org/rocksdb/StringAppendOperator.java
|
||||
src/main/java/org/rocksdb/TableFormatConfig.java
|
||||
src/main/java/org/rocksdb/TickerType.java
|
||||
src/main/java/org/rocksdb/TransactionalDB.java
|
||||
src/main/java/org/rocksdb/TransactionalOptions.java
|
||||
src/main/java/org/rocksdb/TransactionDB.java
|
||||
src/main/java/org/rocksdb/TransactionDBOptions.java
|
||||
src/main/java/org/rocksdb/Transaction.java
|
||||
src/main/java/org/rocksdb/TransactionLogIterator.java
|
||||
src/main/java/org/rocksdb/TransactionOptions.java
|
||||
src/main/java/org/rocksdb/TtlDB.java
|
||||
src/main/java/org/rocksdb/util/Environment.java
|
||||
src/main/java/org/rocksdb/TxnDBWritePolicy.java
|
||||
src/main/java/org/rocksdb/VectorMemTableConfig.java
|
||||
src/main/java/org/rocksdb/WALRecoveryMode.java
|
||||
src/main/java/org/rocksdb/WBWIRocksIterator.java
|
||||
src/main/java/org/rocksdb/WriteBatch.java
|
||||
src/main/java/org/rocksdb/WriteBatchInterface.java
|
||||
src/main/java/org/rocksdb/WriteBatch.java
|
||||
src/main/java/org/rocksdb/WriteBatchWithIndex.java
|
||||
src/main/java/org/rocksdb/WriteOptions.java
|
||||
src/main/java/org/rocksdb/util/BytewiseComparator.java
|
||||
src/main/java/org/rocksdb/util/DirectBytewiseComparator.java
|
||||
src/main/java/org/rocksdb/util/Environment.java
|
||||
src/main/java/org/rocksdb/util/ReverseBytewiseComparator.java
|
||||
src/main/java/org/rocksdb/util/SizeUnit.java
|
||||
src/test/java/org/rocksdb/BackupEngineTest.java
|
||||
src/test/java/org/rocksdb/IngestExternalFileOptionsTest.java
|
||||
src/test/java/org/rocksdb/NativeComparatorWrapperTest.java
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4503) // identifier' : decorated name length exceeded, name was truncated
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_Transaction
|
||||
* Method: setSnapshot
|
||||
@@ -264,15 +269,14 @@ typedef std::function<std::vector<rocksdb::Status>(
|
||||
std::vector<std::string>*)>
|
||||
FnMultiGet;
|
||||
|
||||
void free_key_parts(
|
||||
void free_parts(
|
||||
JNIEnv* env,
|
||||
std::vector<std::tuple<jbyteArray, jbyte*, jobject>> key_parts_to_free) {
|
||||
for (std::vector<std::tuple<jbyteArray, jbyte*, jobject>>::size_type i = 0;
|
||||
i < key_parts_to_free.size(); i++) {
|
||||
std::vector<std::tuple<jbyteArray, jbyte*, jobject>> &parts_to_free) {
|
||||
for (auto &value : parts_to_free) {
|
||||
jobject jk;
|
||||
jbyteArray jk_ba;
|
||||
jbyte* jk_val;
|
||||
std::tie(jk_ba, jk_val, jk) = key_parts_to_free[i];
|
||||
std::tie(jk_ba, jk_val, jk) = value;
|
||||
env->ReleaseByteArrayElements(jk_ba, jk_val, JNI_ABORT);
|
||||
env->DeleteLocalRef(jk);
|
||||
}
|
||||
@@ -295,7 +299,7 @@ jobjectArray txn_multi_get_helper(JNIEnv* env, const FnMultiGet& fn_multi_get,
|
||||
const jobject jk = env->GetObjectArrayElement(jkey_parts, i);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
free_key_parts(env, key_parts_to_free);
|
||||
free_parts(env, key_parts_to_free);
|
||||
return nullptr;
|
||||
}
|
||||
jbyteArray jk_ba = reinterpret_cast<jbyteArray>(jk);
|
||||
@@ -303,14 +307,14 @@ jobjectArray txn_multi_get_helper(JNIEnv* env, const FnMultiGet& fn_multi_get,
|
||||
if (env->EnsureLocalCapacity(len_key) != 0) {
|
||||
// out of memory
|
||||
env->DeleteLocalRef(jk);
|
||||
free_key_parts(env, key_parts_to_free);
|
||||
free_parts(env, key_parts_to_free);
|
||||
return nullptr;
|
||||
}
|
||||
jbyte* jk_val = env->GetByteArrayElements(jk_ba, nullptr);
|
||||
if (jk_val == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->DeleteLocalRef(jk);
|
||||
free_key_parts(env, key_parts_to_free);
|
||||
free_parts(env, key_parts_to_free);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -327,7 +331,7 @@ jobjectArray txn_multi_get_helper(JNIEnv* env, const FnMultiGet& fn_multi_get,
|
||||
fn_multi_get(*read_options, key_parts, &value_parts);
|
||||
|
||||
// free up allocated byte arrays
|
||||
free_key_parts(env, key_parts_to_free);
|
||||
free_parts(env, key_parts_to_free);
|
||||
|
||||
// prepare the results
|
||||
const jclass jcls_ba = env->FindClass("[B");
|
||||
@@ -600,28 +604,6 @@ typedef std::function<rocksdb::Status(const rocksdb::SliceParts&,
|
||||
const rocksdb::SliceParts&)>
|
||||
FnWriteKVParts;
|
||||
|
||||
void free_key_value_parts(
|
||||
JNIEnv* env, const int32_t len,
|
||||
std::tuple<jbyteArray, jbyte*, jobject> jkey_parts_to_free[],
|
||||
std::tuple<jbyteArray, jbyte*, jobject> jvalue_parts_to_free[]) {
|
||||
for (int32_t i = len - 1; i >= 0; --i) {
|
||||
jbyteArray jba_value_part;
|
||||
jbyte* jvalue_part;
|
||||
jobject jobj_value_part;
|
||||
std::tie(jba_value_part, jvalue_part, jobj_value_part) =
|
||||
jvalue_parts_to_free[i];
|
||||
env->ReleaseByteArrayElements(jba_value_part, jvalue_part, JNI_ABORT);
|
||||
env->DeleteLocalRef(jobj_value_part);
|
||||
|
||||
jbyteArray jba_key_part;
|
||||
jbyte* jkey_part;
|
||||
jobject jobj_key_part;
|
||||
std::tie(jba_key_part, jkey_part, jobj_key_part) = jkey_parts_to_free[i];
|
||||
env->ReleaseByteArrayElements(jba_key_part, jkey_part, JNI_ABORT);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(AR) consider refactoring to share this between here and rocksjni.cc
|
||||
void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
const FnWriteKVParts& fn_write_kv_parts,
|
||||
@@ -629,29 +611,29 @@ void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
const jint& jkey_parts_len,
|
||||
const jobjectArray& jvalue_parts,
|
||||
const jint& jvalue_parts_len) {
|
||||
#ifndef DEBUG
|
||||
(void) jvalue_parts_len;
|
||||
#else
|
||||
assert(jkey_parts_len == jvalue_parts_len);
|
||||
#endif
|
||||
|
||||
rocksdb::Slice key_parts[jkey_parts_len];
|
||||
rocksdb::Slice value_parts[jvalue_parts_len];
|
||||
std::tuple<jbyteArray, jbyte*, jobject> jkey_parts_to_free[jkey_parts_len];
|
||||
std::tuple<jbyteArray, jbyte*, jobject>
|
||||
jvalue_parts_to_free[jvalue_parts_len];
|
||||
auto key_parts = std::vector<rocksdb::Slice>();
|
||||
auto value_parts = std::vector<rocksdb::Slice>();
|
||||
auto jparts_to_free = std::vector<std::tuple<jbyteArray, jbyte*, jobject>>();
|
||||
|
||||
// convert java key_parts/value_parts byte[][] to Slice(s)
|
||||
for (jsize i = 0; i < jkey_parts_len; ++i) {
|
||||
const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
const jobject jobj_value_part = env->GetObjectArrayElement(jvalue_parts, i);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -661,8 +643,7 @@ void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
// out of memory
|
||||
env->DeleteLocalRef(jobj_value_part);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr);
|
||||
@@ -670,8 +651,7 @@ void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->DeleteLocalRef(jobj_value_part);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -682,8 +662,7 @@ void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
// out of memory
|
||||
env->DeleteLocalRef(jobj_value_part);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
jbyte* jvalue_part = env->GetByteArrayElements(jba_value_part, nullptr);
|
||||
@@ -692,30 +671,28 @@ void txn_write_kv_parts_helper(JNIEnv* env,
|
||||
env->ReleaseByteArrayElements(jba_value_part, jvalue_part, JNI_ABORT);
|
||||
env->DeleteLocalRef(jobj_value_part);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
return;
|
||||
}
|
||||
|
||||
jkey_parts_to_free[i] = std::tuple<jbyteArray, jbyte*, jobject>(
|
||||
jba_key_part, jkey_part, jobj_key_part);
|
||||
jvalue_parts_to_free[i] = std::tuple<jbyteArray, jbyte*, jobject>(
|
||||
jba_value_part, jvalue_part, jobj_value_part);
|
||||
jparts_to_free.push_back(std::make_tuple(
|
||||
jba_key_part, jkey_part, jobj_key_part));
|
||||
jparts_to_free.push_back(std::make_tuple(
|
||||
jba_value_part, jvalue_part, jobj_value_part));
|
||||
|
||||
key_parts[i] =
|
||||
rocksdb::Slice(reinterpret_cast<char*>(jkey_part), jkey_part_len);
|
||||
value_parts[i] =
|
||||
rocksdb::Slice(reinterpret_cast<char*>(jvalue_part), jvalue_part_len);
|
||||
key_parts.push_back(
|
||||
rocksdb::Slice(reinterpret_cast<char*>(jkey_part), jkey_part_len));
|
||||
value_parts.push_back(
|
||||
rocksdb::Slice(reinterpret_cast<char*>(jvalue_part), jvalue_part_len));
|
||||
}
|
||||
|
||||
// call the write_multi function
|
||||
rocksdb::Status s =
|
||||
fn_write_kv_parts(rocksdb::SliceParts(key_parts, jkey_parts_len),
|
||||
rocksdb::SliceParts(value_parts, jvalue_parts_len));
|
||||
rocksdb::Status s = fn_write_kv_parts(
|
||||
rocksdb::SliceParts(key_parts.data(), (int)key_parts.size()),
|
||||
rocksdb::SliceParts(value_parts.data(), (int)value_parts.size()));
|
||||
|
||||
// cleanup temporary memory
|
||||
free_key_value_parts(env, jkey_parts_len, jkey_parts_to_free,
|
||||
jvalue_parts_to_free);
|
||||
free_parts(env, jparts_to_free);
|
||||
|
||||
// return
|
||||
if (s.ok()) {
|
||||
@@ -857,33 +834,22 @@ void Java_org_rocksdb_Transaction_delete__J_3BI(JNIEnv* env, jobject /*jobj*/,
|
||||
typedef std::function<rocksdb::Status(const rocksdb::SliceParts&)>
|
||||
FnWriteKParts;
|
||||
|
||||
void free_key_parts(
|
||||
JNIEnv* env, const int32_t len,
|
||||
std::tuple<jbyteArray, jbyte*, jobject> jkey_parts_to_free[]) {
|
||||
for (int32_t i = len - 1; i >= 0; --i) {
|
||||
jbyteArray jba_key_part;
|
||||
jbyte* jkey;
|
||||
jobject jobj_key_part;
|
||||
std::tie(jba_key_part, jkey, jobj_key_part) = jkey_parts_to_free[i];
|
||||
env->ReleaseByteArrayElements(jba_key_part, jkey, JNI_ABORT);
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(AR) consider refactoring to share this between here and rocksjni.cc
|
||||
void txn_write_k_parts_helper(JNIEnv* env,
|
||||
const FnWriteKParts& fn_write_k_parts,
|
||||
const jobjectArray& jkey_parts,
|
||||
const jint& jkey_parts_len) {
|
||||
rocksdb::Slice key_parts[jkey_parts_len];
|
||||
std::tuple<jbyteArray, jbyte*, jobject> jkey_parts_to_free[jkey_parts_len];
|
||||
|
||||
std::vector<rocksdb::Slice> key_parts;
|
||||
std::vector<std::tuple<jbyteArray, jbyte*, jobject>> jkey_parts_to_free;
|
||||
|
||||
// convert java key_parts byte[][] to Slice(s)
|
||||
for (jint i = 0; i < jkey_parts_len; ++i) {
|
||||
const jobject jobj_key_part = env->GetObjectArrayElement(jkey_parts, i);
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
free_key_parts(env, jkey_parts_len, jkey_parts_to_free);
|
||||
free_parts(env, jkey_parts_to_free);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -892,30 +858,29 @@ void txn_write_k_parts_helper(JNIEnv* env,
|
||||
if (env->EnsureLocalCapacity(jkey_part_len) != 0) {
|
||||
// out of memory
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_parts(env, jkey_parts_len, jkey_parts_to_free);
|
||||
free_parts(env, jkey_parts_to_free);
|
||||
return;
|
||||
}
|
||||
jbyte* jkey_part = env->GetByteArrayElements(jba_key_part, nullptr);
|
||||
if (jkey_part == nullptr) {
|
||||
// exception thrown: OutOfMemoryError
|
||||
env->DeleteLocalRef(jobj_key_part);
|
||||
free_key_parts(env, jkey_parts_len, jkey_parts_to_free);
|
||||
free_parts(env, jkey_parts_to_free);
|
||||
return;
|
||||
}
|
||||
|
||||
jkey_parts_to_free[i] = std::tuple<jbyteArray, jbyte*, jobject>(
|
||||
jba_key_part, jkey_part, jobj_key_part);
|
||||
jkey_parts_to_free.push_back(std::tuple<jbyteArray, jbyte*, jobject>(
|
||||
jba_key_part, jkey_part, jobj_key_part));
|
||||
|
||||
key_parts[i] =
|
||||
rocksdb::Slice(reinterpret_cast<char*>(jkey_part), jkey_part_len);
|
||||
key_parts.push_back(rocksdb::Slice(reinterpret_cast<char*>(jkey_part), jkey_part_len));
|
||||
}
|
||||
|
||||
// call the write_multi function
|
||||
rocksdb::Status s =
|
||||
fn_write_k_parts(rocksdb::SliceParts(key_parts, jkey_parts_len));
|
||||
fn_write_k_parts(rocksdb::SliceParts(key_parts.data(), (int)key_parts.size()));
|
||||
|
||||
// cleanup temporary memory
|
||||
free_key_parts(env, jkey_parts_len, jkey_parts_to_free);
|
||||
free_parts(env, jkey_parts_to_free);
|
||||
|
||||
// return
|
||||
if (s.ok()) {
|
||||
@@ -1582,7 +1547,7 @@ jbyte Java_org_rocksdb_Transaction_getState(JNIEnv* /*env*/, jobject /*jobj*/,
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return 0xFF;
|
||||
return static_cast<jbyte>(-1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -269,7 +269,7 @@ jlongArray Java_org_rocksdb_TransactionDB_getAllPreparedTransactions(
|
||||
assert(size < UINT32_MAX); // does it fit in a jint?
|
||||
|
||||
const jsize len = static_cast<jsize>(size);
|
||||
jlong tmp[len];
|
||||
std::vector<jlong> tmp(len);
|
||||
for (jsize i = 0; i < len; ++i) {
|
||||
tmp[i] = reinterpret_cast<jlong>(txns[i]);
|
||||
}
|
||||
@@ -279,7 +279,7 @@ jlongArray Java_org_rocksdb_TransactionDB_getAllPreparedTransactions(
|
||||
// exception thrown: OutOfMemoryError
|
||||
return nullptr;
|
||||
}
|
||||
env->SetLongArrayRegion(jtxns, 0, len, tmp);
|
||||
env->SetLongArrayRegion(jtxns, 0, len, tmp.data());
|
||||
if (env->ExceptionCheck()) {
|
||||
// exception thrown: ArrayIndexOutOfBoundsException
|
||||
env->DeleteLocalRef(jtxns);
|
||||
|
||||
@@ -306,7 +306,11 @@ rocksdb::Status WriteBatchHandlerJniCallback::PutBlobIndexCF(uint32_t column_fam
|
||||
}
|
||||
|
||||
rocksdb::Status WriteBatchHandlerJniCallback::MarkBeginPrepare(bool unprepare) {
|
||||
#ifndef DEBUG
|
||||
(void) unprepare;
|
||||
#else
|
||||
assert(!unprepare);
|
||||
#endif
|
||||
m_env->CallVoidMethod(m_jcallback_obj, m_jMarkBeginPrepareMethodId);
|
||||
|
||||
// check for Exception, in-particular RocksDBException
|
||||
|
||||
@@ -688,7 +688,7 @@ public abstract class AbstractTransactionTest {
|
||||
final long preStartTxnTime = System.currentTimeMillis();
|
||||
try(final DBContainer dbContainer = startDb();
|
||||
final Transaction txn = dbContainer.beginTransaction()) {
|
||||
Thread.sleep(1);
|
||||
Thread.sleep(2);
|
||||
|
||||
final long txnElapsedTime = txn.getElapsedTime();
|
||||
assertThat(txnElapsedTime).isLessThan(System.currentTimeMillis()
|
||||
|
||||
+29
-1
@@ -75,7 +75,6 @@ ImmutableCFOptions::ImmutableCFOptions(const ImmutableDBOptions& db_options,
|
||||
max_subcompactions(db_options.max_subcompactions),
|
||||
memtable_insert_with_hint_prefix_extractor(
|
||||
cf_options.memtable_insert_with_hint_prefix_extractor.get()),
|
||||
ttl(cf_options.ttl),
|
||||
cf_paths(cf_options.cf_paths) {}
|
||||
|
||||
// Multiple two operands. If they overflow, return op1.
|
||||
@@ -168,6 +167,8 @@ void MutableCFOptions::Dump(Logger* log) const {
|
||||
max_bytes_for_level_base);
|
||||
ROCKS_LOG_INFO(log, " max_bytes_for_level_multiplier: %f",
|
||||
max_bytes_for_level_multiplier);
|
||||
ROCKS_LOG_INFO(log, " ttl: %" PRIu64,
|
||||
ttl);
|
||||
std::string result;
|
||||
char buf[10];
|
||||
for (const auto m : max_bytes_for_level_multiplier_additional) {
|
||||
@@ -190,6 +191,33 @@ void MutableCFOptions::Dump(Logger* log) const {
|
||||
report_bg_io_stats);
|
||||
ROCKS_LOG_INFO(log, " compression: %d",
|
||||
static_cast<int>(compression));
|
||||
|
||||
// Universal Compaction Options
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.size_ratio : %d",
|
||||
compaction_options_universal.size_ratio);
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.min_merge_width : %d",
|
||||
compaction_options_universal.min_merge_width);
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.max_merge_width : %d",
|
||||
compaction_options_universal.max_merge_width);
|
||||
ROCKS_LOG_INFO(
|
||||
log, "compaction_options_universal.max_size_amplification_percent : %d",
|
||||
compaction_options_universal.max_size_amplification_percent);
|
||||
ROCKS_LOG_INFO(log,
|
||||
"compaction_options_universal.compression_size_percent : %d",
|
||||
compaction_options_universal.compression_size_percent);
|
||||
ROCKS_LOG_INFO(log, "compaction_options_universal.stop_style : %d",
|
||||
compaction_options_universal.stop_style);
|
||||
ROCKS_LOG_INFO(
|
||||
log, "compaction_options_universal.allow_trivial_move : %d",
|
||||
static_cast<int>(compaction_options_universal.allow_trivial_move));
|
||||
|
||||
// FIFO Compaction Options
|
||||
ROCKS_LOG_INFO(log, "compaction_options_fifo.max_table_files_size : %" PRIu64,
|
||||
compaction_options_fifo.max_table_files_size);
|
||||
ROCKS_LOG_INFO(log, "compaction_options_fifo.ttl : %" PRIu64,
|
||||
compaction_options_fifo.ttl);
|
||||
ROCKS_LOG_INFO(log, "compaction_options_fifo.allow_compaction : %d",
|
||||
compaction_options_fifo.allow_compaction);
|
||||
}
|
||||
|
||||
MutableCFOptions::MutableCFOptions(const Options& options)
|
||||
|
||||
@@ -119,8 +119,6 @@ struct ImmutableCFOptions {
|
||||
|
||||
const SliceTransform* memtable_insert_with_hint_prefix_extractor;
|
||||
|
||||
uint64_t ttl;
|
||||
|
||||
std::vector<DbPath> cf_paths;
|
||||
};
|
||||
|
||||
@@ -149,6 +147,7 @@ struct MutableCFOptions {
|
||||
target_file_size_multiplier(options.target_file_size_multiplier),
|
||||
max_bytes_for_level_base(options.max_bytes_for_level_base),
|
||||
max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
|
||||
ttl(options.ttl),
|
||||
max_bytes_for_level_multiplier_additional(
|
||||
options.max_bytes_for_level_multiplier_additional),
|
||||
compaction_options_fifo(options.compaction_options_fifo),
|
||||
@@ -181,6 +180,7 @@ struct MutableCFOptions {
|
||||
target_file_size_multiplier(0),
|
||||
max_bytes_for_level_base(0),
|
||||
max_bytes_for_level_multiplier(0),
|
||||
ttl(0),
|
||||
compaction_options_fifo(),
|
||||
max_sequential_skip_in_iterations(0),
|
||||
paranoid_file_checks(false),
|
||||
@@ -228,6 +228,7 @@ struct MutableCFOptions {
|
||||
int target_file_size_multiplier;
|
||||
uint64_t max_bytes_for_level_base;
|
||||
double max_bytes_for_level_multiplier;
|
||||
uint64_t ttl;
|
||||
std::vector<int> max_bytes_for_level_multiplier_additional;
|
||||
CompactionOptionsFIFO compaction_options_fifo;
|
||||
CompactionOptionsUniversal compaction_options_universal;
|
||||
|
||||
@@ -168,6 +168,7 @@ ColumnFamilyOptions BuildColumnFamilyOptions(
|
||||
mutable_cf_options.max_bytes_for_level_base;
|
||||
cf_opts.max_bytes_for_level_multiplier =
|
||||
mutable_cf_options.max_bytes_for_level_multiplier;
|
||||
cf_opts.ttl = mutable_cf_options.ttl;
|
||||
|
||||
cf_opts.max_bytes_for_level_multiplier_additional.clear();
|
||||
for (auto value :
|
||||
@@ -1867,7 +1868,8 @@ std::unordered_map<std::string, OptionTypeInfo>
|
||||
offsetof(struct MutableCFOptions, compaction_options_universal)}},
|
||||
{"ttl",
|
||||
{offset_of(&ColumnFamilyOptions::ttl), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, false, 0}}};
|
||||
OptionVerificationType::kNormal, true,
|
||||
offsetof(struct MutableCFOptions, ttl)}}};
|
||||
|
||||
std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionsHelper::fifo_compaction_options_type_info = {
|
||||
|
||||
@@ -37,10 +37,14 @@ ZSTD_customMem GetJeZstdAllocationOverrides() {
|
||||
// Global operators to be replaced by a linker when this file is
|
||||
// a part of the build
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
void* jemalloc_aligned_alloc(size_t size, size_t alignment) ROCKSDB_NOEXCEPT {
|
||||
return je_aligned_alloc(alignment, size);
|
||||
}
|
||||
void jemalloc_aligned_free(void* p) ROCKSDB_NOEXCEPT { je_free(p); }
|
||||
} // port
|
||||
} // rocksdb
|
||||
|
||||
void* operator new(size_t size) {
|
||||
void* p = je_malloc(size);
|
||||
|
||||
@@ -38,7 +38,6 @@ LIB_SOURCES = \
|
||||
db/log_reader.cc \
|
||||
db/log_writer.cc \
|
||||
db/malloc_stats.cc \
|
||||
db/managed_iterator.cc \
|
||||
db/memtable.cc \
|
||||
db/memtable_list.cc \
|
||||
db/merge_helper.cc \
|
||||
@@ -164,7 +163,6 @@ LIB_SOURCES = \
|
||||
utilities/blob_db/blob_log_format.cc \
|
||||
utilities/blob_db/blob_log_reader.cc \
|
||||
utilities/blob_db/blob_log_writer.cc \
|
||||
utilities/blob_db/ttl_extractor.cc \
|
||||
utilities/cassandra/cassandra_compaction_filter.cc \
|
||||
utilities/cassandra/format.cc \
|
||||
utilities/cassandra/merge_operator.cc \
|
||||
|
||||
+99
-26
@@ -56,12 +56,40 @@ static inline const char* DecodeEntry(const char* p, const char* limit,
|
||||
return p;
|
||||
}
|
||||
|
||||
void BlockIter::Next() {
|
||||
void DataBlockIter::Next() {
|
||||
assert(Valid());
|
||||
ParseNextKey();
|
||||
ParseNextDataKey();
|
||||
}
|
||||
|
||||
void BlockIter::Prev() {
|
||||
void IndexBlockIter::Next() {
|
||||
assert(Valid());
|
||||
ParseNextIndexKey();
|
||||
}
|
||||
|
||||
void IndexBlockIter::Prev() {
|
||||
assert(Valid());
|
||||
// Scan backwards to a restart point before current_
|
||||
const uint32_t original = current_;
|
||||
while (GetRestartPoint(restart_index_) >= original) {
|
||||
if (restart_index_ == 0) {
|
||||
// No more entries
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
return;
|
||||
}
|
||||
restart_index_--;
|
||||
}
|
||||
SeekToRestartPoint(restart_index_);
|
||||
do {
|
||||
if (!ParseNextIndexKey()) {
|
||||
break;
|
||||
}
|
||||
// Loop until end of current entry hits the start of original entry
|
||||
} while (NextEntryOffset() < original);
|
||||
}
|
||||
|
||||
// Similar to IndexBlockIter::Prev but also caches the prev entries
|
||||
void DataBlockIter::Prev() {
|
||||
assert(Valid());
|
||||
|
||||
assert(prev_entries_idx_ == -1 ||
|
||||
@@ -87,11 +115,7 @@ void BlockIter::Prev() {
|
||||
const Slice current_key(key_ptr, current_prev_entry.key_size);
|
||||
|
||||
current_ = current_prev_entry.offset;
|
||||
if (key_includes_seq_) {
|
||||
key_.SetInternalKey(current_key, false /* copy */);
|
||||
} else {
|
||||
key_.SetUserKey(current_key, false /* copy */);
|
||||
}
|
||||
key_.SetKey(current_key, false /* copy */);
|
||||
value_ = current_prev_entry.value;
|
||||
|
||||
return;
|
||||
@@ -117,7 +141,7 @@ void BlockIter::Prev() {
|
||||
SeekToRestartPoint(restart_index_);
|
||||
|
||||
do {
|
||||
if (!ParseNextKey()) {
|
||||
if (!ParseNextDataKey()) {
|
||||
break;
|
||||
}
|
||||
Slice current_key = key();
|
||||
@@ -155,7 +179,7 @@ void DataBlockIter::Seek(const Slice& target) {
|
||||
// Linear search (within restart block) for first key >= target
|
||||
|
||||
while (true) {
|
||||
if (!ParseNextKey() || Compare(key_, seek_key) >= 0) {
|
||||
if (!ParseNextDataKey() || Compare(key_, seek_key) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -175,8 +199,7 @@ void IndexBlockIter::Seek(const Slice& target) {
|
||||
if (prefix_index_) {
|
||||
ok = PrefixSeek(target, &index);
|
||||
} else {
|
||||
ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index,
|
||||
key_includes_seq_ ? comparator_ : user_comparator_);
|
||||
ok = BinarySeek(seek_key, 0, num_restarts_ - 1, &index, active_comparator_);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
@@ -186,7 +209,7 @@ void IndexBlockIter::Seek(const Slice& target) {
|
||||
// Linear search (within restart block) for first key >= target
|
||||
|
||||
while (true) {
|
||||
if (!ParseNextKey() || Compare(key_, seek_key) >= 0) {
|
||||
if (!ParseNextIndexKey() || Compare(key_, seek_key) >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -207,7 +230,7 @@ void DataBlockIter::SeekForPrev(const Slice& target) {
|
||||
SeekToRestartPoint(index);
|
||||
// Linear search (within restart block) for first key >= seek_key
|
||||
|
||||
while (ParseNextKey() && Compare(key_, seek_key) < 0) {
|
||||
while (ParseNextDataKey() && Compare(key_, seek_key) < 0) {
|
||||
}
|
||||
if (!Valid()) {
|
||||
SeekToLast();
|
||||
@@ -218,20 +241,38 @@ void DataBlockIter::SeekForPrev(const Slice& target) {
|
||||
}
|
||||
}
|
||||
|
||||
void BlockIter::SeekToFirst() {
|
||||
void DataBlockIter::SeekToFirst() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(0);
|
||||
ParseNextKey();
|
||||
ParseNextDataKey();
|
||||
}
|
||||
|
||||
void BlockIter::SeekToLast() {
|
||||
void IndexBlockIter::SeekToFirst() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(0);
|
||||
ParseNextIndexKey();
|
||||
}
|
||||
|
||||
void DataBlockIter::SeekToLast() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(num_restarts_ - 1);
|
||||
while (ParseNextKey() && NextEntryOffset() < restarts_) {
|
||||
while (ParseNextDataKey() && NextEntryOffset() < restarts_) {
|
||||
// Keep skipping
|
||||
}
|
||||
}
|
||||
|
||||
void IndexBlockIter::SeekToLast() {
|
||||
if (data_ == nullptr) { // Not init yet
|
||||
return;
|
||||
}
|
||||
SeekToRestartPoint(num_restarts_ - 1);
|
||||
while (ParseNextIndexKey() && NextEntryOffset() < restarts_) {
|
||||
// Keep skipping
|
||||
}
|
||||
}
|
||||
@@ -244,7 +285,7 @@ void BlockIter::CorruptionError() {
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
bool BlockIter::ParseNextKey() {
|
||||
bool DataBlockIter::ParseNextDataKey() {
|
||||
current_ = NextEntryOffset();
|
||||
const char* p = data_ + current_;
|
||||
const char* limit = data_ + restarts_; // Restarts come right after data
|
||||
@@ -265,11 +306,7 @@ bool BlockIter::ParseNextKey() {
|
||||
if (shared == 0) {
|
||||
// If this key dont share any bytes with prev key then we dont need
|
||||
// to decode it and can use it's address in the block directly.
|
||||
if (key_includes_seq_) {
|
||||
key_.SetInternalKey(Slice(p, non_shared), false /* copy */);
|
||||
} else {
|
||||
key_.SetUserKey(Slice(p, non_shared), false /* copy */);
|
||||
}
|
||||
key_.SetKey(Slice(p, non_shared), false /* copy */);
|
||||
key_pinned_ = true;
|
||||
} else {
|
||||
// This key share `shared` bytes with prev key, we need to decode it
|
||||
@@ -283,7 +320,7 @@ bool BlockIter::ParseNextKey() {
|
||||
// type is kTypeValue, kTypeMerge, kTypeDeletion, or kTypeRangeDeletion.
|
||||
assert(GetInternalKeySeqno(key_.GetInternalKey()) == 0);
|
||||
|
||||
ValueType value_type = ExtractValueType(key_.GetInternalKey());
|
||||
ValueType value_type = ExtractValueType(key_.GetKey());
|
||||
assert(value_type == ValueType::kTypeValue ||
|
||||
value_type == ValueType::kTypeMerge ||
|
||||
value_type == ValueType::kTypeDeletion ||
|
||||
@@ -311,6 +348,42 @@ bool BlockIter::ParseNextKey() {
|
||||
}
|
||||
}
|
||||
|
||||
bool IndexBlockIter::ParseNextIndexKey() {
|
||||
current_ = NextEntryOffset();
|
||||
const char* p = data_ + current_;
|
||||
const char* limit = data_ + restarts_; // Restarts come right after data
|
||||
if (p >= limit) {
|
||||
// No more entries to return. Mark as invalid.
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode next entry
|
||||
uint32_t shared, non_shared, value_length;
|
||||
p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);
|
||||
if (p == nullptr || key_.Size() < shared) {
|
||||
CorruptionError();
|
||||
return false;
|
||||
}
|
||||
if (shared == 0) {
|
||||
// If this key dont share any bytes with prev key then we dont need
|
||||
// to decode it and can use it's address in the block directly.
|
||||
key_.SetKey(Slice(p, non_shared), false /* copy */);
|
||||
key_pinned_ = true;
|
||||
} else {
|
||||
// This key share `shared` bytes with prev key, we need to decode it
|
||||
key_.TrimAppend(shared, p, non_shared);
|
||||
key_pinned_ = false;
|
||||
}
|
||||
value_ = Slice(p + non_shared, value_length);
|
||||
while (restart_index_ + 1 < num_restarts_ &&
|
||||
GetRestartPoint(restart_index_ + 1) < current_) {
|
||||
++restart_index_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Binary search in restart array to find the first restart point that
|
||||
// is either the last restart point with a key less than target,
|
||||
// which means the key of next restart point is larger than target, or
|
||||
@@ -349,7 +422,7 @@ bool BlockIter::BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
}
|
||||
|
||||
// Compare target key and the block key of the block of `block_index`.
|
||||
// Return -1 if eror.
|
||||
// Return -1 if error.
|
||||
int IndexBlockIter::CompareBlockKey(uint32_t block_index, const Slice& target) {
|
||||
uint32_t region_offset = GetRestartPoint(block_index);
|
||||
uint32_t shared, non_shared, value_length;
|
||||
|
||||
+70
-64
@@ -208,8 +208,7 @@ class BlockIter : public InternalIterator {
|
||||
public:
|
||||
void InitializeBase(const Comparator* comparator, const char* data,
|
||||
uint32_t restarts, uint32_t num_restarts,
|
||||
SequenceNumber global_seqno, bool key_includes_seq,
|
||||
bool block_contents_pinned) {
|
||||
SequenceNumber global_seqno, bool block_contents_pinned) {
|
||||
assert(data_ == nullptr); // Ensure it is called only once
|
||||
assert(num_restarts > 0); // Ensure the param is valid
|
||||
|
||||
@@ -220,13 +219,12 @@ class BlockIter : public InternalIterator {
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
global_seqno_ = global_seqno;
|
||||
key_includes_seq_ = key_includes_seq;
|
||||
block_contents_pinned_ = block_contents_pinned;
|
||||
}
|
||||
|
||||
// Makes Valid() return false, status() return `s`, and Seek()/Prev()/etc do
|
||||
// nothing. Calls cleanup functions.
|
||||
void Invalidate(Status s) {
|
||||
void InvalidateBase(Status s) {
|
||||
// Assert that the BlockIter is never deleted while Pinning is Enabled.
|
||||
assert(!pinned_iters_mgr_ ||
|
||||
(pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
|
||||
@@ -237,32 +235,19 @@ class BlockIter : public InternalIterator {
|
||||
|
||||
// Call cleanup callbacks.
|
||||
Cleanable::Reset();
|
||||
|
||||
// Clear prev entries cache.
|
||||
prev_entries_keys_buff_.clear();
|
||||
prev_entries_.clear();
|
||||
prev_entries_idx_ = -1;
|
||||
}
|
||||
|
||||
virtual bool Valid() const override { return current_ < restarts_; }
|
||||
virtual Status status() const override { return status_; }
|
||||
virtual Slice key() const override {
|
||||
assert(Valid());
|
||||
return key_.GetInternalKey();
|
||||
return key_.GetKey();
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(Valid());
|
||||
return value_;
|
||||
}
|
||||
|
||||
virtual void Next() override;
|
||||
|
||||
virtual void Prev() override;
|
||||
|
||||
virtual void SeekToFirst() override;
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
#ifndef NDEBUG
|
||||
virtual ~BlockIter() {
|
||||
// Assert that the BlockIter is never deleted while Pinning is Enabled.
|
||||
@@ -305,34 +290,8 @@ class BlockIter : public InternalIterator {
|
||||
bool key_pinned_;
|
||||
// whether the block data is guaranteed to outlive this iterator
|
||||
bool block_contents_pinned_;
|
||||
// Key is in InternalKey format
|
||||
bool key_includes_seq_;
|
||||
SequenceNumber global_seqno_;
|
||||
|
||||
struct CachedPrevEntry {
|
||||
explicit CachedPrevEntry(uint32_t _offset, const char* _key_ptr,
|
||||
size_t _key_offset, size_t _key_size, Slice _value)
|
||||
: offset(_offset),
|
||||
key_ptr(_key_ptr),
|
||||
key_offset(_key_offset),
|
||||
key_size(_key_size),
|
||||
value(_value) {}
|
||||
|
||||
// offset of entry in block
|
||||
uint32_t offset;
|
||||
// Pointer to key data in block (nullptr if key is delta-encoded)
|
||||
const char* key_ptr;
|
||||
// offset of key in prev_entries_keys_buff_ (0 if key_ptr is not nullptr)
|
||||
size_t key_offset;
|
||||
// size of key
|
||||
size_t key_size;
|
||||
// value slice pointing to data in block
|
||||
Slice value;
|
||||
};
|
||||
std::string prev_entries_keys_buff_;
|
||||
std::vector<CachedPrevEntry> prev_entries_;
|
||||
int32_t prev_entries_idx_ = -1;
|
||||
|
||||
public:
|
||||
// Return the offset in data_ just past the end of the current entry.
|
||||
inline uint32_t NextEntryOffset() const {
|
||||
@@ -357,7 +316,6 @@ class BlockIter : public InternalIterator {
|
||||
|
||||
void CorruptionError();
|
||||
|
||||
bool ParseNextKey();
|
||||
bool BinarySeek(const Slice& target, uint32_t left, uint32_t right,
|
||||
uint32_t* index, const Comparator* comp);
|
||||
};
|
||||
@@ -380,9 +338,9 @@ class DataBlockIter final : public BlockIter {
|
||||
SequenceNumber global_seqno,
|
||||
BlockReadAmpBitmap* read_amp_bitmap,
|
||||
bool block_contents_pinned) {
|
||||
const bool kKeyIncludesSeq = true;
|
||||
InitializeBase(comparator, data, restarts, num_restarts, global_seqno,
|
||||
kKeyIncludesSeq, block_contents_pinned);
|
||||
block_contents_pinned);
|
||||
key_.SetIsUserKey(false);
|
||||
read_amp_bitmap_ = read_amp_bitmap;
|
||||
last_bitmap_offset_ = current_ + 1;
|
||||
}
|
||||
@@ -402,11 +360,52 @@ class DataBlockIter final : public BlockIter {
|
||||
|
||||
virtual void SeekForPrev(const Slice& target) override;
|
||||
|
||||
virtual void Prev() override;
|
||||
|
||||
virtual void Next() override;
|
||||
|
||||
virtual void SeekToFirst() override;
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
void Invalidate(Status s) {
|
||||
InvalidateBase(s);
|
||||
// Clear prev entries cache.
|
||||
prev_entries_keys_buff_.clear();
|
||||
prev_entries_.clear();
|
||||
prev_entries_idx_ = -1;
|
||||
}
|
||||
|
||||
private:
|
||||
// read-amp bitmap
|
||||
BlockReadAmpBitmap* read_amp_bitmap_;
|
||||
// last `current_` value we report to read-amp bitmp
|
||||
mutable uint32_t last_bitmap_offset_;
|
||||
struct CachedPrevEntry {
|
||||
explicit CachedPrevEntry(uint32_t _offset, const char* _key_ptr,
|
||||
size_t _key_offset, size_t _key_size, Slice _value)
|
||||
: offset(_offset),
|
||||
key_ptr(_key_ptr),
|
||||
key_offset(_key_offset),
|
||||
key_size(_key_size),
|
||||
value(_value) {}
|
||||
|
||||
// offset of entry in block
|
||||
uint32_t offset;
|
||||
// Pointer to key data in block (nullptr if key is delta-encoded)
|
||||
const char* key_ptr;
|
||||
// offset of key in prev_entries_keys_buff_ (0 if key_ptr is not nullptr)
|
||||
size_t key_offset;
|
||||
// size of key
|
||||
size_t key_size;
|
||||
// value slice pointing to data in block
|
||||
Slice value;
|
||||
};
|
||||
std::string prev_entries_keys_buff_;
|
||||
std::vector<CachedPrevEntry> prev_entries_;
|
||||
int32_t prev_entries_idx_ = -1;
|
||||
|
||||
bool ParseNextDataKey();
|
||||
|
||||
inline int Compare(const IterKey& ikey, const Slice& b) const {
|
||||
return comparator_->Compare(ikey.GetInternalKey(), b);
|
||||
@@ -419,7 +418,7 @@ class IndexBlockIter final : public BlockIter {
|
||||
|
||||
virtual Slice key() const override {
|
||||
assert(Valid());
|
||||
return key_includes_seq_ ? key_.GetInternalKey() : key_.GetUserKey();
|
||||
return key_.GetKey();
|
||||
}
|
||||
IndexBlockIter(const Comparator* comparator,
|
||||
const Comparator* user_comparator, const char* data,
|
||||
@@ -436,10 +435,11 @@ class IndexBlockIter final : public BlockIter {
|
||||
uint32_t restarts, uint32_t num_restarts,
|
||||
BlockPrefixIndex* prefix_index, bool key_includes_seq,
|
||||
bool block_contents_pinned) {
|
||||
user_comparator_ = user_comparator;
|
||||
InitializeBase(comparator, data, restarts, num_restarts,
|
||||
kDisableGlobalSequenceNumber, key_includes_seq,
|
||||
block_contents_pinned);
|
||||
kDisableGlobalSequenceNumber, block_contents_pinned);
|
||||
key_includes_seq_ = key_includes_seq;
|
||||
active_comparator_ = key_includes_seq_ ? comparator_ : user_comparator;
|
||||
key_.SetIsUserKey(!key_includes_seq_);
|
||||
prefix_index_ = prefix_index;
|
||||
}
|
||||
|
||||
@@ -450,12 +450,22 @@ class IndexBlockIter final : public BlockIter {
|
||||
current_ = restarts_;
|
||||
restart_index_ = num_restarts_;
|
||||
status_ = Status::InvalidArgument(
|
||||
"RocksDB internal error: should never all SeekForPrev() for index "
|
||||
"RocksDB internal error: should never call SeekForPrev() on index "
|
||||
"blocks");
|
||||
key_.Clear();
|
||||
value_.clear();
|
||||
}
|
||||
|
||||
virtual void Prev() override;
|
||||
|
||||
virtual void Next() override;
|
||||
|
||||
virtual void SeekToFirst() override;
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
void Invalidate(Status s) { InvalidateBase(s); }
|
||||
|
||||
private:
|
||||
bool PrefixSeek(const Slice& target, uint32_t* index);
|
||||
bool BinaryBlockIndexSeek(const Slice& target, uint32_t* block_ids,
|
||||
@@ -464,23 +474,19 @@ class IndexBlockIter final : public BlockIter {
|
||||
int CompareBlockKey(uint32_t block_index, const Slice& target);
|
||||
|
||||
inline int Compare(const Slice& a, const Slice& b) const {
|
||||
if (key_includes_seq_) {
|
||||
return comparator_->Compare(a, b);
|
||||
} else {
|
||||
return user_comparator_->Compare(a, b);
|
||||
}
|
||||
return active_comparator_->Compare(a, b);
|
||||
}
|
||||
|
||||
inline int Compare(const IterKey& ikey, const Slice& b) const {
|
||||
if (key_includes_seq_) {
|
||||
return comparator_->Compare(ikey.GetInternalKey(), b);
|
||||
} else {
|
||||
return user_comparator_->Compare(ikey.GetUserKey(), b);
|
||||
}
|
||||
return active_comparator_->Compare(ikey.GetKey(), b);
|
||||
}
|
||||
|
||||
// Same as comparator_ if comparator_ is not InernalKeyComparator
|
||||
const Comparator* user_comparator_;
|
||||
bool ParseNextIndexKey();
|
||||
|
||||
// Key is in InternalKey format
|
||||
bool key_includes_seq_;
|
||||
// key_includes_seq_ ? comparator_ : user_comparator_
|
||||
const Comparator* active_comparator_;
|
||||
BlockPrefixIndex* prefix_index_;
|
||||
};
|
||||
|
||||
|
||||
+176
-156
@@ -37,7 +37,6 @@
|
||||
#include "table/filter_block.h"
|
||||
#include "table/format.h"
|
||||
#include "table/full_filter_block.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/table_builder.h"
|
||||
|
||||
#include "util/string_util.h"
|
||||
@@ -668,6 +667,172 @@ Status BlockBasedTableBuilder::InsertBlockInCache(const Slice& block_contents,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteFilterBlock(
|
||||
MetaIndexBuilder* meta_index_builder) {
|
||||
BlockHandle filter_block_handle;
|
||||
bool empty_filter_block = (rep_->filter_builder == nullptr ||
|
||||
rep_->filter_builder->NumAdded() == 0);
|
||||
if (ok() && !empty_filter_block) {
|
||||
Status s = Status::Incomplete();
|
||||
while (ok() && s.IsIncomplete()) {
|
||||
Slice filter_content = rep_->filter_builder->Finish(filter_block_handle, &s);
|
||||
assert(s.ok() || s.IsIncomplete());
|
||||
rep_->props.filter_size += filter_content.size();
|
||||
WriteRawBlock(filter_content, kNoCompression, &filter_block_handle);
|
||||
}
|
||||
}
|
||||
if (ok() && !empty_filter_block) {
|
||||
// Add mapping from "<filter_block_prefix>.Name" to location
|
||||
// of filter data.
|
||||
std::string key;
|
||||
if (rep_->filter_builder->IsBlockBased()) {
|
||||
key = BlockBasedTable::kFilterBlockPrefix;
|
||||
} else {
|
||||
key = rep_->table_options.partition_filters
|
||||
? BlockBasedTable::kPartitionedFilterBlockPrefix
|
||||
: BlockBasedTable::kFullFilterBlockPrefix;
|
||||
}
|
||||
key.append(rep_->table_options.filter_policy->Name());
|
||||
meta_index_builder->Add(key, filter_block_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteIndexBlock(
|
||||
MetaIndexBuilder* meta_index_builder, BlockHandle* index_block_handle) {
|
||||
IndexBuilder::IndexBlocks index_blocks;
|
||||
auto index_builder_status = rep_->index_builder->Finish(&index_blocks);
|
||||
if (index_builder_status.IsIncomplete()) {
|
||||
// We we have more than one index partition then meta_blocks are not
|
||||
// supported for the index. Currently meta_blocks are used only by
|
||||
// HashIndexBuilder which is not multi-partition.
|
||||
assert(index_blocks.meta_blocks.empty());
|
||||
} else if (ok() && !index_builder_status.ok()) {
|
||||
rep_->status = index_builder_status;
|
||||
}
|
||||
if (ok()) {
|
||||
for (const auto& item : index_blocks.meta_blocks) {
|
||||
BlockHandle block_handle;
|
||||
WriteBlock(item.second, &block_handle, false /* is_data_block */);
|
||||
if (!ok()) {
|
||||
break;
|
||||
}
|
||||
meta_index_builder->Add(item.first, block_handle);
|
||||
}
|
||||
}
|
||||
if (ok()) {
|
||||
if (rep_->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, index_block_handle, false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
index_block_handle);
|
||||
}
|
||||
}
|
||||
// If there are more index partitions, finish them and write them out
|
||||
Status s = index_builder_status;
|
||||
while (ok() && s.IsIncomplete()) {
|
||||
s = rep_->index_builder->Finish(&index_blocks, *index_block_handle);
|
||||
if (!s.ok() && !s.IsIncomplete()) {
|
||||
rep_->status = s;
|
||||
return;
|
||||
}
|
||||
if (rep_->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, index_block_handle, false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
index_block_handle);
|
||||
}
|
||||
// The last index_block_handle will be for the partition index block
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WritePropertiesBlock(
|
||||
MetaIndexBuilder* meta_index_builder) {
|
||||
BlockHandle properties_block_handle;
|
||||
if (ok()) {
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
rep_->props.column_family_id = rep_->column_family_id;
|
||||
rep_->props.column_family_name = rep_->column_family_name;
|
||||
rep_->props.filter_policy_name = rep_->table_options.filter_policy != nullptr
|
||||
? rep_->table_options.filter_policy->Name()
|
||||
: "";
|
||||
rep_->props.index_size =
|
||||
rep_->index_builder->IndexSize() + kBlockTrailerSize;
|
||||
rep_->props.comparator_name = rep_->ioptions.user_comparator != nullptr
|
||||
? rep_->ioptions.user_comparator->Name()
|
||||
: "nullptr";
|
||||
rep_->props.merge_operator_name = rep_->ioptions.merge_operator != nullptr
|
||||
? rep_->ioptions.merge_operator->Name()
|
||||
: "nullptr";
|
||||
rep_->props.compression_name =
|
||||
CompressionTypeToString(rep_->compression_ctx.type());
|
||||
rep_->props.prefix_extractor_name = rep_->moptions.prefix_extractor != nullptr
|
||||
? rep_->moptions.prefix_extractor->Name()
|
||||
: "nullptr";
|
||||
|
||||
std::string property_collectors_names = "[";
|
||||
for (size_t i = 0;
|
||||
i < rep_->ioptions.table_properties_collector_factories.size(); ++i) {
|
||||
if (i != 0) {
|
||||
property_collectors_names += ",";
|
||||
}
|
||||
property_collectors_names +=
|
||||
rep_->ioptions.table_properties_collector_factories[i]->Name();
|
||||
}
|
||||
property_collectors_names += "]";
|
||||
rep_->props.property_collectors_names = property_collectors_names;
|
||||
if (rep_->table_options.index_type ==
|
||||
BlockBasedTableOptions::kTwoLevelIndexSearch) {
|
||||
assert(rep_->p_index_builder_ != nullptr);
|
||||
rep_->props.index_partitions = rep_->p_index_builder_->NumPartitions();
|
||||
rep_->props.top_level_index_size =
|
||||
rep_->p_index_builder_->TopLevelIndexSize(rep_->offset);
|
||||
}
|
||||
rep_->props.index_key_is_user_key =
|
||||
!rep_->index_builder->seperator_is_key_plus_seq();
|
||||
rep_->props.creation_time = rep_->creation_time;
|
||||
rep_->props.oldest_key_time = rep_->oldest_key_time;
|
||||
|
||||
// Add basic properties
|
||||
property_block_builder.AddTableProperty(rep_->props);
|
||||
|
||||
// Add use collected properties
|
||||
NotifyCollectTableCollectorsOnFinish(rep_->table_properties_collectors,
|
||||
rep_->ioptions.info_log,
|
||||
&property_block_builder);
|
||||
|
||||
WriteRawBlock(property_block_builder.Finish(), kNoCompression,
|
||||
&properties_block_handle);
|
||||
}
|
||||
if (ok()) {
|
||||
meta_index_builder->Add(kPropertiesBlock, properties_block_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteCompressionDictBlock(
|
||||
MetaIndexBuilder* meta_index_builder) {
|
||||
if (rep_->compression_dict && rep_->compression_dict->size()) {
|
||||
BlockHandle compression_dict_block_handle;
|
||||
if (ok()) {
|
||||
WriteRawBlock(*rep_->compression_dict, kNoCompression,
|
||||
&compression_dict_block_handle);
|
||||
}
|
||||
if (ok()) {
|
||||
meta_index_builder->Add(kCompressionDictBlock,
|
||||
compression_dict_block_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteRangeDelBlock(
|
||||
MetaIndexBuilder* meta_index_builder) {
|
||||
if (ok() && !rep_->range_del_block.empty()) {
|
||||
BlockHandle range_del_block_handle;
|
||||
WriteRawBlock(rep_->range_del_block.Finish(), kNoCompression,
|
||||
&range_del_block_handle);
|
||||
meta_index_builder->Add(kRangeDelBlock, range_del_block_handle);
|
||||
}
|
||||
}
|
||||
|
||||
Status BlockBasedTableBuilder::Finish() {
|
||||
Rep* r = rep_;
|
||||
bool empty_data_block = r->data_block.empty();
|
||||
@@ -676,175 +841,30 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
r->closed = true;
|
||||
|
||||
// To make sure properties block is able to keep the accurate size of index
|
||||
// block, we will finish writing all index entries here and flush them
|
||||
// to storage after metaindex block is written.
|
||||
// block, we will finish writing all index entries first.
|
||||
if (ok() && !empty_data_block) {
|
||||
r->index_builder->AddIndexEntry(
|
||||
&r->last_key, nullptr /* no next data block */, r->pending_handle);
|
||||
}
|
||||
|
||||
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle,
|
||||
compression_dict_block_handle, range_del_block_handle;
|
||||
|
||||
// Write filter block
|
||||
bool empty_filter_block = (r->filter_builder == nullptr ||
|
||||
r->filter_builder->NumAdded() == 0);
|
||||
if (ok() && !empty_filter_block) {
|
||||
Status s = Status::Incomplete();
|
||||
while (s.IsIncomplete()) {
|
||||
Slice filter_content = r->filter_builder->Finish(filter_block_handle, &s);
|
||||
assert(s.ok() || s.IsIncomplete());
|
||||
r->props.filter_size += filter_content.size();
|
||||
WriteRawBlock(filter_content, kNoCompression, &filter_block_handle);
|
||||
}
|
||||
}
|
||||
|
||||
IndexBuilder::IndexBlocks index_blocks;
|
||||
auto index_builder_status = r->index_builder->Finish(&index_blocks);
|
||||
if (index_builder_status.IsIncomplete()) {
|
||||
// We we have more than one index partition then meta_blocks are not
|
||||
// supported for the index. Currently meta_blocks are used only by
|
||||
// HashIndexBuilder which is not multi-partition.
|
||||
assert(index_blocks.meta_blocks.empty());
|
||||
} else if (!index_builder_status.ok()) {
|
||||
return index_builder_status;
|
||||
}
|
||||
|
||||
// Write meta blocks and metaindex block with the following order.
|
||||
// 1. [meta block: filter]
|
||||
// 2. [meta block: properties]
|
||||
// 2. [meta block: index]
|
||||
// 3. [meta block: compression dictionary]
|
||||
// 4. [meta block: range deletion tombstone]
|
||||
// 5. [metaindex block]
|
||||
// write meta blocks
|
||||
// 5. [meta block: properties]
|
||||
// 6. [metaindex block]
|
||||
BlockHandle metaindex_block_handle, index_block_handle;
|
||||
MetaIndexBuilder meta_index_builder;
|
||||
for (const auto& item : index_blocks.meta_blocks) {
|
||||
BlockHandle block_handle;
|
||||
WriteBlock(item.second, &block_handle, false /* is_data_block */);
|
||||
meta_index_builder.Add(item.first, block_handle);
|
||||
}
|
||||
|
||||
if (ok()) {
|
||||
if (!empty_filter_block) {
|
||||
// Add mapping from "<filter_block_prefix>.Name" to location
|
||||
// of filter data.
|
||||
std::string key;
|
||||
if (r->filter_builder->IsBlockBased()) {
|
||||
key = BlockBasedTable::kFilterBlockPrefix;
|
||||
} else {
|
||||
key = r->table_options.partition_filters
|
||||
? BlockBasedTable::kPartitionedFilterBlockPrefix
|
||||
: BlockBasedTable::kFullFilterBlockPrefix;
|
||||
}
|
||||
key.append(r->table_options.filter_policy->Name());
|
||||
meta_index_builder.Add(key, filter_block_handle);
|
||||
}
|
||||
|
||||
// Write properties and compression dictionary blocks.
|
||||
{
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
r->props.column_family_id = r->column_family_id;
|
||||
r->props.column_family_name = r->column_family_name;
|
||||
r->props.filter_policy_name = r->table_options.filter_policy != nullptr ?
|
||||
r->table_options.filter_policy->Name() : "";
|
||||
r->props.index_size =
|
||||
r->index_builder->EstimatedSize() + kBlockTrailerSize;
|
||||
r->props.comparator_name = r->ioptions.user_comparator != nullptr
|
||||
? r->ioptions.user_comparator->Name()
|
||||
: "nullptr";
|
||||
r->props.merge_operator_name = r->ioptions.merge_operator != nullptr
|
||||
? r->ioptions.merge_operator->Name()
|
||||
: "nullptr";
|
||||
r->props.compression_name =
|
||||
CompressionTypeToString(r->compression_ctx.type());
|
||||
r->props.prefix_extractor_name =
|
||||
r->moptions.prefix_extractor != nullptr
|
||||
? r->moptions.prefix_extractor->Name()
|
||||
: "nullptr";
|
||||
|
||||
std::string property_collectors_names = "[";
|
||||
for (size_t i = 0;
|
||||
i < r->ioptions.table_properties_collector_factories.size(); ++i) {
|
||||
if (i != 0) {
|
||||
property_collectors_names += ",";
|
||||
}
|
||||
property_collectors_names +=
|
||||
r->ioptions.table_properties_collector_factories[i]->Name();
|
||||
}
|
||||
property_collectors_names += "]";
|
||||
r->props.property_collectors_names = property_collectors_names;
|
||||
if (r->table_options.index_type ==
|
||||
BlockBasedTableOptions::kTwoLevelIndexSearch) {
|
||||
assert(r->p_index_builder_ != nullptr);
|
||||
r->props.index_partitions = r->p_index_builder_->NumPartitions();
|
||||
r->props.top_level_index_size =
|
||||
r->p_index_builder_->EstimateTopLevelIndexSize(r->offset);
|
||||
}
|
||||
r->props.index_key_is_user_key =
|
||||
!r->index_builder->seperator_is_key_plus_seq();
|
||||
r->props.creation_time = r->creation_time;
|
||||
r->props.oldest_key_time = r->oldest_key_time;
|
||||
|
||||
// Add basic properties
|
||||
property_block_builder.AddTableProperty(r->props);
|
||||
|
||||
// Add use collected properties
|
||||
NotifyCollectTableCollectorsOnFinish(r->table_properties_collectors,
|
||||
r->ioptions.info_log,
|
||||
&property_block_builder);
|
||||
|
||||
BlockHandle properties_block_handle;
|
||||
WriteRawBlock(
|
||||
property_block_builder.Finish(),
|
||||
kNoCompression,
|
||||
&properties_block_handle
|
||||
);
|
||||
meta_index_builder.Add(kPropertiesBlock, properties_block_handle);
|
||||
|
||||
// Write compression dictionary block
|
||||
if (r->compression_dict && r->compression_dict->size()) {
|
||||
WriteRawBlock(*r->compression_dict, kNoCompression,
|
||||
&compression_dict_block_handle);
|
||||
meta_index_builder.Add(kCompressionDictBlock,
|
||||
compression_dict_block_handle);
|
||||
}
|
||||
} // end of properties/compression dictionary block writing
|
||||
|
||||
if (ok() && !r->range_del_block.empty()) {
|
||||
WriteRawBlock(r->range_del_block.Finish(), kNoCompression,
|
||||
&range_del_block_handle);
|
||||
meta_index_builder.Add(kRangeDelBlock, range_del_block_handle);
|
||||
} // range deletion tombstone meta block
|
||||
} // meta blocks
|
||||
|
||||
// Write index block
|
||||
WriteFilterBlock(&meta_index_builder);
|
||||
WriteIndexBlock(&meta_index_builder, &index_block_handle);
|
||||
WriteCompressionDictBlock(&meta_index_builder);
|
||||
WriteRangeDelBlock(&meta_index_builder);
|
||||
WritePropertiesBlock(&meta_index_builder);
|
||||
if (ok()) {
|
||||
// flush the meta index block
|
||||
WriteRawBlock(meta_index_builder.Finish(), kNoCompression,
|
||||
&metaindex_block_handle);
|
||||
|
||||
if (r->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle, false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
&index_block_handle);
|
||||
}
|
||||
// If there are more index partitions, finish them and write them out
|
||||
Status& s = index_builder_status;
|
||||
while (s.IsIncomplete()) {
|
||||
s = r->index_builder->Finish(&index_blocks, index_block_handle);
|
||||
if (!s.ok() && !s.IsIncomplete()) {
|
||||
return s;
|
||||
}
|
||||
if (r->table_options.enable_index_compression) {
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle,
|
||||
false);
|
||||
} else {
|
||||
WriteRawBlock(index_blocks.index_block_contents, kNoCompression,
|
||||
&index_block_handle);
|
||||
}
|
||||
// The last index_block_handle will be for the partition index block
|
||||
}
|
||||
}
|
||||
|
||||
// Write footer
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/meta_blocks.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
@@ -106,6 +107,14 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
Status InsertBlockInCache(const Slice& block_contents,
|
||||
const CompressionType type,
|
||||
const BlockHandle* handle);
|
||||
|
||||
void WriteFilterBlock(MetaIndexBuilder* meta_index_builder);
|
||||
void WriteIndexBlock(MetaIndexBuilder* meta_index_builder,
|
||||
BlockHandle* index_block_handle);
|
||||
void WritePropertiesBlock(MetaIndexBuilder* meta_index_builder);
|
||||
void WriteCompressionDictBlock(MetaIndexBuilder* meta_index_builder);
|
||||
void WriteRangeDelBlock(MetaIndexBuilder* meta_index_builder);
|
||||
|
||||
struct Rep;
|
||||
class BlockBasedTablePropertiesCollectorFactory;
|
||||
class BlockBasedTablePropertiesCollector;
|
||||
|
||||
@@ -738,8 +738,17 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
|
||||
std::unique_ptr<FilePrefetchBuffer> prefetch_buffer;
|
||||
|
||||
// Before read footer, readahead backwards to prefetch data
|
||||
const size_t kTailPrefetchSize = 512 * 1024;
|
||||
// prefetch both index and filters, down to all partitions
|
||||
const bool prefetch_all = prefetch_index_and_filter_in_cache || level == 0;
|
||||
const bool preload_all = !table_options.cache_index_and_filter_blocks;
|
||||
// Before read footer, readahead backwards to prefetch data. Do more readahead
|
||||
// if we're going to read index/filter.
|
||||
// TODO: This may incorrectly select small readahead in case partitioned
|
||||
// index/filter is enabled and top-level partition pinning is enabled. That's
|
||||
// because we need to issue readahead before we read the properties, at which
|
||||
// point we don't yet know the index type.
|
||||
const size_t kTailPrefetchSize =
|
||||
prefetch_all || preload_all ? 512 * 1024 : 4 * 1024;
|
||||
size_t prefetch_off;
|
||||
size_t prefetch_len;
|
||||
if (file_size < kTailPrefetchSize) {
|
||||
@@ -945,8 +954,6 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
bool need_upper_bound_check =
|
||||
PrefixExtractorChanged(rep->table_properties.get(), prefix_extractor);
|
||||
|
||||
// prefetch both index and filters, down to all partitions
|
||||
const bool prefetch_all = prefetch_index_and_filter_in_cache || level == 0;
|
||||
BlockBasedTableOptions::IndexType index_type = new_table->UpdateIndexType();
|
||||
// prefetch the first level of index
|
||||
const bool prefetch_index =
|
||||
@@ -1944,6 +1951,7 @@ bool BlockBasedTable::PrefixMayMatch(
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::Seek(const Slice& target) {
|
||||
is_out_of_bound_ = false;
|
||||
if (!CheckPrefixMayMatch(target)) {
|
||||
ResetDataIter();
|
||||
return;
|
||||
@@ -1973,6 +1981,7 @@ void BlockBasedTableIterator<TBlockIter>::Seek(const Slice& target) {
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::SeekForPrev(const Slice& target) {
|
||||
is_out_of_bound_ = false;
|
||||
if (!CheckPrefixMayMatch(target)) {
|
||||
ResetDataIter();
|
||||
return;
|
||||
@@ -2015,6 +2024,7 @@ void BlockBasedTableIterator<TBlockIter>::SeekForPrev(const Slice& target) {
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::SeekToFirst() {
|
||||
is_out_of_bound_ = false;
|
||||
SavePrevIndexValue();
|
||||
index_iter_->SeekToFirst();
|
||||
if (!index_iter_->Valid()) {
|
||||
@@ -2028,6 +2038,7 @@ void BlockBasedTableIterator<TBlockIter>::SeekToFirst() {
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::SeekToLast() {
|
||||
is_out_of_bound_ = false;
|
||||
SavePrevIndexValue();
|
||||
index_iter_->SeekToLast();
|
||||
if (!index_iter_->Valid()) {
|
||||
@@ -2106,7 +2117,7 @@ void BlockBasedTableIterator<TBlockIter>::InitDataBlock() {
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::FindKeyForward() {
|
||||
is_out_of_bound_ = false;
|
||||
assert(!is_out_of_bound_);
|
||||
// TODO the while loop inherits from two-level-iterator. We don't know
|
||||
// whether a block can be empty so it can be replaced by an "if".
|
||||
while (!block_iter_.Valid()) {
|
||||
@@ -2146,6 +2157,7 @@ void BlockBasedTableIterator<TBlockIter>::FindKeyForward() {
|
||||
|
||||
template <class TBlockIter>
|
||||
void BlockBasedTableIterator<TBlockIter>::FindKeyBackward() {
|
||||
assert(!is_out_of_bound_);
|
||||
while (!block_iter_.Valid()) {
|
||||
if (!block_iter_.status().ok()) {
|
||||
return;
|
||||
|
||||
+1
-1
@@ -212,7 +212,7 @@ struct BlockContents {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
return malloc_usable_size(allocation.get());
|
||||
#else
|
||||
return sizeof(*allocation.get());
|
||||
return data.size();
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
} else {
|
||||
return 0; // no extra memory is occupied by the data
|
||||
|
||||
@@ -200,7 +200,7 @@ bool GetContext::SaveValue(const ParsedInternalKey& parsed_key,
|
||||
merge_context_->PushOperand(value, false);
|
||||
}
|
||||
if (merge_operator_ != nullptr &&
|
||||
merge_operator_->ShouldMerge(merge_context_->GetOperands())) {
|
||||
merge_operator_->ShouldMerge(merge_context_->GetOperandsDirectionBackward())) {
|
||||
state_ = kFound;
|
||||
if (LIKELY(pinnable_val_ != nullptr)) {
|
||||
Status merge_status = MergeHelper::TimedFullMerge(
|
||||
|
||||
+18
-35
@@ -70,6 +70,12 @@ PartitionedIndexBuilder::PartitionedIndexBuilder(
|
||||
table_opt.format_version),
|
||||
sub_index_builder_(nullptr),
|
||||
table_opt_(table_opt),
|
||||
// We start by false. After each partition we revise the value based on
|
||||
// what the sub_index_builder has decided. If the feature is disabled
|
||||
// entirely, this will be set to true after switching the first
|
||||
// sub_index_builder. Otherwise, it could be set to true even one of the
|
||||
// sub_index_builders could not safely exclude seq from the keys, then it
|
||||
// wil be enforced on all sub_index_builders on ::Finish.
|
||||
seperator_is_key_plus_seq_(false) {}
|
||||
|
||||
PartitionedIndexBuilder::~PartitionedIndexBuilder() {
|
||||
@@ -83,7 +89,11 @@ void PartitionedIndexBuilder::MakeNewSubIndexBuilder() {
|
||||
table_opt_.format_version);
|
||||
flush_policy_.reset(FlushBlockBySizePolicyFactory::NewFlushBlockPolicy(
|
||||
table_opt_.metadata_block_size, table_opt_.block_size_deviation,
|
||||
sub_index_builder_->index_block_builder_));
|
||||
// Note: this is sub-optimal since sub_index_builder_ could later reset
|
||||
// seperator_is_key_plus_seq_ but the probability of that is low.
|
||||
sub_index_builder_->seperator_is_key_plus_seq_
|
||||
? sub_index_builder_->index_block_builder_
|
||||
: sub_index_builder_->index_block_builder_without_seq_));
|
||||
partition_cut_requested_ = false;
|
||||
}
|
||||
|
||||
@@ -143,6 +153,9 @@ void PartitionedIndexBuilder::AddIndexEntry(
|
||||
|
||||
Status PartitionedIndexBuilder::Finish(
|
||||
IndexBlocks* index_blocks, const BlockHandle& last_partition_block_handle) {
|
||||
if (partition_cnt_ == 0) {
|
||||
partition_cnt_ = entries_.size();
|
||||
}
|
||||
// It must be set to null after last key is added
|
||||
assert(sub_index_builder_ == nullptr);
|
||||
if (finishing_indexes == true) {
|
||||
@@ -164,6 +177,8 @@ Status PartitionedIndexBuilder::Finish(
|
||||
index_blocks->index_block_contents =
|
||||
index_block_builder_without_seq_.Finish();
|
||||
}
|
||||
top_level_index_size_ = index_blocks->index_block_contents.size();
|
||||
index_size_ += top_level_index_size_;
|
||||
return Status::OK();
|
||||
} else {
|
||||
// Finish the next partition index in line and Incomplete() to indicate we
|
||||
@@ -172,45 +187,13 @@ Status PartitionedIndexBuilder::Finish(
|
||||
// Apply the policy to all sub-indexes
|
||||
entry.value->seperator_is_key_plus_seq_ = seperator_is_key_plus_seq_;
|
||||
auto s = entry.value->Finish(index_blocks);
|
||||
index_size_ += index_blocks->index_block_contents.size();
|
||||
finishing_indexes = true;
|
||||
return s.ok() ? Status::Incomplete() : s;
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate size excluding the top-level index
|
||||
// It is assumed that this method is called before writing index partition
|
||||
// starts
|
||||
size_t PartitionedIndexBuilder::EstimatedSize() const {
|
||||
size_t total = 0;
|
||||
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||||
total += it->value->EstimatedSize();
|
||||
}
|
||||
total +=
|
||||
sub_index_builder_ == nullptr ? 0 : sub_index_builder_->EstimatedSize();
|
||||
return total;
|
||||
}
|
||||
|
||||
// Since when this method is called we do not know the index block offsets yet,
|
||||
// the top-level index does not exist. Hence we estimate the block offsets and
|
||||
// create a temporary top-level index.
|
||||
size_t PartitionedIndexBuilder::EstimateTopLevelIndexSize(
|
||||
uint64_t offset) const {
|
||||
BlockBuilder tmp_builder(
|
||||
table_opt_.index_block_restart_interval); // tmp top-level index builder
|
||||
for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||||
std::string tmp_handle_encoding;
|
||||
uint64_t size = it->value->EstimatedSize();
|
||||
BlockHandle tmp_block_handle(offset, size);
|
||||
tmp_block_handle.EncodeTo(&tmp_handle_encoding);
|
||||
tmp_builder.Add(
|
||||
seperator_is_key_plus_seq_ ? it->key : ExtractUserKey(it->key),
|
||||
tmp_handle_encoding);
|
||||
offset += size;
|
||||
}
|
||||
return tmp_builder.CurrentSizeEstimate();
|
||||
}
|
||||
|
||||
size_t PartitionedIndexBuilder::NumPartitions() const {
|
||||
return entries_.size();
|
||||
return partition_cnt_;
|
||||
}
|
||||
} // namespace rocksdb
|
||||
+20
-12
@@ -96,13 +96,15 @@ class IndexBuilder {
|
||||
virtual Status Finish(IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) = 0;
|
||||
|
||||
// Get the estimated size for index block.
|
||||
virtual size_t EstimatedSize() const = 0;
|
||||
// Get the size for index block. Must be called after ::Finish.
|
||||
virtual size_t IndexSize() const = 0;
|
||||
|
||||
virtual bool seperator_is_key_plus_seq() { return true; }
|
||||
|
||||
protected:
|
||||
const InternalKeyComparator* comparator_;
|
||||
// Set after ::Finish is called
|
||||
size_t index_size_ = 0;
|
||||
};
|
||||
|
||||
// This index builder builds space-efficient index block.
|
||||
@@ -162,15 +164,12 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
index_blocks->index_block_contents =
|
||||
index_block_builder_without_seq_.Finish();
|
||||
}
|
||||
index_size_ = index_blocks->index_block_contents.size();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual size_t EstimatedSize() const override {
|
||||
if (seperator_is_key_plus_seq_) {
|
||||
return index_block_builder_.CurrentSizeEstimate();
|
||||
} else {
|
||||
return index_block_builder_without_seq_.CurrentSizeEstimate();
|
||||
}
|
||||
virtual size_t IndexSize() const override {
|
||||
return index_size_;
|
||||
}
|
||||
|
||||
virtual bool seperator_is_key_plus_seq() override {
|
||||
@@ -272,8 +271,8 @@ class HashIndexBuilder : public IndexBuilder {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual size_t EstimatedSize() const override {
|
||||
return primary_index_builder_.EstimatedSize() + prefix_block_.size() +
|
||||
virtual size_t IndexSize() const override {
|
||||
return primary_index_builder_.IndexSize() + prefix_block_.size() +
|
||||
prefix_meta_block_.size();
|
||||
}
|
||||
|
||||
@@ -338,8 +337,12 @@ class PartitionedIndexBuilder : public IndexBuilder {
|
||||
IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) override;
|
||||
|
||||
virtual size_t EstimatedSize() const override;
|
||||
size_t EstimateTopLevelIndexSize(uint64_t) const;
|
||||
virtual size_t IndexSize() const override {
|
||||
return index_size_;
|
||||
}
|
||||
size_t TopLevelIndexSize(uint64_t) const {
|
||||
return top_level_index_size_;
|
||||
}
|
||||
size_t NumPartitions() const;
|
||||
|
||||
inline bool ShouldCutFilterBlock() {
|
||||
@@ -362,6 +365,11 @@ class PartitionedIndexBuilder : public IndexBuilder {
|
||||
}
|
||||
|
||||
private:
|
||||
// Set after ::Finish is called
|
||||
size_t top_level_index_size_ = 0;
|
||||
// Set after ::Finish is called
|
||||
size_t partition_cnt_ = 0;
|
||||
|
||||
void MakeNewSubIndexBuilder();
|
||||
|
||||
struct Entry {
|
||||
|
||||
+89
-9
@@ -352,19 +352,19 @@ class TableConstructor: public Constructor {
|
||||
file_writer_->Flush();
|
||||
EXPECT_TRUE(s.ok()) << s.ToString();
|
||||
|
||||
EXPECT_EQ(GetSink()->contents().size(), builder->FileSize());
|
||||
EXPECT_EQ(TEST_GetSink()->contents().size(), builder->FileSize());
|
||||
|
||||
// Open the table
|
||||
uniq_id_ = cur_uniq_id_++;
|
||||
file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource(
|
||||
GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
|
||||
TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
|
||||
const bool kSkipFilters = true;
|
||||
const bool kImmortal = true;
|
||||
return ioptions.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
|
||||
internal_comparator, !kSkipFilters, !kImmortal,
|
||||
level_),
|
||||
std::move(file_reader_), GetSink()->contents().size(), &table_reader_);
|
||||
std::move(file_reader_), TEST_GetSink()->contents().size(), &table_reader_);
|
||||
}
|
||||
|
||||
virtual InternalIterator* NewIterator(
|
||||
@@ -390,11 +390,11 @@ class TableConstructor: public Constructor {
|
||||
virtual Status Reopen(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& moptions) {
|
||||
file_reader_.reset(test::GetRandomAccessFileReader(new test::StringSource(
|
||||
GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
|
||||
TEST_GetSink()->contents(), uniq_id_, ioptions.allow_mmap_reads)));
|
||||
return ioptions.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions, moptions.prefix_extractor.get(), soptions,
|
||||
*last_internal_key_),
|
||||
std::move(file_reader_), GetSink()->contents().size(), &table_reader_);
|
||||
std::move(file_reader_), TEST_GetSink()->contents().size(), &table_reader_);
|
||||
}
|
||||
|
||||
virtual TableReader* GetTableReader() {
|
||||
@@ -409,6 +409,10 @@ class TableConstructor: public Constructor {
|
||||
|
||||
bool ConvertToInternalKey() { return convert_to_internal_key_; }
|
||||
|
||||
test::StringSink* TEST_GetSink() {
|
||||
return static_cast<test::StringSink*>(file_writer_->writable_file());
|
||||
}
|
||||
|
||||
private:
|
||||
void Reset() {
|
||||
uniq_id_ = 0;
|
||||
@@ -417,10 +421,6 @@ class TableConstructor: public Constructor {
|
||||
file_reader_.reset();
|
||||
}
|
||||
|
||||
test::StringSink* GetSink() {
|
||||
return static_cast<test::StringSink*>(file_writer_->writable_file());
|
||||
}
|
||||
|
||||
uint64_t uniq_id_;
|
||||
unique_ptr<WritableFileWriter> file_writer_;
|
||||
unique_ptr<RandomAccessFileReader> file_reader_;
|
||||
@@ -3494,6 +3494,86 @@ TEST_P(BlockBasedTableTest, PropertiesBlockRestartPointTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(BlockBasedTableTest, PropertiesMetaBlockLast) {
|
||||
// The properties meta-block should come at the end since we always need to
|
||||
// read it when opening a file, unlike index/filter/other meta-blocks, which
|
||||
// are sometimes read depending on the user's configuration. This ordering
|
||||
// allows us to do a small readahead on the end of the file to read properties
|
||||
// and meta-index blocks with one I/O.
|
||||
TableConstructor c(BytewiseComparator(), true /* convert_to_internal_key_ */);
|
||||
c.Add("a1", "val1");
|
||||
c.Add("b2", "val2");
|
||||
c.Add("c3", "val3");
|
||||
c.Add("d4", "val4");
|
||||
c.Add("e5", "val5");
|
||||
c.Add("f6", "val6");
|
||||
c.Add("g7", "val7");
|
||||
c.Add("h8", "val8");
|
||||
c.Add("j9", "val9");
|
||||
|
||||
// write an SST file
|
||||
Options options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(
|
||||
8 /* bits_per_key */, false /* use_block_based_filter */));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
ImmutableCFOptions ioptions(options);
|
||||
MutableCFOptions moptions(options);
|
||||
std::vector<std::string> keys;
|
||||
stl_wrappers::KVMap kvmap;
|
||||
c.Finish(options, ioptions, moptions, table_options,
|
||||
GetPlainInternalComparator(options.comparator), &keys, &kvmap);
|
||||
|
||||
// get file reader
|
||||
test::StringSink* table_sink = c.TEST_GetSink();
|
||||
std::unique_ptr<RandomAccessFileReader> table_reader{
|
||||
test::GetRandomAccessFileReader(
|
||||
new test::StringSource(table_sink->contents(), 0 /* unique_id */,
|
||||
false /* allow_mmap_reads */))};
|
||||
size_t table_size = table_sink->contents().size();
|
||||
|
||||
// read footer
|
||||
Footer footer;
|
||||
ASSERT_OK(ReadFooterFromFile(table_reader.get(),
|
||||
nullptr /* prefetch_buffer */, table_size,
|
||||
&footer, kBlockBasedTableMagicNumber));
|
||||
|
||||
// read metaindex
|
||||
auto metaindex_handle = footer.metaindex_handle();
|
||||
BlockContents metaindex_contents;
|
||||
Slice compression_dict;
|
||||
PersistentCacheOptions pcache_opts;
|
||||
BlockFetcher block_fetcher(
|
||||
table_reader.get(), nullptr /* prefetch_buffer */, footer, ReadOptions(),
|
||||
metaindex_handle, &metaindex_contents, ioptions, false /* decompress */,
|
||||
compression_dict, pcache_opts);
|
||||
ASSERT_OK(block_fetcher.ReadBlockContents());
|
||||
Block metaindex_block(std::move(metaindex_contents),
|
||||
kDisableGlobalSequenceNumber);
|
||||
|
||||
// verify properties block comes last
|
||||
std::unique_ptr<InternalIterator> metaindex_iter{
|
||||
metaindex_block.NewIterator<DataBlockIter>(options.comparator,
|
||||
options.comparator)};
|
||||
uint64_t max_offset = 0;
|
||||
std::string key_at_max_offset;
|
||||
for (metaindex_iter->SeekToFirst(); metaindex_iter->Valid();
|
||||
metaindex_iter->Next()) {
|
||||
BlockHandle handle;
|
||||
Slice value = metaindex_iter->value();
|
||||
ASSERT_OK(handle.DecodeFrom(&value));
|
||||
if (handle.offset() > max_offset) {
|
||||
max_offset = handle.offset();
|
||||
key_at_max_offset = metaindex_iter->key().ToString();
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kPropertiesBlock, key_at_max_offset);
|
||||
// index handle is stored in footer rather than metaindex block, so need
|
||||
// separate logic to verify it comes before properties block.
|
||||
ASSERT_GT(max_offset, footer.index_handle().offset());
|
||||
c.ResetTableReader();
|
||||
}
|
||||
|
||||
TEST_P(BlockBasedTableTest, BadOptions) {
|
||||
rocksdb::Options options;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
@@ -242,6 +242,14 @@ LDBCommand* LDBCommand::SelectCommand(const ParsedParams& parsed_params) {
|
||||
} else if (parsed_params.cmd == RestoreCommand::Name()) {
|
||||
return new RestoreCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map, parsed_params.flags);
|
||||
} else if (parsed_params.cmd == WriteExternalSstFilesCommand::Name()) {
|
||||
return new WriteExternalSstFilesCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map,
|
||||
parsed_params.flags);
|
||||
} else if (parsed_params.cmd == IngestExternalSstFilesCommand::Name()) {
|
||||
return new IngestExternalSstFilesCommand(parsed_params.cmd_params,
|
||||
parsed_params.option_map,
|
||||
parsed_params.flags);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2939,5 +2947,180 @@ void DBFileDumperCommand::DoCommand() {
|
||||
}
|
||||
}
|
||||
|
||||
void WriteExternalSstFilesCommand::Help(std::string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(WriteExternalSstFilesCommand::Name());
|
||||
ret.append(" <output_sst_path>");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
WriteExternalSstFilesCommand::WriteExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags)
|
||||
: LDBCommand(
|
||||
options, flags, false /* is_read_only */,
|
||||
BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM,
|
||||
ARG_TO, ARG_CREATE_IF_MISSING})) {
|
||||
create_if_missing_ =
|
||||
IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
|
||||
ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
|
||||
if (params.size() != 1) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"output SST file path must be specified");
|
||||
} else {
|
||||
output_sst_path_ = params.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteExternalSstFilesCommand::DoCommand() {
|
||||
if (!db_) {
|
||||
assert(GetExecuteState().IsFailed());
|
||||
return;
|
||||
}
|
||||
ColumnFamilyHandle* cfh = GetCfHandle();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), db_->GetOptions(), cfh);
|
||||
Status status = sst_file_writer.Open(output_sst_path_);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed("failed to open SST file: " +
|
||||
status.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
int bad_lines = 0;
|
||||
std::string line;
|
||||
std::ifstream ifs_stdin("/dev/stdin");
|
||||
std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin;
|
||||
while (getline(*istream_p, line, '\n')) {
|
||||
std::string key;
|
||||
std::string value;
|
||||
if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) {
|
||||
status = sst_file_writer.Put(key, value);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"failed to write record to file: " + status.ToString());
|
||||
return;
|
||||
}
|
||||
} else if (0 == line.find("Keys in range:")) {
|
||||
// ignore this line
|
||||
} else if (0 == line.find("Created bg thread 0x")) {
|
||||
// ignore this line
|
||||
} else {
|
||||
bad_lines++;
|
||||
}
|
||||
}
|
||||
|
||||
status = sst_file_writer.Finish();
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"Failed to finish writing to file: " + status.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (bad_lines > 0) {
|
||||
fprintf(stderr, "Warning: %d bad lines ignored.\n", bad_lines);
|
||||
}
|
||||
exec_state_ = LDBCommandExecuteResult::Succeed(
|
||||
"external SST file written to " + output_sst_path_);
|
||||
}
|
||||
|
||||
Options WriteExternalSstFilesCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = create_if_missing_;
|
||||
return opt;
|
||||
}
|
||||
|
||||
const std::string IngestExternalSstFilesCommand::ARG_MOVE_FILES = "move_files";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_SNAPSHOT_CONSISTENCY =
|
||||
"snapshot_consistency";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_ALLOW_GLOBAL_SEQNO =
|
||||
"allow_global_seqno";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_ALLOW_BLOCKING_FLUSH =
|
||||
"allow_blocking_flush";
|
||||
const std::string IngestExternalSstFilesCommand::ARG_INGEST_BEHIND =
|
||||
"ingest_behind";
|
||||
|
||||
void IngestExternalSstFilesCommand::Help(std::string& ret) {
|
||||
ret.append(" ");
|
||||
ret.append(IngestExternalSstFilesCommand::Name());
|
||||
ret.append(" <input_sst_path>");
|
||||
ret.append(" [--" + ARG_MOVE_FILES + "] ");
|
||||
ret.append(" [--" + ARG_SNAPSHOT_CONSISTENCY + "] ");
|
||||
ret.append(" [--" + ARG_ALLOW_GLOBAL_SEQNO + "] ");
|
||||
ret.append(" [--" + ARG_ALLOW_BLOCKING_FLUSH + "] ");
|
||||
ret.append(" [--" + ARG_INGEST_BEHIND + "] ");
|
||||
ret.append("\n");
|
||||
}
|
||||
|
||||
IngestExternalSstFilesCommand::IngestExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags)
|
||||
: LDBCommand(
|
||||
options, flags, false /* is_read_only */,
|
||||
BuildCmdLineOptions({ARG_MOVE_FILES, ARG_SNAPSHOT_CONSISTENCY,
|
||||
ARG_ALLOW_GLOBAL_SEQNO, ARG_CREATE_IF_MISSING,
|
||||
ARG_ALLOW_BLOCKING_FLUSH, ARG_INGEST_BEHIND})),
|
||||
move_files_(false),
|
||||
snapshot_consistency_(true),
|
||||
allow_global_seqno_(true),
|
||||
allow_blocking_flush_(true),
|
||||
ingest_behind_(false) {
|
||||
create_if_missing_ =
|
||||
IsFlagPresent(flags, ARG_CREATE_IF_MISSING) ||
|
||||
ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false);
|
||||
move_files_ = IsFlagPresent(flags, ARG_MOVE_FILES) ||
|
||||
ParseBooleanOption(options, ARG_MOVE_FILES, false);
|
||||
snapshot_consistency_ =
|
||||
IsFlagPresent(flags, ARG_SNAPSHOT_CONSISTENCY) ||
|
||||
ParseBooleanOption(options, ARG_SNAPSHOT_CONSISTENCY, true);
|
||||
allow_global_seqno_ =
|
||||
IsFlagPresent(flags, ARG_ALLOW_GLOBAL_SEQNO) ||
|
||||
ParseBooleanOption(options, ARG_ALLOW_GLOBAL_SEQNO, true);
|
||||
allow_blocking_flush_ =
|
||||
IsFlagPresent(flags, ARG_ALLOW_BLOCKING_FLUSH) ||
|
||||
ParseBooleanOption(options, ARG_ALLOW_BLOCKING_FLUSH, true);
|
||||
ingest_behind_ = IsFlagPresent(flags, ARG_INGEST_BEHIND) ||
|
||||
ParseBooleanOption(options, ARG_INGEST_BEHIND, false);
|
||||
|
||||
if (params.size() != 1) {
|
||||
exec_state_ =
|
||||
LDBCommandExecuteResult::Failed("input SST path must be specified");
|
||||
} else {
|
||||
input_sst_path_ = params.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
void IngestExternalSstFilesCommand::DoCommand() {
|
||||
if (!db_) {
|
||||
assert(GetExecuteState().IsFailed());
|
||||
return;
|
||||
}
|
||||
if (GetExecuteState().IsFailed()) {
|
||||
return;
|
||||
}
|
||||
ColumnFamilyHandle* cfh = GetCfHandle();
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = move_files_;
|
||||
ifo.snapshot_consistency = snapshot_consistency_;
|
||||
ifo.allow_global_seqno = allow_global_seqno_;
|
||||
ifo.allow_blocking_flush = allow_blocking_flush_;
|
||||
ifo.ingest_behind = ingest_behind_;
|
||||
Status status = db_->IngestExternalFile(cfh, {input_sst_path_}, ifo);
|
||||
if (!status.ok()) {
|
||||
exec_state_ = LDBCommandExecuteResult::Failed(
|
||||
"failed to ingest external SST: " + status.ToString());
|
||||
} else {
|
||||
exec_state_ =
|
||||
LDBCommandExecuteResult::Succeed("external SST files ingested");
|
||||
}
|
||||
}
|
||||
|
||||
Options IngestExternalSstFilesCommand::PrepareOptionsForOpenDB() {
|
||||
Options opt = LDBCommand::PrepareOptionsForOpenDB();
|
||||
opt.create_if_missing = create_if_missing_;
|
||||
return opt;
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -522,4 +522,55 @@ class RestoreCommand : public BackupableCommand {
|
||||
static void Help(std::string& ret);
|
||||
};
|
||||
|
||||
class WriteExternalSstFilesCommand : public LDBCommand {
|
||||
public:
|
||||
static std::string Name() { return "write_extern_sst"; }
|
||||
WriteExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
virtual void DoCommand() override;
|
||||
|
||||
virtual bool NoDBOpen() override { return false; }
|
||||
|
||||
virtual Options PrepareOptionsForOpenDB() override;
|
||||
|
||||
static void Help(std::string& ret);
|
||||
|
||||
private:
|
||||
std::string output_sst_path_;
|
||||
};
|
||||
|
||||
class IngestExternalSstFilesCommand : public LDBCommand {
|
||||
public:
|
||||
static std::string Name() { return "ingest_extern_sst"; }
|
||||
IngestExternalSstFilesCommand(
|
||||
const std::vector<std::string>& params,
|
||||
const std::map<std::string, std::string>& options,
|
||||
const std::vector<std::string>& flags);
|
||||
|
||||
virtual void DoCommand() override;
|
||||
|
||||
virtual bool NoDBOpen() override { return false; }
|
||||
|
||||
virtual Options PrepareOptionsForOpenDB() override;
|
||||
|
||||
static void Help(std::string& ret);
|
||||
|
||||
private:
|
||||
std::string input_sst_path_;
|
||||
bool move_files_;
|
||||
bool snapshot_consistency_;
|
||||
bool allow_global_seqno_;
|
||||
bool allow_blocking_flush_;
|
||||
bool ingest_behind_;
|
||||
|
||||
static const std::string ARG_MOVE_FILES;
|
||||
static const std::string ARG_SNAPSHOT_CONSISTENCY;
|
||||
static const std::string ARG_ALLOW_GLOBAL_SEQNO;
|
||||
static const std::string ARG_ALLOW_BLOCKING_FLUSH;
|
||||
static const std::string ARG_INGEST_BEHIND;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+42
-1
@@ -76,7 +76,7 @@ class LDBTestCase(unittest.TestCase):
|
||||
|
||||
my_check_output("./ldb %s >/dev/null 2>&1 |grep -v \"Created bg \
|
||||
thread\"" % params, shell=True)
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
return
|
||||
self.fail(
|
||||
"Exception should have been raised for command with params: %s" %
|
||||
@@ -146,6 +146,14 @@ class LDBTestCase(unittest.TestCase):
|
||||
def loadDb(self, params, dumpFile):
|
||||
return 0 == run_err_null("cat %s | ./ldb load %s" % (dumpFile, params))
|
||||
|
||||
def writeExternSst(self, params, inputDumpFile, outputSst):
|
||||
return 0 == run_err_null("cat %s | ./ldb write_extern_sst %s %s"
|
||||
% (inputDumpFile, outputSst, params))
|
||||
|
||||
def ingestExternSst(self, params, inputSst):
|
||||
return 0 == run_err_null("./ldb ingest_extern_sst %s %s"
|
||||
% (inputSst, params))
|
||||
|
||||
def testStringBatchPut(self):
|
||||
print "Running testStringBatchPut..."
|
||||
self.assertRunOK("batchput x1 y1 --create_if_missing", "OK")
|
||||
@@ -547,5 +555,38 @@ class LDBTestCase(unittest.TestCase):
|
||||
# non-existing column family.
|
||||
self.assertRunFAIL("get cf3_1 --column_family=four")
|
||||
|
||||
def testIngestExternalSst(self):
|
||||
print "Running testIngestExternalSst..."
|
||||
|
||||
# Dump, load, write external sst and ingest it in another db
|
||||
dbPath = os.path.join(self.TMP_DIR, "db1")
|
||||
self.assertRunOK(
|
||||
"batchput --db=%s --create_if_missing x1 y1 x2 y2 x3 y3 x4 y4"
|
||||
% dbPath,
|
||||
"OK")
|
||||
self.assertRunOK("scan --db=%s" % dbPath,
|
||||
"x1 : y1\nx2 : y2\nx3 : y3\nx4 : y4")
|
||||
dumpFilePath = os.path.join(self.TMP_DIR, "dump1")
|
||||
with open(dumpFilePath, 'w') as f:
|
||||
f.write("x1 ==> y10\nx2 ==> y20\nx3 ==> y30\nx4 ==> y40")
|
||||
externSstPath = os.path.join(self.TMP_DIR, "extern_data1.sst")
|
||||
self.assertTrue(self.writeExternSst("--create_if_missing --db=%s"
|
||||
% dbPath,
|
||||
dumpFilePath,
|
||||
externSstPath))
|
||||
# cannot ingest if allow_global_seqno is false
|
||||
self.assertFalse(
|
||||
self.ingestExternSst(
|
||||
"--create_if_missing --allow_global_seqno=false --db=%s"
|
||||
% dbPath,
|
||||
externSstPath))
|
||||
self.assertTrue(
|
||||
self.ingestExternSst(
|
||||
"--create_if_missing --allow_global_seqno --db=%s"
|
||||
% dbPath,
|
||||
externSstPath))
|
||||
self.assertRunOKFull("scan --db=%s" % dbPath,
|
||||
"x1 : y10\nx2 : y20\nx3 : y30\nx4 : y40")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -88,6 +88,8 @@ void LDBCommandRunner::PrintHelp(const LDBOptions& ldb_options,
|
||||
BackupCommand::Help(ret);
|
||||
RestoreCommand::Help(ret);
|
||||
CheckPointCommand::Help(ret);
|
||||
WriteExternalSstFilesCommand::Help(ret);
|
||||
IngestExternalSstFilesCommand::Help(ret);
|
||||
|
||||
fprintf(stderr, "%s\n", ret.c_str());
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace rocksdb {
|
||||
const unsigned int kMaxVarint64Length = 10;
|
||||
|
||||
// Standard Put... routines append to a string
|
||||
extern void PutFixed16(std::string* dst, uint16_t value);
|
||||
extern void PutFixed32(std::string* dst, uint32_t value);
|
||||
extern void PutFixed64(std::string* dst, uint64_t value);
|
||||
extern void PutVarint32(std::string* dst, uint32_t value);
|
||||
@@ -54,6 +55,7 @@ extern void PutLengthPrefixedSliceParts(std::string* dst,
|
||||
// and advance the slice past the parsed value.
|
||||
extern bool GetFixed64(Slice* input, uint64_t* value);
|
||||
extern bool GetFixed32(Slice* input, uint32_t* value);
|
||||
extern bool GetFixed16(Slice* input, uint16_t* value);
|
||||
extern bool GetVarint32(Slice* input, uint32_t* value);
|
||||
extern bool GetVarint64(Slice* input, uint64_t* value);
|
||||
extern bool GetLengthPrefixedSlice(Slice* input, Slice* result);
|
||||
@@ -74,6 +76,7 @@ extern int VarintLength(uint64_t v);
|
||||
|
||||
// Lower-level versions of Put... that write directly into a character buffer
|
||||
// REQUIRES: dst has enough space for the value being written
|
||||
extern void EncodeFixed16(char* dst, uint16_t value);
|
||||
extern void EncodeFixed32(char* dst, uint32_t value);
|
||||
extern void EncodeFixed64(char* dst, uint64_t value);
|
||||
|
||||
@@ -86,6 +89,18 @@ extern char* EncodeVarint64(char* dst, uint64_t value);
|
||||
// Lower-level versions of Get... that read directly from a character buffer
|
||||
// without any bounds checking.
|
||||
|
||||
inline uint16_t DecodeFixed16(const char* ptr) {
|
||||
if (port::kLittleEndian) {
|
||||
// Load the raw bytes
|
||||
uint16_t result;
|
||||
memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
|
||||
return result;
|
||||
} else {
|
||||
return ((static_cast<uint16_t>(static_cast<unsigned char>(ptr[0]))) |
|
||||
(static_cast<uint16_t>(static_cast<unsigned char>(ptr[1])) << 8));
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t DecodeFixed32(const char* ptr) {
|
||||
if (port::kLittleEndian) {
|
||||
// Load the raw bytes
|
||||
@@ -131,6 +146,15 @@ inline const char* GetVarint32Ptr(const char* p,
|
||||
}
|
||||
|
||||
// -- Implementation of the functions declared above
|
||||
inline void EncodeFixed16(char* buf, uint16_t value) {
|
||||
if (port::kLittleEndian) {
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
} else {
|
||||
buf[0] = value & 0xff;
|
||||
buf[1] = (value >> 8) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
inline void EncodeFixed32(char* buf, uint32_t value) {
|
||||
if (port::kLittleEndian) {
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
@@ -158,6 +182,17 @@ inline void EncodeFixed64(char* buf, uint64_t value) {
|
||||
}
|
||||
|
||||
// Pull the last 8 bits and cast it to a character
|
||||
inline void PutFixed16(std::string* dst, uint16_t value) {
|
||||
if (port::kLittleEndian) {
|
||||
dst->append(const_cast<const char*>(reinterpret_cast<char*>(&value)),
|
||||
sizeof(value));
|
||||
} else {
|
||||
char buf[sizeof(value)];
|
||||
EncodeFixed16(buf, value);
|
||||
dst->append(buf, sizeof(buf));
|
||||
}
|
||||
}
|
||||
|
||||
inline void PutFixed32(std::string* dst, uint32_t value) {
|
||||
if (port::kLittleEndian) {
|
||||
dst->append(const_cast<const char*>(reinterpret_cast<char*>(&value)),
|
||||
@@ -286,6 +321,15 @@ inline bool GetFixed32(Slice* input, uint32_t* value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool GetFixed16(Slice* input, uint16_t* value) {
|
||||
if (input->size() < sizeof(uint16_t)) {
|
||||
return false;
|
||||
}
|
||||
*value = DecodeFixed16(input->data());
|
||||
input->remove_prefix(sizeof(uint16_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool GetVarint32(Slice* input, uint32_t* value) {
|
||||
const char* p = input->data();
|
||||
const char* limit = p + input->size();
|
||||
|
||||
@@ -14,6 +14,19 @@
|
||||
namespace rocksdb {
|
||||
|
||||
class Coding { };
|
||||
TEST(Coding, Fixed16) {
|
||||
std::string s;
|
||||
for (uint16_t v = 0; v < 0xFFFF; v++) {
|
||||
PutFixed16(&s, v);
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
for (uint16_t v = 0; v < 0xFFFF; v++) {
|
||||
uint16_t actual = DecodeFixed16(p);
|
||||
ASSERT_EQ(v, actual);
|
||||
p += sizeof(uint16_t);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Coding, Fixed32) {
|
||||
std::string s;
|
||||
|
||||
@@ -18,9 +18,17 @@ namespace rocksdb {
|
||||
__thread size_t ConcurrentArena::tls_cpuid = 0;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
// If the shard block size is too large, in the worst case, every core
|
||||
// allocates a block without populate it. If the shared block size is
|
||||
// 1MB, 64 cores will quickly allocate 64MB, and may quickly trigger a
|
||||
// flush. Cap the size instead.
|
||||
const size_t kMaxShardBlockSize = size_t{128 * 1024};
|
||||
} // namespace
|
||||
|
||||
ConcurrentArena::ConcurrentArena(size_t block_size, AllocTracker* tracker,
|
||||
size_t huge_page_size)
|
||||
: shard_block_size_(block_size / 8),
|
||||
: shard_block_size_(std::min(kMaxShardBlockSize, block_size / 8)),
|
||||
shards_(),
|
||||
arena_(block_size, tracker, huge_page_size) {
|
||||
Fixup();
|
||||
|
||||
@@ -9,15 +9,29 @@
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
#include <stdio.h>
|
||||
#ifdef OS_WIN
|
||||
#include <string.h>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "port/port.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
const char* Status::CopyState(const char* state) {
|
||||
#ifdef OS_WIN
|
||||
const size_t cch =
|
||||
std::strlen(state) + 1; // +1 for the null terminator
|
||||
char* result = new char[cch];
|
||||
errno_t ret;
|
||||
ret = strncpy_s(result, cch, state, cch - 1);
|
||||
result[cch - 1] = '\0';
|
||||
assert(ret == 0);
|
||||
return result;
|
||||
#else
|
||||
const size_t cch =
|
||||
std::strlen(state) + 1; // +1 for the null terminator
|
||||
return std::strncpy(new char[cch], state, cch);
|
||||
#endif
|
||||
}
|
||||
|
||||
Status::Status(Code _code, SubCode _subcode, const Slice& msg, const Slice& msg2)
|
||||
|
||||
+5
-5
@@ -247,7 +247,7 @@ class RandomRWStringSink : public RandomRWFile {
|
||||
public:
|
||||
explicit RandomRWStringSink(StringSink* ss) : ss_(ss) {}
|
||||
|
||||
Status Write(uint64_t offset, const Slice& data) {
|
||||
Status Write(uint64_t offset, const Slice& data) override {
|
||||
if (offset + data.size() > ss_->contents_.size()) {
|
||||
ss_->contents_.resize(offset + data.size(), '\0');
|
||||
}
|
||||
@@ -258,7 +258,7 @@ class RandomRWStringSink : public RandomRWFile {
|
||||
}
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* /*scratch*/) const {
|
||||
char* /*scratch*/) const override {
|
||||
*result = Slice(nullptr, 0);
|
||||
if (offset < ss_->contents_.size()) {
|
||||
size_t str_res_sz =
|
||||
@@ -268,11 +268,11 @@ class RandomRWStringSink : public RandomRWFile {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Flush() { return Status::OK(); }
|
||||
Status Flush() override { return Status::OK(); }
|
||||
|
||||
Status Sync() { return Status::OK(); }
|
||||
Status Sync() override { return Status::OK(); }
|
||||
|
||||
Status Close() { return Status::OK(); }
|
||||
Status Close() override { return Status::OK(); }
|
||||
|
||||
const std::string& contents() const { return ss_->contents(); }
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ void NTAPI WinOnThreadExit(PVOID module, DWORD reason, PVOID reserved) {
|
||||
// We decided to punt on PROCESS_EXIT
|
||||
if (DLL_THREAD_DETACH == reason) {
|
||||
if (thread_local_key != pthread_key_t(-1) && thread_local_inclass_routine != nullptr) {
|
||||
void* tls = pthread_getspecific(thread_local_key);
|
||||
void* tls = TlsGetValue(thread_local_key);
|
||||
if (tls != nullptr) {
|
||||
thread_local_inclass_routine(tls);
|
||||
}
|
||||
|
||||
@@ -87,9 +87,6 @@ void BlobDBOptions::Dump(Logger* log) const {
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " BlobDBOptions.blob_file_size: %" PRIu64,
|
||||
blob_file_size);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " BlobDBOptions.ttl_extractor: %p",
|
||||
ttl_extractor.get());
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " BlobDBOptions.compression: %d",
|
||||
static_cast<int>(compression));
|
||||
|
||||
@@ -18,8 +18,6 @@ namespace rocksdb {
|
||||
|
||||
namespace blob_db {
|
||||
|
||||
class TTLExtractor;
|
||||
|
||||
// A wrapped database which puts values of KV pairs in a separate log
|
||||
// and store location to the log in the underlying DB.
|
||||
// It lacks lots of importatant functionalities, e.g. DB restarts,
|
||||
@@ -67,11 +65,6 @@ struct BlobDBOptions {
|
||||
// after it exceeds that size
|
||||
uint64_t blob_file_size = 256 * 1024 * 1024;
|
||||
|
||||
// Instead of setting TTL explicitly by calling PutWithTTL or PutUntil,
|
||||
// applications can set a TTLExtractor which can extract TTL from key-value
|
||||
// pairs.
|
||||
std::shared_ptr<TTLExtractor> ttl_extractor = nullptr;
|
||||
|
||||
// what compression to use for Blob's
|
||||
CompressionType compression = kNoCompression;
|
||||
|
||||
@@ -151,6 +144,15 @@ class BlobDB : public StackableDB {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override = 0;
|
||||
|
||||
// Get value and expiration.
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, uint64_t* expiration) = 0;
|
||||
virtual Status Get(const ReadOptions& options, const Slice& key,
|
||||
PinnableSlice* value, uint64_t* expiration) {
|
||||
return Get(options, DefaultColumnFamily(), key, value, expiration);
|
||||
}
|
||||
|
||||
using rocksdb::StackableDB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
@@ -188,7 +190,6 @@ class BlobDB : public StackableDB {
|
||||
|
||||
virtual Status Write(const WriteOptions& opts,
|
||||
WriteBatch* updates) override = 0;
|
||||
|
||||
using rocksdb::StackableDB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& options) override = 0;
|
||||
virtual Iterator* NewIterator(const ReadOptions& options,
|
||||
@@ -228,33 +229,6 @@ class BlobDB : public StackableDB {
|
||||
Status DestroyBlobDB(const std::string& dbname, const Options& options,
|
||||
const BlobDBOptions& bdb_options);
|
||||
|
||||
// TTLExtractor allow applications to extract TTL from key-value pairs.
|
||||
// This useful for applications using Put or WriteBatch to write keys and
|
||||
// don't intend to migrate to PutWithTTL or PutUntil.
|
||||
//
|
||||
// Applications can implement either ExtractTTL or ExtractExpiration. If both
|
||||
// are implemented, ExtractExpiration will take precedence.
|
||||
class TTLExtractor {
|
||||
public:
|
||||
// Extract TTL from key-value pair.
|
||||
// Return true if the key has TTL, false otherwise. If key has TTL,
|
||||
// TTL is pass back through ttl. The method can optionally modify the value,
|
||||
// pass the result back through new_value, and also set value_changed to true.
|
||||
virtual bool ExtractTTL(const Slice& key, const Slice& value, uint64_t* ttl,
|
||||
std::string* new_value, bool* value_changed);
|
||||
|
||||
// Extract expiration time from key-value pair.
|
||||
// Return true if the key has expiration time, false otherwise. If key has
|
||||
// expiration time, it is pass back through expiration. The method can
|
||||
// optionally modify the value, pass the result back through new_value,
|
||||
// and also set value_changed to true.
|
||||
virtual bool ExtractExpiration(const Slice& key, const Slice& value,
|
||||
uint64_t now, uint64_t* expiration,
|
||||
std::string* new_value, bool* value_changed);
|
||||
|
||||
virtual ~TTLExtractor() = default;
|
||||
};
|
||||
|
||||
} // namespace blob_db
|
||||
} // namespace rocksdb
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -79,7 +79,6 @@ BlobDBImpl::BlobDBImpl(const std::string& dbname,
|
||||
dbname_(dbname),
|
||||
db_impl_(nullptr),
|
||||
env_(db_options.env),
|
||||
ttl_extractor_(blob_db_options.ttl_extractor.get()),
|
||||
bdb_options_(blob_db_options),
|
||||
db_options_(db_options),
|
||||
cf_options_(cf_options),
|
||||
@@ -213,8 +212,8 @@ void BlobDBImpl::StartBackgroundTasks() {
|
||||
tqueue_.add(kSanityCheckPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::SanityCheck, this, std::placeholders::_1));
|
||||
tqueue_.add(
|
||||
kCheckSeqFilesPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::CheckSeqFiles, this, std::placeholders::_1));
|
||||
kEvictExpiredFilesPeriodMillisecs,
|
||||
std::bind(&BlobDBImpl::EvictExpiredFiles, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
Status BlobDBImpl::GetAllBlobFiles(std::set<uint64_t>* file_numbers) {
|
||||
@@ -563,12 +562,8 @@ class BlobDBImpl::BlobInserter : public WriteBatch::Handler {
|
||||
return Status::NotSupported(
|
||||
"Blob DB doesn't support non-default column family.");
|
||||
}
|
||||
std::string new_value;
|
||||
Slice value_slice;
|
||||
uint64_t expiration =
|
||||
blob_db_impl_->ExtractExpiration(key, value, &value_slice, &new_value);
|
||||
Status s = blob_db_impl_->PutBlobValue(options_, key, value_slice,
|
||||
expiration, &batch_);
|
||||
Status s = blob_db_impl_->PutBlobValue(options_, key, value,
|
||||
kNoExpiration, &batch_);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -661,10 +656,7 @@ void BlobDBImpl::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) {
|
||||
|
||||
Status BlobDBImpl::Put(const WriteOptions& options, const Slice& key,
|
||||
const Slice& value) {
|
||||
std::string new_value;
|
||||
Slice value_slice;
|
||||
uint64_t expiration = ExtractExpiration(key, value, &value_slice, &new_value);
|
||||
return PutUntil(options, key, value_slice, expiration);
|
||||
return PutUntil(options, key, value, kNoExpiration);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::PutWithTTL(const WriteOptions& options,
|
||||
@@ -786,20 +778,6 @@ Slice BlobDBImpl::GetCompressedSlice(const Slice& raw,
|
||||
return *compression_output;
|
||||
}
|
||||
|
||||
uint64_t BlobDBImpl::ExtractExpiration(const Slice& key, const Slice& value,
|
||||
Slice* value_slice,
|
||||
std::string* new_value) {
|
||||
uint64_t expiration = kNoExpiration;
|
||||
bool has_expiration = false;
|
||||
bool value_changed = false;
|
||||
if (ttl_extractor_ != nullptr) {
|
||||
has_expiration = ttl_extractor_->ExtractExpiration(
|
||||
key, value, EpochNow(), &expiration, new_value, &value_changed);
|
||||
}
|
||||
*value_slice = value_changed ? Slice(*new_value) : value;
|
||||
return has_expiration ? expiration : kNoExpiration;
|
||||
}
|
||||
|
||||
void BlobDBImpl::GetCompactionContext(BlobCompactionContext* context) {
|
||||
ReadLock l(&mutex_);
|
||||
|
||||
@@ -990,7 +968,7 @@ bool BlobDBImpl::SetSnapshotIfNeeded(ReadOptions* read_options) {
|
||||
}
|
||||
|
||||
Status BlobDBImpl::GetBlobValue(const Slice& key, const Slice& index_entry,
|
||||
PinnableSlice* value) {
|
||||
PinnableSlice* value, uint64_t* expiration) {
|
||||
assert(value != nullptr);
|
||||
BlobIndex blob_index;
|
||||
Status s = blob_index.DecodeFrom(index_entry);
|
||||
@@ -1000,6 +978,13 @@ Status BlobDBImpl::GetBlobValue(const Slice& key, const Slice& index_entry,
|
||||
if (blob_index.HasTTL() && blob_index.expiration() <= EpochNow()) {
|
||||
return Status::NotFound("Key expired");
|
||||
}
|
||||
if (expiration != nullptr) {
|
||||
if (blob_index.HasTTL()) {
|
||||
*expiration = blob_index.expiration();
|
||||
} else {
|
||||
*expiration = kNoExpiration;
|
||||
}
|
||||
}
|
||||
if (blob_index.IsInlined()) {
|
||||
// TODO(yiwu): If index_entry is a PinnableSlice, we can also pin the same
|
||||
// memory buffer to avoid extra copy.
|
||||
@@ -1135,14 +1120,20 @@ Status BlobDBImpl::GetBlobValue(const Slice& key, const Slice& index_entry,
|
||||
Status BlobDBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return Get(read_options, column_family, key, value, nullptr /*expiration*/);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, uint64_t* expiration) {
|
||||
StopWatch get_sw(env_, statistics_, BLOB_DB_GET_MICROS);
|
||||
RecordTick(statistics_, BLOB_DB_NUM_GET);
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
return GetImpl(read_options, column_family, key, value, expiration);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
PinnableSlice* value, uint64_t* expiration) {
|
||||
if (column_family != DefaultColumnFamily()) {
|
||||
return Status::NotSupported(
|
||||
"Blob DB doesn't support non-default column family.");
|
||||
@@ -1160,10 +1151,13 @@ Status BlobDBImpl::GetImpl(const ReadOptions& read_options,
|
||||
&is_blob_index);
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:1");
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:2");
|
||||
if (expiration != nullptr) {
|
||||
*expiration = kNoExpiration;
|
||||
}
|
||||
if (s.ok() && is_blob_index) {
|
||||
std::string index_entry = value->ToString();
|
||||
value->Reset();
|
||||
s = GetBlobValue(key, index_entry, value);
|
||||
s = GetBlobValue(key, index_entry, value, expiration);
|
||||
}
|
||||
if (snapshot_created) {
|
||||
db_->ReleaseSnapshot(ro.snapshot);
|
||||
@@ -1276,11 +1270,11 @@ bool BlobDBImpl::VisibleToActiveSnapshot(
|
||||
// [earliest_sequence, obsolete_sequence). But doing so will make the
|
||||
// implementation more complicated.
|
||||
SequenceNumber obsolete_sequence = bfile->GetObsoleteSequence();
|
||||
SequenceNumber oldest_snapshot = 0;
|
||||
SequenceNumber oldest_snapshot = kMaxSequenceNumber;
|
||||
{
|
||||
// Need to lock DBImpl mutex before access snapshot list.
|
||||
InstrumentedMutexLock l(db_impl_->mutex());
|
||||
auto snapshots = db_impl_->snapshots();
|
||||
auto& snapshots = db_impl_->snapshots();
|
||||
if (!snapshots.empty()) {
|
||||
oldest_snapshot = snapshots.oldest()->GetSequenceNumber();
|
||||
}
|
||||
@@ -1288,29 +1282,38 @@ bool BlobDBImpl::VisibleToActiveSnapshot(
|
||||
return oldest_snapshot < obsolete_sequence;
|
||||
}
|
||||
|
||||
std::pair<bool, int64_t> BlobDBImpl::CheckSeqFiles(bool aborted) {
|
||||
if (aborted) return std::make_pair(false, -1);
|
||||
std::pair<bool, int64_t> BlobDBImpl::EvictExpiredFiles(bool aborted) {
|
||||
if (aborted) {
|
||||
return std::make_pair(false, -1);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<BlobFile>> process_files;
|
||||
uint64_t now = EpochNow();
|
||||
{
|
||||
uint64_t epoch_now = EpochNow();
|
||||
|
||||
ReadLock rl(&mutex_);
|
||||
for (auto bfile : open_ttl_files_) {
|
||||
{
|
||||
ReadLock lockbfile_r(&bfile->mutex_);
|
||||
|
||||
if (bfile->expiration_range_.second > epoch_now) {
|
||||
continue;
|
||||
}
|
||||
process_files.push_back(bfile);
|
||||
for (auto p : blob_files_) {
|
||||
auto& blob_file = p.second;
|
||||
ReadLock file_lock(&blob_file->mutex_);
|
||||
if (blob_file->HasTTL() && !blob_file->Obsolete() &&
|
||||
blob_file->GetExpirationRange().second <= now) {
|
||||
process_files.push_back(blob_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MutexLock l(&write_mutex_);
|
||||
for (auto bfile : process_files) {
|
||||
CloseBlobFile(bfile);
|
||||
SequenceNumber seq = GetLatestSequenceNumber();
|
||||
{
|
||||
MutexLock l(&write_mutex_);
|
||||
for (auto& blob_file : process_files) {
|
||||
WriteLock file_lock(&blob_file->mutex_);
|
||||
if (!blob_file->Immutable()) {
|
||||
CloseBlobFile(blob_file, false /*need_lock*/);
|
||||
}
|
||||
// Need to double check if the file is obsolete.
|
||||
if (!blob_file->Obsolete()) {
|
||||
ObsoleteBlobFile(blob_file, seq, true /*update_size*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(true, -1);
|
||||
@@ -1587,8 +1590,8 @@ Status BlobDBImpl::GCFileAndUpdateLSM(const std::shared_ptr<BlobFile>& bfptr,
|
||||
}
|
||||
|
||||
// We don't add the file to open_ttl_files_ or open_non_ttl_files_, to
|
||||
// avoid user writes writing to the file, and avoid CheckSeqFiles close
|
||||
// the file by mistake.
|
||||
// avoid user writes writing to the file, and avoid
|
||||
// EvictExpiredFiles close the file by mistake.
|
||||
WriteLock wl(&mutex_);
|
||||
blob_files_.insert(std::make_pair(newfile->BlobFileNumber(), newfile));
|
||||
}
|
||||
@@ -1857,6 +1860,10 @@ Status BlobDBImpl::TEST_GCFileAndUpdateLSM(std::shared_ptr<BlobFile>& bfile,
|
||||
|
||||
void BlobDBImpl::TEST_RunGC() { RunGC(false /*abort*/); }
|
||||
|
||||
void BlobDBImpl::TEST_EvictExpiredFiles() {
|
||||
EvictExpiredFiles(false /*abort*/);
|
||||
}
|
||||
|
||||
uint64_t BlobDBImpl::TEST_live_sst_size() { return live_sst_size_.load(); }
|
||||
#endif // !NDEBUG
|
||||
|
||||
|
||||
@@ -115,8 +115,8 @@ class BlobDBImpl : public BlobDB {
|
||||
// how often to schedule delete obs files periods
|
||||
static constexpr uint32_t kDeleteObsoleteFilesPeriodMillisecs = 10 * 1000;
|
||||
|
||||
// how often to schedule check seq files period
|
||||
static constexpr uint32_t kCheckSeqFilesPeriodMillisecs = 10 * 1000;
|
||||
// how often to schedule expired files eviction.
|
||||
static constexpr uint32_t kEvictExpiredFilesPeriodMillisecs = 10 * 1000;
|
||||
|
||||
// when should oldest file be evicted:
|
||||
// on reaching 90% of blob_dir_size
|
||||
@@ -130,6 +130,10 @@ class BlobDBImpl : public BlobDB {
|
||||
Status Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status Get(const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
uint64_t* expiration) override;
|
||||
|
||||
using BlobDB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& read_options) override;
|
||||
|
||||
@@ -200,6 +204,8 @@ class BlobDBImpl : public BlobDB {
|
||||
|
||||
void TEST_RunGC();
|
||||
|
||||
void TEST_EvictExpiredFiles();
|
||||
|
||||
void TEST_DeleteObsoleteFiles();
|
||||
|
||||
uint64_t TEST_live_sst_size();
|
||||
@@ -215,10 +221,10 @@ class BlobDBImpl : public BlobDB {
|
||||
|
||||
Status GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value);
|
||||
PinnableSlice* value, uint64_t* expiration = nullptr);
|
||||
|
||||
Status GetBlobValue(const Slice& key, const Slice& index_entry,
|
||||
PinnableSlice* value);
|
||||
PinnableSlice* value, uint64_t* expiration = nullptr);
|
||||
|
||||
Slice GetCompressedSlice(const Slice& raw,
|
||||
std::string* compression_output) const;
|
||||
@@ -235,9 +241,6 @@ class BlobDBImpl : public BlobDB {
|
||||
void ObsoleteBlobFile(std::shared_ptr<BlobFile> blob_file,
|
||||
SequenceNumber obsolete_seq, bool update_size);
|
||||
|
||||
uint64_t ExtractExpiration(const Slice& key, const Slice& value,
|
||||
Slice* value_slice, std::string* new_value);
|
||||
|
||||
Status PutBlobValue(const WriteOptions& options, const Slice& key,
|
||||
const Slice& value, uint64_t expiration,
|
||||
WriteBatch* batch);
|
||||
@@ -269,7 +272,7 @@ class BlobDBImpl : public BlobDB {
|
||||
|
||||
// periodically check if open blob files and their TTL's has expired
|
||||
// if expired, close the sequential writer and make the file immutable
|
||||
std::pair<bool, int64_t> CheckSeqFiles(bool aborted);
|
||||
std::pair<bool, int64_t> EvictExpiredFiles(bool aborted);
|
||||
|
||||
// if the number of open files, approaches ULIMIT's this
|
||||
// task will close random readers, which are kept around for
|
||||
@@ -337,7 +340,6 @@ class BlobDBImpl : public BlobDB {
|
||||
// the base DB
|
||||
DBImpl* db_impl_;
|
||||
Env* env_;
|
||||
TTLExtractor* ttl_extractor_;
|
||||
|
||||
// the options that govern the behavior of Blob Storage
|
||||
BlobDBOptions bdb_options_;
|
||||
|
||||
@@ -235,7 +235,6 @@ class BlobDBTest : public testing::Test {
|
||||
|
||||
const std::string dbname_;
|
||||
std::unique_ptr<MockTimeEnv> mock_env_;
|
||||
std::shared_ptr<TTLExtractor> ttl_extractor_;
|
||||
BlobDB *blob_db_;
|
||||
}; // class BlobDBTest
|
||||
|
||||
@@ -312,191 +311,6 @@ TEST_F(BlobDBTest, PutUntil) {
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, TTLExtrator_NoTTL) {
|
||||
// The default ttl extractor return no ttl for every key.
|
||||
ttl_extractor_.reset(new TTLExtractor());
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.ttl_range_secs = 1000;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.blob_file_size = 256 * 1000 * 1000;
|
||||
bdb_options.ttl_extractor = ttl_extractor_;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options, options);
|
||||
std::map<std::string, std::string> data;
|
||||
mock_env_->set_current_time(0);
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
PutRandom("key" + ToString(i), &rnd, &data);
|
||||
}
|
||||
// very far in the future..
|
||||
mock_env_->set_current_time(std::numeric_limits<uint64_t>::max() / 1000000 -
|
||||
10);
|
||||
auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_FALSE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(0, gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(100, gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, TTLExtractor_ExtractTTL) {
|
||||
Random rnd(301);
|
||||
class TestTTLExtractor : public TTLExtractor {
|
||||
public:
|
||||
explicit TestTTLExtractor(Random *r) : rnd(r) {}
|
||||
|
||||
virtual bool ExtractTTL(const Slice &key, const Slice &value, uint64_t *ttl,
|
||||
std::string * /*new_value*/,
|
||||
bool * /*value_changed*/) override {
|
||||
*ttl = rnd->Next() % 100;
|
||||
if (*ttl > 50) {
|
||||
data[key.ToString()] = value.ToString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Random *rnd;
|
||||
std::map<std::string, std::string> data;
|
||||
};
|
||||
ttl_extractor_.reset(new TestTTLExtractor(&rnd));
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.ttl_range_secs = 1000;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.blob_file_size = 256 * 1000 * 1000;
|
||||
bdb_options.ttl_extractor = ttl_extractor_;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options, options);
|
||||
mock_env_->set_current_time(50);
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
PutRandom("key" + ToString(i), &rnd);
|
||||
}
|
||||
mock_env_->set_current_time(100);
|
||||
auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
auto &data = static_cast<TestTTLExtractor *>(ttl_extractor_.get())->data;
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(data.size(), gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, TTLExtractor_ExtractExpiration) {
|
||||
Random rnd(301);
|
||||
class TestTTLExtractor : public TTLExtractor {
|
||||
public:
|
||||
explicit TestTTLExtractor(Random *r) : rnd(r) {}
|
||||
|
||||
virtual bool ExtractExpiration(const Slice &key, const Slice &value,
|
||||
uint64_t /*now*/, uint64_t *expiration,
|
||||
std::string * /*new_value*/,
|
||||
bool * /*value_changed*/) override {
|
||||
*expiration = rnd->Next() % 100 + 50;
|
||||
if (*expiration > 100) {
|
||||
data[key.ToString()] = value.ToString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Random *rnd;
|
||||
std::map<std::string, std::string> data;
|
||||
};
|
||||
ttl_extractor_.reset(new TestTTLExtractor(&rnd));
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.ttl_range_secs = 1000;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.blob_file_size = 256 * 1000 * 1000;
|
||||
bdb_options.ttl_extractor = ttl_extractor_;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options, options);
|
||||
mock_env_->set_current_time(50);
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
PutRandom("key" + ToString(i), &rnd);
|
||||
}
|
||||
mock_env_->set_current_time(100);
|
||||
auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
auto &data = static_cast<TestTTLExtractor *>(ttl_extractor_.get())->data;
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(data.size(), gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, TTLExtractor_ChangeValue) {
|
||||
class TestTTLExtractor : public TTLExtractor {
|
||||
public:
|
||||
const Slice kTTLSuffix = Slice("ttl:");
|
||||
|
||||
bool ExtractTTL(const Slice & /*key*/, const Slice &value, uint64_t *ttl,
|
||||
std::string *new_value, bool *value_changed) override {
|
||||
if (value.size() < 12) {
|
||||
return false;
|
||||
}
|
||||
const char *p = value.data() + value.size() - 12;
|
||||
if (kTTLSuffix != Slice(p, 4)) {
|
||||
return false;
|
||||
}
|
||||
*ttl = DecodeFixed64(p + 4);
|
||||
*new_value = Slice(value.data(), value.size() - 12).ToString();
|
||||
*value_changed = true;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.ttl_range_secs = 1000;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.blob_file_size = 256 * 1000 * 1000;
|
||||
bdb_options.ttl_extractor = std::make_shared<TestTTLExtractor>();
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options, options);
|
||||
std::map<std::string, std::string> data;
|
||||
mock_env_->set_current_time(50);
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
int len = rnd.Next() % kMaxBlobSize + 1;
|
||||
std::string key = "key" + ToString(i);
|
||||
std::string value = test::RandomHumanReadableString(&rnd, len);
|
||||
uint64_t ttl = rnd.Next() % 100;
|
||||
std::string value_ttl = value + "ttl:";
|
||||
PutFixed64(&value_ttl, ttl);
|
||||
ASSERT_OK(blob_db_->Put(WriteOptions(), Slice(key), Slice(value_ttl)));
|
||||
if (ttl > 50) {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
mock_env_->set_current_time(100);
|
||||
auto *bdb_impl = static_cast<BlobDBImpl *>(blob_db_);
|
||||
auto blob_files = bdb_impl->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(data.size(), gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, StackableDBGet) {
|
||||
Random rnd(301);
|
||||
BlobDBOptions bdb_options;
|
||||
@@ -520,6 +334,25 @@ TEST_F(BlobDBTest, StackableDBGet) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GetExpiration) {
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
mock_env_->set_current_time(100);
|
||||
Open(bdb_options, options);
|
||||
Put("key1", "value1");
|
||||
PutWithTTL("key2", "value2", 200);
|
||||
PinnableSlice value;
|
||||
uint64_t expiration;
|
||||
ASSERT_OK(blob_db_->Get(ReadOptions(), "key1", &value, &expiration));
|
||||
ASSERT_EQ("value1", value.ToString());
|
||||
ASSERT_EQ(kNoExpiration, expiration);
|
||||
ASSERT_OK(blob_db_->Get(ReadOptions(), "key2", &value, &expiration));
|
||||
ASSERT_EQ("value2", value.ToString());
|
||||
ASSERT_EQ(300 /* = 100 + 200 */, expiration);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, WriteBatch) {
|
||||
Random rnd(301);
|
||||
BlobDBOptions bdb_options;
|
||||
@@ -1548,6 +1381,36 @@ TEST_F(BlobDBTest, FilterForFIFOEviction) {
|
||||
VerifyDB(data_after_compact);
|
||||
}
|
||||
|
||||
// File should be evicted after expiration.
|
||||
TEST_F(BlobDBTest, EvictExpiredFile) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.ttl_range_secs = 100;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
Open(bdb_options, options);
|
||||
mock_env_->set_current_time(50);
|
||||
std::map<std::string, std::string> data;
|
||||
ASSERT_OK(PutWithTTL("foo", "bar", 100, &data));
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
auto blob_file = blob_files[0];
|
||||
ASSERT_FALSE(blob_file->Immutable());
|
||||
ASSERT_FALSE(blob_file->Obsolete());
|
||||
VerifyDB(data);
|
||||
mock_env_->set_current_time(250);
|
||||
// The key should expired now.
|
||||
blob_db_impl()->TEST_EvictExpiredFiles();
|
||||
ASSERT_EQ(1, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
ASSERT_EQ(1, blob_db_impl()->TEST_GetObsoleteFiles().size());
|
||||
ASSERT_TRUE(blob_file->Immutable());
|
||||
ASSERT_TRUE(blob_file->Obsolete());
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
ASSERT_EQ(0, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
ASSERT_EQ(0, blob_db_impl()->TEST_GetObsoleteFiles().size());
|
||||
}
|
||||
|
||||
} // namespace blob_db
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "utilities/blob_db/blob_db.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace blob_db {
|
||||
|
||||
bool TTLExtractor::ExtractTTL(const Slice& /*key*/, const Slice& /*value*/,
|
||||
uint64_t* /*ttl*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TTLExtractor::ExtractExpiration(const Slice& key, const Slice& value,
|
||||
uint64_t now, uint64_t* expiration,
|
||||
std::string* new_value,
|
||||
bool* value_changed) {
|
||||
uint64_t ttl;
|
||||
bool has_ttl = ExtractTTL(key, value, &ttl, new_value, value_changed);
|
||||
if (has_ttl) {
|
||||
*expiration = now + ttl;
|
||||
}
|
||||
return has_ttl;
|
||||
}
|
||||
|
||||
} // namespace blob_db
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -20,7 +20,7 @@ class SequentialFileMirror : public SequentialFile {
|
||||
std::string fname;
|
||||
explicit SequentialFileMirror(std::string f) : fname(f) {}
|
||||
|
||||
Status Read(size_t n, Slice* result, char* scratch) {
|
||||
Status Read(size_t n, Slice* result, char* scratch) override {
|
||||
Slice aslice;
|
||||
Status as = a_->Read(n, &aslice, scratch);
|
||||
if (as == Status::OK()) {
|
||||
@@ -44,13 +44,13 @@ class SequentialFileMirror : public SequentialFile {
|
||||
return as;
|
||||
}
|
||||
|
||||
Status Skip(uint64_t n) {
|
||||
Status Skip(uint64_t n) override {
|
||||
Status as = a_->Skip(n);
|
||||
Status bs = b_->Skip(n);
|
||||
assert(as == bs);
|
||||
return as;
|
||||
}
|
||||
Status InvalidateCache(size_t offset, size_t length) {
|
||||
Status InvalidateCache(size_t offset, size_t length) override {
|
||||
Status as = a_->InvalidateCache(offset, length);
|
||||
Status bs = b_->InvalidateCache(offset, length);
|
||||
assert(as == bs);
|
||||
@@ -64,7 +64,7 @@ class RandomAccessFileMirror : public RandomAccessFile {
|
||||
std::string fname;
|
||||
explicit RandomAccessFileMirror(std::string f) : fname(f) {}
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const {
|
||||
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const override {
|
||||
Status as = a_->Read(offset, n, result, scratch);
|
||||
if (as == Status::OK()) {
|
||||
char* bscratch = new char[n];
|
||||
@@ -86,7 +86,7 @@ class RandomAccessFileMirror : public RandomAccessFile {
|
||||
return as;
|
||||
}
|
||||
|
||||
size_t GetUniqueId(char* id, size_t max_size) const {
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
// NOTE: not verified
|
||||
return a_->GetUniqueId(id, max_size);
|
||||
}
|
||||
|
||||
@@ -251,20 +251,20 @@ class PersistentCacheTier : public PersistentCache {
|
||||
// Print stats to string recursively
|
||||
virtual std::string PrintStats();
|
||||
|
||||
virtual PersistentCache::StatsType Stats();
|
||||
virtual PersistentCache::StatsType Stats() override;
|
||||
|
||||
// Insert to page cache
|
||||
virtual Status Insert(const Slice& page_key, const char* data,
|
||||
const size_t size) = 0;
|
||||
const size_t size) override = 0;
|
||||
|
||||
// Lookup page cache by page identifier
|
||||
virtual Status Lookup(const Slice& page_key, std::unique_ptr<char[]>* data,
|
||||
size_t* size) = 0;
|
||||
size_t* size) override = 0;
|
||||
|
||||
// Does it store compressed data ?
|
||||
virtual bool IsCompressed() = 0;
|
||||
virtual bool IsCompressed() override = 0;
|
||||
|
||||
virtual std::string GetPrintableOptions() const = 0;
|
||||
virtual std::string GetPrintableOptions() const override = 0;
|
||||
|
||||
// Return a reference to next tier
|
||||
virtual Tier& next_tier() { return next_tier_; }
|
||||
|
||||
Reference in new issue
Block a user