mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3513d4e93f | ||
|
|
76a56d89a7 | ||
|
|
dd757ffde0 | ||
|
|
fdc56e2b73 | ||
|
|
cfd6eb3e68 | ||
|
|
9623a66b24 | ||
|
|
47f0d5fdd0 | ||
|
|
3542f7ef00 | ||
|
|
5c12a474f3 | ||
|
|
6d113fc066 | ||
|
|
11afcbe7ba | ||
|
|
b6e554e698 | ||
|
|
a1f08cc953 | ||
|
|
0d9bfa6e4d | ||
|
|
9feb730c6e | ||
|
|
70dca18c96 | ||
|
|
5f703af1ea | ||
|
|
8baa66acf6 | ||
|
|
570d490a3d | ||
|
|
55320dedab | ||
|
|
03c7ae24c2 | ||
|
|
a5debd7ed8 | ||
|
|
b02d0c238d | ||
|
|
25810ca9c7 | ||
|
|
210b49cac9 | ||
|
|
18864567c8 | ||
|
|
35e6ba734e | ||
|
|
3548e4220d | ||
|
|
8c7eb59838 | ||
|
|
506e8448be | ||
|
|
6eb317bb4c | ||
|
|
084a3c697c | ||
|
|
da96f2fe00 | ||
|
|
66d8360beb | ||
|
|
cd77d3c558 | ||
|
|
aa56b7e74a | ||
|
|
1c8cbf315f | ||
|
|
a7d103198e | ||
|
|
990b2f4cb3 | ||
|
|
72c8533f2c | ||
|
|
78a6e07c83 | ||
|
|
47fd574829 | ||
|
|
8272a6de57 | ||
|
|
df38c1ce66 | ||
|
|
de76909464 | ||
|
|
dc64c2f5cc | ||
|
|
c77aab584e | ||
|
|
5265c5709e | ||
|
|
7a73adda9c | ||
|
|
efa948741c | ||
|
|
3bdce20e2b | ||
|
|
d6862b3f51 | ||
|
|
01cfea6637 | ||
|
|
992dfc7811 | ||
|
|
6c2bf9e916 | ||
|
|
392f6d49e5 | ||
|
|
5b7e09bd6f | ||
|
|
248b6b551e | ||
|
|
baa5302447 | ||
|
|
d9280ff2d2 | ||
|
|
beb44ec3eb | ||
|
|
f1239d5f10 | ||
|
|
71a82a0abe | ||
|
|
3e63e553b4 | ||
|
|
b70967aac7 | ||
|
|
b5cad5c986 | ||
|
|
8295d364e2 | ||
|
|
29111e92b4 | ||
|
|
cca141ecf8 | ||
|
|
d655a3aab7 | ||
|
|
fe642cbee6 | ||
|
|
1966a7c055 | ||
|
|
85b2bde3dd | ||
|
|
3189398c00 | ||
|
|
f49e12b892 | ||
|
|
fefd4b98c5 | ||
|
|
ed9f5e21aa | ||
|
|
d3d20dcdca | ||
|
|
ef0fc1b461 | ||
|
|
313e877285 | ||
|
|
0bb555630f | ||
|
|
de00f28132 | ||
|
|
39c6c5fc1b | ||
|
|
479c566771 | ||
|
|
8d1e52165d | ||
|
|
59ef2ba559 | ||
|
|
306b9adfd8 | ||
|
|
c06c4c01c5 | ||
|
|
f0edf9d575 | ||
|
|
75e8b6dfcf | ||
|
|
7441a0ecba | ||
|
|
ebb9b2ed16 | ||
|
|
e8480d4d9d | ||
|
|
5234fc1b70 | ||
|
|
26015f3b48 | ||
|
|
14b3f683a1 | ||
|
|
d9d3cacaf5 | ||
|
|
ebcc8ae1d3 | ||
|
|
fa1b558299 | ||
|
|
120bc4715b | ||
|
|
127a850beb | ||
|
|
a703f16da9 | ||
|
|
f29dc1b906 | ||
|
|
09957ded1d | ||
|
|
d77476ef55 | ||
|
|
dae3b5545c | ||
|
|
a98317f555 | ||
|
|
106a94af15 | ||
|
|
d1edf4eced | ||
|
|
89ab1381f8 | ||
|
|
1f7f5a5a79 | ||
|
|
5f6adf3f6a | ||
|
|
8c072044d2 | ||
|
|
d69241586e | ||
|
|
2b4d5ceb47 | ||
|
|
9358178edc | ||
|
|
2a5463ae84 | ||
|
|
01e6badbb6 | ||
|
|
4774a9409b | ||
|
|
fe2bd190a5 | ||
|
|
75133b1b6b | ||
|
|
7ca9eb7542 | ||
|
|
3c5eed5ebe | ||
|
|
48e7effa79 | ||
|
|
52e6404e0f | ||
|
|
a4396f9218 | ||
|
|
88d85b6820 | ||
|
|
c84fad7a19 | ||
|
|
34f8ac0c99 | ||
|
|
1721635f76 | ||
|
|
959f86e5f8 | ||
|
|
a291f3a1e5 | ||
|
|
e50326f327 | ||
|
|
36c2a7cfb1 | ||
|
|
f2f6acbef3 | ||
|
|
b45b1cde3e | ||
|
|
20d49da90c | ||
|
|
b4fa51dfaf | ||
|
|
2263f86901 | ||
|
|
dcde292c3b | ||
|
|
fdc72a5c5d | ||
|
|
5a5c0492db | ||
|
|
8a1ecd1982 | ||
|
|
c4e00cf10a | ||
|
|
05ebfebc17 | ||
|
|
62eb2c23aa | ||
|
|
04d3ac4e63 | ||
|
|
79b6ab43ce | ||
|
|
04a2631dbe | ||
|
|
703f1375c2 | ||
|
|
186b3afaa8 | ||
|
|
a283800616 | ||
|
|
9a31b8dd2c | ||
|
|
0920bf4e68 | ||
|
|
18d2e4beb7 | ||
|
|
aef763b6d6 | ||
|
|
916e524134 | ||
|
|
0b80f6b380 | ||
|
|
77ebc82b92 | ||
|
|
68a2f94d5d | ||
|
|
06ea73d60b | ||
|
|
f83eecff99 | ||
|
|
b157d3d128 | ||
|
|
5e298f865b | ||
|
|
a661c0d208 | ||
|
|
bb474e9a02 | ||
|
|
06f378d75e |
No files matched your search
+9
-4
@@ -92,7 +92,7 @@ else()
|
||||
endif()
|
||||
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
if(ZLIB_INCLUDE_DIRS)
|
||||
# CMake 3
|
||||
@@ -317,6 +317,10 @@ if(DISABLE_STALL_NOTIF)
|
||||
add_definitions(-DROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
endif()
|
||||
|
||||
option(WITH_DYNAMIC_EXTENSION "build with dynamic extension support" OFF)
|
||||
if(NOT WITH_DYNAMIC_EXTENSION)
|
||||
add_definitions(-DROCKSDB_NO_DYNAMIC_EXTENSION)
|
||||
endif()
|
||||
|
||||
if(DEFINED USE_RTTI)
|
||||
if(USE_RTTI)
|
||||
@@ -416,7 +420,7 @@ if(WITH_FALLOCATE)
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
int fd = open(\"/dev/null\", 0);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
|
||||
}
|
||||
" HAVE_FALLOCATE)
|
||||
if(HAVE_FALLOCATE)
|
||||
@@ -461,8 +465,6 @@ include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest)
|
||||
|
||||
# Main library source code
|
||||
|
||||
set(SOURCES
|
||||
@@ -489,6 +491,7 @@ set(SOURCES
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_impl_secondary.cc
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
db/dbformat.cc
|
||||
@@ -842,6 +845,7 @@ endif()
|
||||
|
||||
option(WITH_TESTS "build with tests" ON)
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(third-party/gtest-1.7.0/fused-src/gtest)
|
||||
set(TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
@@ -873,6 +877,7 @@ if(WITH_TESTS)
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_secondary_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_statistics_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
|
||||
+56
-9
@@ -1,22 +1,69 @@
|
||||
# Rocksdb Change Log
|
||||
## 6.2.4 (9/18/2019)
|
||||
### Bug Fixes
|
||||
* Disable snap_refresh_nanos by default. The feature is to be deprecated in the next release.
|
||||
|
||||
## 6.0.2 (4/23/2019)
|
||||
## Bug Fixes
|
||||
* Fix build failures due to missing JEMALLOC_CXX_THROW macro (#5053)
|
||||
* Fix compilation on db_bench_tool.cc on Windows (#5227)
|
||||
## 6.2.3 (9/3/2019)
|
||||
### Bug Fixes
|
||||
* Fix a bug in file ingestion caused by incorrect file number allocation when the number of column families involved in the ingestion exceeds 2.
|
||||
|
||||
## 6.0.1 (3/26/2019)
|
||||
## 6.2.2 (6/7/2019)
|
||||
### Bug Fixes
|
||||
* Disable dynamic extension support by default for CMake.
|
||||
|
||||
## 6.2.1 (6/4/2019)
|
||||
### Bug Fixes
|
||||
* Fix flush's/compaction's merge processing logic which allowed `Put`s covered by range tombstones to reappear. Note `Put`s may exist even if the user only ever called `Merge()` due to an internal conversion during compaction to the bottommost level.
|
||||
|
||||
## 6.2.0 (4/30/2019)
|
||||
### New Features
|
||||
* Add an option `strict_bytes_per_sync` that causes a file-writing thread to block rather than exceed the limit on bytes pending writeback specified by `bytes_per_sync` or `wal_bytes_per_sync`.
|
||||
* Improve range scan performance by avoiding per-key upper bound check in BlockBasedTableIterator.
|
||||
* Introduce Periodic Compaction for Level style compaction. Files are re-compacted periodically and put in the same level.
|
||||
* Block-based table index now contains exact highest key in the file, rather than an upper bound. This may improve Get() and iterator Seek() performance in some situations, especially when direct IO is enabled and block cache is disabled. A setting BlockBasedTableOptions::index_shortening is introduced to control this behavior. Set it to kShortenSeparatorsAndSuccessor to get the old behavior.
|
||||
* When reading from option file/string/map, customized envs can be filled according to object registry.
|
||||
* Add an option `snap_refresh_nanos` (default to 0.5s) to periodically refresh the snapshot list in compaction jobs. Assign to 0 to disable the feature.
|
||||
* Improve range scan performance when using explicit user readahead by not creating new table readers for every iterator.
|
||||
|
||||
### Public API Change
|
||||
* Added many new features to the Java API to bring it closer to the C++ API.
|
||||
* Change the behavior of OptimizeForPointLookup(): move away from hash-based block-based-table index, and use whole key memtable filtering.
|
||||
* Change the behavior of OptimizeForSmallDb(): use a 16MB block cache, put index and filter blocks into it, and cost the memtable size to it. DBOptions.OptimizeForSmallDb() and ColumnFamilyOptions.OptimizeForSmallDb() start to take an optional cache object.
|
||||
* Added BottommostLevelCompaction::kForceOptimized to avoid double compacting newly compacted files in the bottommost level compaction of manual compaction. Note this option may prohibit the manual compaction to produce a single file in the bottommost level.
|
||||
|
||||
### Bug Fixes
|
||||
* Make BlobDB wait for all background tasks on shutdown.
|
||||
* Fixed a BlobDB issue where some trash files are not tracked causing them to remain forever.
|
||||
* Adjust WriteBufferManager's dummy entry size to block cache from 1MB to 256KB.
|
||||
* Fix a race condition between WritePrepared::Get and ::Put with duplicate keys.
|
||||
* Fix crash when memtable prefix bloom is enabled and read/write a key out of domain of prefix extractor.
|
||||
* Close a WAL file before another thread deletes it.
|
||||
* Fix an assertion failure `IsFlushPending() == true` caused by one bg thread releasing the db mutex in ~ColumnFamilyData and another thread clearing `flush_requested_` flag.
|
||||
|
||||
## 6.1.1 (4/9/2019)
|
||||
### New Features
|
||||
* When reading from option file/string/map, customized comparators and/or merge operators can be filled according to object registry.
|
||||
|
||||
### Public API Change
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in 2PC where a sequence of txn prepare, memtable flush, and crash could result in losing the prepared transaction.
|
||||
* Fix a bug in Encryption Env which could cause encrypted files to be read beyond file boundaries.
|
||||
|
||||
## 6.1.0 (3/27/2019)
|
||||
### New Features
|
||||
* Introduce two more stats levels, kExceptHistogramOrTimers and kExceptTimers.
|
||||
* Added a feature to perform data-block sampling for compressibility, and report stats to user.
|
||||
* Add support for trace filtering.
|
||||
* Add DBOptions.avoid_unnecessary_blocking_io. If true, we avoid file deletion when destorying ColumnFamilyHandle and Iterator. Instead, a job is scheduled to delete the files in background.
|
||||
|
||||
### Public API Change
|
||||
* Remove bundled fbson library.
|
||||
* statistics.stats_level_ becomes atomic. It is preferred to use statistics.set_stats_level() and statistics.get_stats_level() to access it.
|
||||
* Introduce a new IOError subcode, PathNotFound, to indicate trying to open a nonexistent file or directory for read.
|
||||
* Add initial support for multiple db instances sharing the same data in single-writer, multi-reader mode.
|
||||
* Removed some "using std::xxx" from public headers.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix JEMALLOC_CXX_THROW macro missing from older Jemalloc versions, causing build failures on some platforms.
|
||||
|
||||
* Fix SstFileReader not able to open file ingested with write_glbal_seqno=true.
|
||||
|
||||
## 6.0.0 (2/19/2019)
|
||||
### New Features
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* Java - https://github.com/facebook/rocksdb/tree/master/java
|
||||
* Python - http://pyrocksdb.readthedocs.org/en/latest/
|
||||
* Python
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
* Perl - https://metacpan.org/pod/RocksDB
|
||||
* Node.js - https://npmjs.org/package/rocksdb
|
||||
* Go - https://github.com/tecbot/gorocksdb
|
||||
|
||||
@@ -137,6 +137,12 @@ CFLAGS += -DHAVE_POWER8
|
||||
HAVE_POWER8=1
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -march=armv8-a+crc -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -march=armv8-a+crc
|
||||
CFLAGS += -march=armv8-a+crc
|
||||
ARMCRC_SOURCE=1
|
||||
endif
|
||||
|
||||
# if we're compiling for release, compile without debug code (-DNDEBUG)
|
||||
ifeq ($(DEBUG_LEVEL),0)
|
||||
OPT += -DNDEBUG
|
||||
@@ -443,6 +449,7 @@ TESTS = \
|
||||
db_merge_operator_test \
|
||||
db_options_test \
|
||||
db_range_del_test \
|
||||
db_secondary_test \
|
||||
db_sst_test \
|
||||
db_tailing_iter_test \
|
||||
db_io_failure_test \
|
||||
@@ -547,6 +554,7 @@ TESTS = \
|
||||
range_tombstone_fragmenter_test \
|
||||
range_del_aggregator_test \
|
||||
sst_file_reader_test \
|
||||
db_secondary_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
@@ -1571,6 +1579,9 @@ range_tombstone_fragmenter_test: db/range_tombstone_fragmenter_test.o db/db_test
|
||||
sst_file_reader_test: table/sst_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_secondary_test: db/db_secondary_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com)
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
and Jeff Dean (jeff@google.com)
|
||||
|
||||
This code is a library that forms the core building block for a fast
|
||||
|
||||
@@ -98,6 +98,7 @@ cpp_library(
|
||||
"db/db_impl_files.cc",
|
||||
"db/db_impl_open.cc",
|
||||
"db/db_impl_readonly.cc",
|
||||
"db/db_impl_secondary.cc",
|
||||
"db/db_impl_write.cc",
|
||||
"db/db_info_dumper.cc",
|
||||
"db/db_iter.cc",
|
||||
@@ -605,6 +606,11 @@ ROCKS_TESTS = [
|
||||
"db/db_range_del_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_secondary_test",
|
||||
"db/db_secondary_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_sst_test",
|
||||
"db/db_sst_test.cc",
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ We plan to use this port for our business purposes here at Bing and this provide
|
||||
|
||||
* Certain headers that are not present and not necessary on Windows were simply `#ifndef OS_WIN` in a few places (`unistd.h`)
|
||||
* All posix specific headers were replaced to port/port.h which worked well
|
||||
* Replaced `dirent.h` for `port/dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `dirent.h` for `port/port_dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port`
|
||||
* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `zu` on posix systems and to `Iu` on windows.
|
||||
* in class member initialization were moved to a __ctors in some cases
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
@@ -81,7 +82,7 @@ def get_tests(repo_path):
|
||||
else:
|
||||
# we consumed all the parallel tests
|
||||
break
|
||||
|
||||
|
||||
return tests
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# Create a tmp directory for the test to use
|
||||
TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX)
|
||||
# shellcheck disable=SC2068
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
"""
|
||||
This module keeps commonly used components.
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
# amalgamate.py creates an amalgamation from a unity build.
|
||||
# It can be run with either Python 2 or 3.
|
||||
|
||||
@@ -234,7 +234,7 @@ else
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, 1024);
|
||||
fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 1024);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -518,8 +518,8 @@ if test "$USE_HDFS"; then
|
||||
echo "JAVA_HOME has to be set for HDFS usage."
|
||||
exit 1
|
||||
fi
|
||||
HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64"
|
||||
HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS -I$HADOOP_HOME/include"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64 -L$HADOOP_HOME/lib/native"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -ldl -lverify -ljava -ljvm"
|
||||
COMMON_FLAGS="$COMMON_FLAGS $HDFS_CCFLAGS"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/112ec378fec7002ad3e09afde022e656049f7191/5.x/centos7-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/04999bdb3ce81a11073535dcb00b5e13dc1cbaf5/stable/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/92b0c8e5c8eecc71eb042594ce1ab3413799b385/5.x/gcc-5-glibc-2.23/339d858
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# shellcheck disable=SC2148
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
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
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
docker run -v $PWD:/rocks -w /rocks buildpack-deps make
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# fail early
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ compiler and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# If clang_format_diff.py command is not specfied, we assume we are able to
|
||||
# access directly without any path.
|
||||
if [ -z $CLANG_FORMAT_DIFF ]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# shellcheck disable=SC1113
|
||||
#/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/local/fbcode/gcc-4.9-glibc-2.20-fb/bin/python2.7
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# This script enables you running RocksDB tests by running
|
||||
# All the tests concurrently and utilizing all the cores
|
||||
Param(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
set -e
|
||||
|
||||
ROCKSDB_VERSION="5.10.3"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Update dependencies.sh file with the latest avaliable versions
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
if [ "$#" = "0" ]; then
|
||||
echo "Usage: $0 major|minor|patch|full"
|
||||
exit 1
|
||||
|
||||
Vendored
+8
-7
@@ -98,7 +98,7 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Lookup(shared_ptr<Cache> cache, int key) {
|
||||
int Lookup(std::shared_ptr<Cache> cache, int key) {
|
||||
Cache::Handle* handle = cache->Lookup(EncodeKey(key));
|
||||
const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle));
|
||||
if (handle != nullptr) {
|
||||
@@ -107,16 +107,16 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
return r;
|
||||
}
|
||||
|
||||
void Insert(shared_ptr<Cache> cache, int key, int value, int charge = 1) {
|
||||
void Insert(std::shared_ptr<Cache> cache, int key, int value,
|
||||
int charge = 1) {
|
||||
cache->Insert(EncodeKey(key), EncodeValue(value), charge,
|
||||
&CacheTest::Deleter);
|
||||
}
|
||||
|
||||
void Erase(shared_ptr<Cache> cache, int key) {
|
||||
void Erase(std::shared_ptr<Cache> cache, int key) {
|
||||
cache->Erase(EncodeKey(key));
|
||||
}
|
||||
|
||||
|
||||
int Lookup(int key) {
|
||||
return Lookup(cache_, key);
|
||||
}
|
||||
@@ -306,7 +306,7 @@ TEST_P(CacheTest, EvictionPolicy) {
|
||||
Insert(200, 201);
|
||||
|
||||
// Frequently used entry must be kept around
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
for (int i = 0; i < kCacheSize + 200; i++) {
|
||||
Insert(1000+i, 2000+i);
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
}
|
||||
@@ -359,7 +359,7 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
Insert(303, 104);
|
||||
|
||||
// Insert entries much more than Cache capacity
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
for (int i = 0; i < kCacheSize + 200; i++) {
|
||||
Insert(1000 + i, 2000 + i);
|
||||
}
|
||||
|
||||
@@ -687,7 +687,8 @@ TEST_P(CacheTest, DefaultShardBits) {
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_CLOCK_CACHE
|
||||
shared_ptr<Cache> (*new_clock_cache_func)(size_t, int, bool) = NewClockCache;
|
||||
std::shared_ptr<Cache> (*new_clock_cache_func)(size_t, int,
|
||||
bool) = NewClockCache;
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest,
|
||||
testing::Values(kLRU, kClock));
|
||||
#else
|
||||
|
||||
Vendored
+2
-2
@@ -234,7 +234,7 @@ struct CleanupContext {
|
||||
};
|
||||
|
||||
// A cache shard which maintains its own CLOCK cache.
|
||||
class ClockCacheShard : public CacheShard {
|
||||
class ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
// Hash map type.
|
||||
typedef tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey> HashTable;
|
||||
@@ -672,7 +672,7 @@ void ClockCacheShard::EraseUnRefEntries() {
|
||||
Cleanup(context);
|
||||
}
|
||||
|
||||
class ClockCache : public ShardedCache {
|
||||
class ClockCache final : public ShardedCache {
|
||||
public:
|
||||
ClockCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
||||
|
||||
Vendored
+14
-7
@@ -100,14 +100,16 @@ void LRUHandleTable::Resize() {
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio)
|
||||
double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex)
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
high_pri_pool_ratio_(high_pri_pool_ratio),
|
||||
high_pri_pool_capacity_(0),
|
||||
usage_(0),
|
||||
lru_usage_(0) {
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex) {
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
@@ -462,7 +464,8 @@ std::string LRUCacheShard::GetPrintableOptions() const {
|
||||
|
||||
LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -471,7 +474,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio);
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
use_adaptive_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,13 +544,15 @@ std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator);
|
||||
cache_opts.memory_allocator,
|
||||
cache_opts.use_adaptive_mutex);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator) {
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator,
|
||||
bool use_adaptive_mutex) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
@@ -559,7 +565,8 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
return std::make_shared<LRUCache>(capacity, num_shard_bits,
|
||||
strict_capacity_limit, high_pri_pool_ratio,
|
||||
std::move(memory_allocator));
|
||||
std::move(memory_allocator),
|
||||
use_adaptive_mutex);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
Vendored
+32
-19
@@ -55,10 +55,18 @@ struct LRUHandle {
|
||||
// cache itself is counted as 1
|
||||
|
||||
// Include the following flags:
|
||||
// in_cache: whether this entry is referenced by the hash table.
|
||||
// is_high_pri: whether this entry is high priority entry.
|
||||
// in_high_pri_pool: whether this entry is in high-pri pool.
|
||||
char flags;
|
||||
// IN_CACHE: whether this entry is referenced by the hash table.
|
||||
// IS_HIGH_PRI: whether this entry is high priority entry.
|
||||
// IN_HIGH_PRI_POOL: whether this entry is in high-pri pool.
|
||||
// HAS_HIT: whether this entry has had any lookups (hits).
|
||||
enum Flags : uint8_t {
|
||||
IN_CACHE = (1 << 0),
|
||||
IS_HIGH_PRI = (1 << 1),
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
HAS_HIT = (1 << 3),
|
||||
};
|
||||
|
||||
uint8_t flags;
|
||||
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
|
||||
@@ -74,36 +82,36 @@ struct LRUHandle {
|
||||
}
|
||||
}
|
||||
|
||||
bool InCache() { return flags & 1; }
|
||||
bool IsHighPri() { return flags & 2; }
|
||||
bool InHighPriPool() { return flags & 4; }
|
||||
bool HasHit() { return flags & 8; }
|
||||
bool InCache() const { return flags & IN_CACHE; }
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
flags |= 1;
|
||||
flags |= IN_CACHE;
|
||||
} else {
|
||||
flags &= ~1;
|
||||
flags &= ~IN_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPriority(Cache::Priority priority) {
|
||||
if (priority == Cache::Priority::HIGH) {
|
||||
flags |= 2;
|
||||
flags |= IS_HIGH_PRI;
|
||||
} else {
|
||||
flags &= ~2;
|
||||
flags &= ~IS_HIGH_PRI;
|
||||
}
|
||||
}
|
||||
|
||||
void SetInHighPriPool(bool in_high_pri_pool) {
|
||||
if (in_high_pri_pool) {
|
||||
flags |= 4;
|
||||
flags |= IN_HIGH_PRI_POOL;
|
||||
} else {
|
||||
flags &= ~4;
|
||||
flags &= ~IN_HIGH_PRI_POOL;
|
||||
}
|
||||
}
|
||||
|
||||
void SetHit() { flags |= 8; }
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void Free() {
|
||||
assert((refs == 1 && InCache()) || (refs == 0 && !InCache()));
|
||||
@@ -157,10 +165,10 @@ class LRUHandleTable {
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio);
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex);
|
||||
virtual ~LRUCacheShard();
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -276,11 +284,16 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
mutable port::Mutex mutex_;
|
||||
};
|
||||
|
||||
class LRUCache : public ShardedCache {
|
||||
class LRUCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache {
|
||||
public:
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr);
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex);
|
||||
virtual ~LRUCache();
|
||||
virtual const char* Name() const override { return "LRUCache"; }
|
||||
virtual CacheShard* GetShard(int shard) override;
|
||||
|
||||
Vendored
+3
-2
@@ -25,12 +25,13 @@ class LRUCacheTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0) {
|
||||
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex) {
|
||||
DeleteCache();
|
||||
cache_ = reinterpret_cast<LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
|
||||
new (cache_) LRUCacheShard(capacity, false /*strict_capcity_limit*/,
|
||||
high_pri_pool_ratio);
|
||||
high_pri_pool_ratio, use_adaptive_mutex);
|
||||
}
|
||||
|
||||
void Insert(const std::string& key,
|
||||
|
||||
Vendored
+1
-1
@@ -83,7 +83,7 @@ class ShardedCache : public Cache {
|
||||
|
||||
private:
|
||||
static inline uint32_t HashSlice(const Slice& s) {
|
||||
return Hash(s.data(), s.size(), 0);
|
||||
return static_cast<uint32_t>(GetSliceNPHash64(s));
|
||||
}
|
||||
|
||||
uint32_t Shard(uint32_t hash) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
# Exit on error.
|
||||
set -e
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
+17
-13
@@ -47,18 +47,20 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size) {
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
int level, const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size,
|
||||
const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, moptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
compression_opts, skip_filters, column_family_name,
|
||||
level, creation_time, oldest_key_time,
|
||||
target_file_size),
|
||||
sample_for_compression, compression_opts,
|
||||
skip_filters, column_family_name, level,
|
||||
creation_time, oldest_key_time, target_file_size,
|
||||
file_creation_time),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
@@ -75,11 +77,12 @@ Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint) {
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, EventLogger* event_logger, int job_id,
|
||||
const Env::IOPriority io_priority, TableProperties* table_properties,
|
||||
int level, const uint64_t creation_time, const uint64_t oldest_key_time,
|
||||
Env::WriteLifeTimeHint write_hint, const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -133,8 +136,9 @@ Status BuildTable(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
column_family_name, file_writer.get(), compression,
|
||||
compression_opts_for_flush, level, false /* skip_filters */,
|
||||
creation_time, oldest_key_time);
|
||||
sample_for_compression, compression_opts_for_flush, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
|
||||
+6
-2
@@ -47,9 +47,11 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0);
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
|
||||
const uint64_t file_creation_time = 0);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
@@ -72,12 +74,14 @@ extern Status BuildTable(
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET);
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -1070,6 +1070,18 @@ int rocksdb_property_int(
|
||||
}
|
||||
}
|
||||
|
||||
int rocksdb_property_int_cf(
|
||||
rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* propname,
|
||||
uint64_t *out_val) {
|
||||
if (db->rep->GetIntProperty(column_family->rep, Slice(propname), out_val)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
char* rocksdb_property_value_cf(
|
||||
rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
@@ -1183,6 +1195,14 @@ void rocksdb_flush(
|
||||
SaveError(errptr, db->rep->Flush(options->rep));
|
||||
}
|
||||
|
||||
void rocksdb_flush_cf(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_flushoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
char** errptr) {
|
||||
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
|
||||
}
|
||||
|
||||
void rocksdb_disable_file_deletions(
|
||||
rocksdb_t* db,
|
||||
char** errptr) {
|
||||
@@ -2206,6 +2226,11 @@ void rocksdb_options_set_max_bytes_for_level_base(
|
||||
opt->rep.max_bytes_for_level_base = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_snap_refresh_nanos(rocksdb_options_t* opt,
|
||||
uint64_t n) {
|
||||
opt->rep.snap_refresh_nanos = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_level_compaction_dynamic_level_bytes(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.level_compaction_dynamic_level_bytes = v;
|
||||
@@ -3702,6 +3727,38 @@ rocksdb_transactiondb_t* rocksdb_transactiondb_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_transactiondb_t* rocksdb_transactiondb_open_column_families(
|
||||
const rocksdb_options_t* options,
|
||||
const rocksdb_transactiondb_options_t* txn_db_options, const char* name,
|
||||
int num_column_families, const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (int i = 0; i < num_column_families; i++) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
std::string(column_family_names[i]),
|
||||
ColumnFamilyOptions(column_family_options[i]->rep)));
|
||||
}
|
||||
|
||||
TransactionDB* txn_db;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
if (SaveError(errptr, TransactionDB::Open(options->rep, txn_db_options->rep,
|
||||
std::string(name), column_families,
|
||||
&handles, &txn_db))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < handles.size(); i++) {
|
||||
rocksdb_column_family_handle_t* c_handle =
|
||||
new rocksdb_column_family_handle_t;
|
||||
c_handle->rep = handles[i];
|
||||
column_family_handles[i] = c_handle;
|
||||
}
|
||||
rocksdb_transactiondb_t* result = new rocksdb_transactiondb_t;
|
||||
result->rep = txn_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_transactiondb_create_snapshot(
|
||||
rocksdb_transactiondb_t* txn_db) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
@@ -3822,6 +3879,26 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
|
||||
return result;
|
||||
}
|
||||
|
||||
char* rocksdb_transaction_get_for_update_cf(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
size_t* vlen, unsigned char exclusive, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
|
||||
Slice(key, klen), &tmp, exclusive);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read a key outside a transaction
|
||||
char* rocksdb_transactiondb_get(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
@@ -3914,6 +3991,14 @@ void rocksdb_transaction_merge(rocksdb_transaction_t* txn, const char* key,
|
||||
SaveError(errptr, txn->rep->Merge(Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transaction_merge_cf(rocksdb_transaction_t* txn,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr, txn->rep->Merge(column_family->rep, Slice(key, klen),
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Merge a key outside a transaction
|
||||
void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
@@ -3923,6 +4008,14 @@ void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_merge_cf(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_writeoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
const char* val, size_t vlen, char** errptr) {
|
||||
SaveError(errptr, txn_db->rep->Merge(options->rep, column_family->rep,
|
||||
Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Delete a key inside a transaction
|
||||
void rocksdb_transaction_delete(rocksdb_transaction_t* txn, const char* key,
|
||||
size_t klen, char** errptr) {
|
||||
@@ -3975,6 +4068,14 @@ rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_transactiondb_create_iterator_cf(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = txn_db->rep->NewIterator(options->rep, column_family->rep);
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close(rocksdb_transactiondb_t* txn_db) {
|
||||
delete txn_db->rep;
|
||||
delete txn_db;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1187,6 +1188,12 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put_cf(db, woptions, handles[1], "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_flushoptions_t *flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
rocksdb_flush_cf(db, flush_options, handles[1], &err);
|
||||
CheckNoError(err)
|
||||
rocksdb_flushoptions_destroy(flush_options);
|
||||
|
||||
CheckGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
CheckPinGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
|
||||
|
||||
+19
-6
@@ -63,12 +63,24 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
JobContext job_context(0);
|
||||
mutex_->Lock();
|
||||
if (cfd_->Unref()) {
|
||||
bool dropped = cfd_->IsDropped();
|
||||
|
||||
delete cfd_;
|
||||
|
||||
if (dropped) {
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
}
|
||||
}
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
mutex_->Unlock();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
bool defer_purge =
|
||||
db_->immutable_db_options().avoid_unnecessary_blocking_io;
|
||||
db_->PurgeObsoleteFiles(job_context, defer_purge);
|
||||
if (defer_purge) {
|
||||
mutex_->Lock();
|
||||
db_->SchedulePurge();
|
||||
mutex_->Unlock();
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -982,13 +994,14 @@ const int ColumnFamilyData::kCompactToBaseLevel = -2;
|
||||
|
||||
Compaction* ColumnFamilyData::CompactRange(
|
||||
const MutableCFOptions& mutable_cf_options, int input_level,
|
||||
int output_level, uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
int output_level, const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* conflict) {
|
||||
InternalKey** compaction_end, bool* conflict,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
auto* result = compaction_picker_->CompactRange(
|
||||
GetName(), mutable_cf_options, current_->storage_info(), input_level,
|
||||
output_level, output_path_id, max_subcompactions, begin, end,
|
||||
compaction_end, conflict);
|
||||
output_level, compact_range_options, begin, end, compaction_end, conflict,
|
||||
max_file_num_to_ignore);
|
||||
if (result != nullptr) {
|
||||
result->SetInputVersion(current_);
|
||||
}
|
||||
|
||||
+3
-2
@@ -296,9 +296,10 @@ class ColumnFamilyData {
|
||||
// REQUIRES: DB mutex held
|
||||
Compaction* CompactRange(const MutableCFOptions& mutable_cf_options,
|
||||
int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
CompactionPicker* compaction_picker() { return compaction_picker_.get(); }
|
||||
// thread-safe
|
||||
|
||||
+38
-22
@@ -38,14 +38,16 @@ CompactionIterator::CompactionIterator(
|
||||
CompactionRangeDelAggregator* range_del_agg, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
SnapshotListFetchCallback* snap_list_callback)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum) {}
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
snap_list_callback) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -57,7 +59,8 @@ CompactionIterator::CompactionIterator(
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
SnapshotListFetchCallback* snap_list_callback)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
@@ -75,7 +78,8 @@ CompactionIterator::CompactionIterator(
|
||||
current_user_key_sequence_(0),
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
current_key_committed_(false) {
|
||||
current_key_committed_(false),
|
||||
snap_list_callback_(snap_list_callback) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
assert(snapshots_ != nullptr);
|
||||
bottommost_level_ =
|
||||
@@ -83,24 +87,7 @@ CompactionIterator::CompactionIterator(
|
||||
if (compaction_ != nullptr) {
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
if (snapshots_->size() == 0) {
|
||||
// optimize for fast path if there are no snapshots
|
||||
visible_at_tip_ = true;
|
||||
earliest_snapshot_iter_ = snapshots_->end();
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
latest_snapshot_ = 0;
|
||||
} else {
|
||||
visible_at_tip_ = false;
|
||||
earliest_snapshot_iter_ = snapshots_->begin();
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
ProcessSnapshotList();
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
}
|
||||
@@ -222,6 +209,28 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::ProcessSnapshotList() {
|
||||
#ifndef NDEBUG
|
||||
// findEarliestVisibleSnapshot assumes this ordering.
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
#endif
|
||||
if (snapshots_->size() == 0) {
|
||||
// optimize for fast path if there are no snapshots
|
||||
visible_at_tip_ = true;
|
||||
earliest_snapshot_iter_ = snapshots_->end();
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
latest_snapshot_ = 0;
|
||||
} else {
|
||||
visible_at_tip_ = false;
|
||||
earliest_snapshot_iter_ = snapshots_->begin();
|
||||
earliest_snapshot_ = snapshots_->at(0);
|
||||
latest_snapshot_ = snapshots_->back();
|
||||
}
|
||||
released_snapshots_.clear();
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
valid_ = false;
|
||||
@@ -269,6 +278,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
if (!has_current_user_key_ ||
|
||||
!cmp_->Equal(ikey_.user_key, current_user_key_)) {
|
||||
num_keys_++;
|
||||
// Use num_keys_ to reduce the overhead of reading current time
|
||||
if (snap_list_callback_ && snapshots_->size() &&
|
||||
snap_list_callback_->TimeToRefresh(num_keys_)) {
|
||||
snap_list_callback_->Refresh(snapshots_, latest_snapshot_);
|
||||
ProcessSnapshotList();
|
||||
}
|
||||
// First occurrence of this user key
|
||||
// Copy key for output
|
||||
key_ = current_key_.SetInternalKey(key_, &ikey_);
|
||||
|
||||
@@ -21,6 +21,54 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// This callback can be used to refresh the snapshot list from the db. It
|
||||
// includes logics to exponentially decrease the refresh rate to limit the
|
||||
// overhead of refresh.
|
||||
class SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallback(Env* env, uint64_t snap_refresh_nanos,
|
||||
size_t every_nth_key = 1024)
|
||||
: timer_(env, /*auto restart*/ true),
|
||||
snap_refresh_nanos_(snap_refresh_nanos),
|
||||
every_nth_key_minus_one_(every_nth_key - 1) {
|
||||
assert(every_nth_key > 0);
|
||||
assert((ceil(log2(every_nth_key)) == floor(log2(every_nth_key))));
|
||||
}
|
||||
// Refresh the snapshot list. snapshots will bre replacted with the new list.
|
||||
// max is the upper bound. Note: this function will acquire the db_mutex_.
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber max) = 0;
|
||||
inline bool TimeToRefresh(const size_t key_index) {
|
||||
assert(snap_refresh_nanos_ != 0);
|
||||
// skip the key if key_index % every_nth_key (which is of power 2) is not 0.
|
||||
if ((key_index & every_nth_key_minus_one_) != 0) {
|
||||
return false;
|
||||
}
|
||||
const uint64_t elapsed = timer_.ElapsedNanos();
|
||||
auto ret = elapsed > snap_refresh_nanos_;
|
||||
// pre-compute the next time threshold
|
||||
if (ret) {
|
||||
// inc next refresh period exponentially (by x4)
|
||||
auto next_refresh_threshold = snap_refresh_nanos_ << 2;
|
||||
// make sure the shift has not overflown the highest 1 bit
|
||||
snap_refresh_nanos_ =
|
||||
std::max(snap_refresh_nanos_, next_refresh_threshold);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static constexpr SnapshotListFetchCallback* kDisabled = nullptr;
|
||||
|
||||
virtual ~SnapshotListFetchCallback() {}
|
||||
|
||||
private:
|
||||
// Time since the callback was created
|
||||
StopWatchNano timer_;
|
||||
// The delay before calling ::Refresh. To be increased exponentially.
|
||||
uint64_t snap_refresh_nanos_;
|
||||
// Skip evey nth key. Number n if of power 2. The math will require n-1.
|
||||
const uint64_t every_nth_key_minus_one_;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
public:
|
||||
// A wrapper around Compaction. Has a much smaller interface, only what
|
||||
@@ -69,7 +117,8 @@ class CompactionIterator {
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
SnapshotListFetchCallback* snap_list_callback = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
@@ -82,7 +131,8 @@ class CompactionIterator {
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
SnapshotListFetchCallback* snap_list_callback = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -110,6 +160,8 @@ class CompactionIterator {
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
// Process snapshots_ and assign related variables
|
||||
void ProcessSnapshotList();
|
||||
|
||||
// Do last preparations before presenting the output to the callee. At this
|
||||
// point this only zeroes out the sequence number if possible for better
|
||||
@@ -144,7 +196,7 @@ class CompactionIterator {
|
||||
InternalIterator* input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
std::vector<SequenceNumber>* snapshots_;
|
||||
// List of snapshots released during compaction.
|
||||
// findEarliestVisibleSnapshot() find them out from return of
|
||||
// snapshot_checker, and make sure they will not be returned as
|
||||
@@ -219,6 +271,9 @@ class CompactionIterator {
|
||||
// Used to avoid purging uncommitted values. The application can specify
|
||||
// uncommitted values by providing a SnapshotChecker object.
|
||||
bool current_key_committed_;
|
||||
SnapshotListFetchCallback* snap_list_callback_;
|
||||
// number of distinct keys processed
|
||||
size_t num_keys_ = 0;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
|
||||
+37
-29
@@ -97,6 +97,8 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
return "Flush";
|
||||
case CompactionReason::kExternalSstIngestion:
|
||||
return "ExternalSstIngestion";
|
||||
case CompactionReason::kPeriodicCompaction:
|
||||
return "PeriodicCompaction";
|
||||
case CompactionReason::kNumOfReasons:
|
||||
// fall through
|
||||
default:
|
||||
@@ -312,7 +314,8 @@ CompactionJob::CompactionJob(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats)
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, SnapshotListFetchCallback* snap_list_callback)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
@@ -333,6 +336,7 @@ CompactionJob::CompactionJob(
|
||||
db_mutex_(db_mutex),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
snap_list_callback_(snap_list_callback),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
table_cache_(std::move(table_cache)),
|
||||
@@ -340,7 +344,8 @@ CompactionJob::CompactionJob(
|
||||
bottommost_level_(false),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET) {
|
||||
write_hint_(Env::WLTH_NOT_SET),
|
||||
thread_pri_(thread_pri) {
|
||||
assert(log_buffer_ != nullptr);
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
@@ -414,11 +419,10 @@ void CompactionJob::Prepare() {
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
if (c->ShouldFormSubcompactions()) {
|
||||
const uint64_t start_micros = env_->NowMicros();
|
||||
GenSubcompactionBoundaries();
|
||||
MeasureTime(stats_, SUBCOMPACTION_SETUP_TIME,
|
||||
env_->NowMicros() - start_micros);
|
||||
|
||||
{
|
||||
StopWatch sw(env_, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
GenSubcompactionBoundaries();
|
||||
}
|
||||
assert(sizes_.size() == boundaries_.size() + 1);
|
||||
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
@@ -426,8 +430,8 @@ void CompactionJob::Prepare() {
|
||||
Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i];
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]);
|
||||
}
|
||||
MeasureTime(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
} else {
|
||||
compact_->sub_compact_states.emplace_back(c, nullptr, nullptr);
|
||||
}
|
||||
@@ -607,8 +611,9 @@ Status CompactionJob::Run() {
|
||||
compact_->sub_compact_states[i].compaction_job_stats.cpu_micros;
|
||||
}
|
||||
|
||||
MeasureTime(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
MeasureTime(stats_, COMPACTION_CPU_TIME, compaction_stats_.cpu_micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_TIME, compaction_stats_.micros);
|
||||
RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME,
|
||||
compaction_stats_.cpu_micros);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify");
|
||||
|
||||
@@ -717,7 +722,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
Status status = compact_->status;
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact_->compaction->output_level(), compaction_stats_);
|
||||
compact_->compaction->output_level(), thread_pri_, compaction_stats_);
|
||||
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(mutable_cf_options);
|
||||
@@ -888,7 +893,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
|
||||
&range_del_agg, sub_compact->compaction, compaction_filter,
|
||||
shutting_down_, preserve_deletes_seqnum_));
|
||||
shutting_down_, preserve_deletes_seqnum_,
|
||||
// Currently range_del_agg is incompatible with snapshot refresh feature.
|
||||
range_del_agg.IsEmpty() ? snap_list_callback_ : nullptr));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -1478,20 +1485,20 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
bool skip_filters =
|
||||
cfd->ioptions()->optimize_filters_for_hits && bottommost_level_;
|
||||
|
||||
uint64_t output_file_creation_time =
|
||||
int64_t temp_current_time = 0;
|
||||
auto get_time_status = env_->GetCurrentTime(&temp_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!get_time_status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get current time. Status: %s",
|
||||
get_time_status.ToString().c_str());
|
||||
}
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
|
||||
uint64_t latest_key_time =
|
||||
sub_compact->compaction->MaxInputFileCreationTime();
|
||||
if (output_file_creation_time == 0) {
|
||||
int64_t _current_time = 0;
|
||||
auto status = db_options_.env->GetCurrentTime(&_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get current time to populate creation_time property. "
|
||||
"Status: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
output_file_creation_time = static_cast<uint64_t>(_current_time);
|
||||
if (latest_key_time == 0) {
|
||||
latest_key_time = current_time;
|
||||
}
|
||||
|
||||
sub_compact->builder.reset(NewTableBuilder(
|
||||
@@ -1499,10 +1506,11 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(),
|
||||
sub_compact->compaction->output_compression(),
|
||||
0 /*sample_for_compression */,
|
||||
sub_compact->compaction->output_compression_opts(),
|
||||
sub_compact->compaction->output_level(), skip_filters,
|
||||
output_file_creation_time, 0 /* oldest_key_time */,
|
||||
sub_compact->compaction->max_output_file_size()));
|
||||
sub_compact->compaction->output_level(), skip_filters, latest_key_time,
|
||||
0 /* oldest_key_time */, sub_compact->compaction->max_output_file_size(),
|
||||
current_time));
|
||||
LogFlush(db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
+16
-16
@@ -57,22 +57,20 @@ class VersionSet;
|
||||
|
||||
class CompactionJob {
|
||||
public:
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats);
|
||||
CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, SnapshotListFetchCallback* snap_list_callback);
|
||||
|
||||
~CompactionJob();
|
||||
|
||||
@@ -152,6 +150,7 @@ class CompactionJob {
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
// deleted because that version is not visible in any snapshot.
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
SnapshotListFetchCallback* snap_list_callback_;
|
||||
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
@@ -172,6 +171,7 @@ class CompactionJob {
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+130
-12
@@ -5,7 +5,13 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -194,6 +200,13 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
DestroyDB(dbname_, Options());
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_));
|
||||
compaction_job_stats_.Reset();
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
@@ -230,7 +243,10 @@ class CompactionJobTest : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const stl_wrappers::KVMap& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {},
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
int output_level = 1, bool verify = true,
|
||||
SnapshotListFetchCallback* snapshot_fetcher =
|
||||
SnapshotListFetchCallback::kDisabled) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
|
||||
size_t num_input_files = 0;
|
||||
@@ -247,7 +263,7 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, 1, 1024 * 1024,
|
||||
compaction_input_files, output_level, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->ioptions()->compression_opts, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
@@ -262,7 +278,8 @@ class CompactionJobTest : public testing::Test {
|
||||
&shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_);
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, snapshot_fetcher);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
@@ -274,15 +291,17 @@ class CompactionJobTest : public testing::Test {
|
||||
ASSERT_OK(compaction_job.Install(*cfd->GetLatestMutableCFOptions()));
|
||||
mutex_.Unlock();
|
||||
|
||||
if (expected_results.size() == 0) {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 0U);
|
||||
} else {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
if (verify) {
|
||||
if (expected_results.size() == 0) {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 0U);
|
||||
} else {
|
||||
ASSERT_GE(compaction_job_stats_.elapsed_micros, 0U);
|
||||
ASSERT_EQ(compaction_job_stats_.num_input_files, num_input_files);
|
||||
ASSERT_EQ(compaction_job_stats_.num_output_files, 1U);
|
||||
mock_table_factory_->AssertLatestFile(expected_results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,6 +956,105 @@ TEST_F(CompactionJobTest, CorruptionAfterDeletion) {
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
// Test the snapshot fetcher in compaction
|
||||
TEST_F(CompactionJobTest, SnapshotRefresh) {
|
||||
uint64_t time_seed = env_->NowMicros();
|
||||
printf("time_seed is %" PRIu64 "\n", time_seed); // would help to reproduce
|
||||
Random64 rand(time_seed);
|
||||
std::vector<SequenceNumber> db_snapshots;
|
||||
class SnapshotListFetchCallbackTest : public SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallbackTest(Env* env, Random64& rand,
|
||||
std::vector<SequenceNumber>* snapshots)
|
||||
: SnapshotListFetchCallback(env, 1 /*short time delay*/,
|
||||
1 /*fetch after each key*/),
|
||||
rand_(rand),
|
||||
snapshots_(snapshots) {}
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber) override {
|
||||
assert(snapshots->size());
|
||||
assert(snapshots_->size());
|
||||
assert(snapshots_->size() == snapshots->size());
|
||||
if (rand_.OneIn(2)) {
|
||||
uint64_t release_index = rand_.Uniform(snapshots_->size());
|
||||
snapshots_->erase(snapshots_->begin() + release_index);
|
||||
*snapshots = *snapshots_;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Random64 rand_;
|
||||
std::vector<SequenceNumber>* snapshots_;
|
||||
} snapshot_fetcher(env_, rand, &db_snapshots);
|
||||
|
||||
std::vector<std::pair<const std::string, std::string>> file1_kvs, file2_kvs;
|
||||
std::array<ValueType, 4> types = {kTypeValue, kTypeDeletion,
|
||||
kTypeSingleDeletion};
|
||||
SequenceNumber last_seq = 0;
|
||||
for (int i = 1; i < 100; i++) {
|
||||
SequenceNumber seq = last_seq + 1;
|
||||
last_seq = seq;
|
||||
if (rand.OneIn(2)) {
|
||||
auto type = types[rand.Uniform(types.size())];
|
||||
file1_kvs.push_back(
|
||||
{test::KeyStr("k" + ToString(i), seq, type), "v" + ToString(i)});
|
||||
}
|
||||
}
|
||||
auto file1 = mock::MakeMockFile(file1_kvs);
|
||||
for (int i = 1; i < 100; i++) {
|
||||
SequenceNumber seq = last_seq + 1;
|
||||
last_seq++;
|
||||
if (rand.OneIn(2)) {
|
||||
auto type = types[rand.Uniform(types.size())];
|
||||
file2_kvs.push_back(
|
||||
{test::KeyStr("k" + ToString(i), seq, type), "v" + ToString(i)});
|
||||
}
|
||||
}
|
||||
auto file2 = mock::MakeMockFile(file2_kvs);
|
||||
for (SequenceNumber i = 1; i < last_seq + 1; i++) {
|
||||
if (rand.OneIn(5)) {
|
||||
db_snapshots.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
const bool kVerify = true;
|
||||
const int output_level_0 = 0;
|
||||
NewDB();
|
||||
AddMockFile(file1);
|
||||
AddMockFile(file2);
|
||||
SetLastSequence(last_seq);
|
||||
auto files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
// put the output on L0 since it is easier to feed them again to the 2nd
|
||||
// compaction
|
||||
RunCompaction({files}, file1, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0, !kVerify, &snapshot_fetcher);
|
||||
|
||||
// Now db_snapshots are changed. Run the compaction again without snapshot
|
||||
// fetcher but with the updated snapshot list.
|
||||
compaction_job_stats_.Reset();
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, file1, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0 + 1, !kVerify);
|
||||
// The result should be what we get if we run compaction without snapshot
|
||||
// fetcher on the updated list of snapshots
|
||||
auto expected = mock_table_factory_->output();
|
||||
|
||||
NewDB();
|
||||
AddMockFile(file1);
|
||||
AddMockFile(file2);
|
||||
SetLastSequence(last_seq);
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0, !kVerify);
|
||||
// The 2nd compaction above would get rid of useless delete markers. To get
|
||||
// the output here exactly as what we got above after two compactions, we also
|
||||
// run the compaction for 2nd time.
|
||||
compaction_job_stats_.Reset();
|
||||
files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected, db_snapshots, kMaxSequenceNumber,
|
||||
output_level_0 + 1, !kVerify);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+101
-10
@@ -543,9 +543,9 @@ void CompactionPicker::GetGrandparents(
|
||||
Compaction* CompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict) {
|
||||
const CompactRangeOptions& compact_range_options, const InternalKey* begin,
|
||||
const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
// CompactionPickerFIFO has its own implementation of compact range
|
||||
assert(ioptions_.compaction_style != kCompactionStyleFIFO);
|
||||
|
||||
@@ -609,11 +609,13 @@ Compaction* CompactionPicker::CompactRange(
|
||||
output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style),
|
||||
/* max_compaction_bytes */ LLONG_MAX, output_path_id,
|
||||
/* max_compaction_bytes */ LLONG_MAX,
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, /* grandparents */ {}, /* is manual */ true);
|
||||
compact_range_options.max_subcompactions, /* grandparents */ {},
|
||||
/* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -658,7 +660,43 @@ Compaction* CompactionPicker::CompactRange(
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(output_path_id < static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
assert(compact_range_options.target_path_id <
|
||||
static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
|
||||
// for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
|
||||
// files that are created during the current compaction.
|
||||
if (compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized &&
|
||||
max_file_num_to_ignore != port::kMaxUint64) {
|
||||
assert(input_level == output_level);
|
||||
// inputs_shrunk holds a continuous subset of input files which were all
|
||||
// created before the current manual compaction
|
||||
std::vector<FileMetaData*> inputs_shrunk;
|
||||
size_t skip_input_index = inputs.size();
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
|
||||
inputs_shrunk.push_back(inputs[i]);
|
||||
} else if (!inputs_shrunk.empty()) {
|
||||
// inputs[i] was created during the current manual compaction and
|
||||
// need to be skipped
|
||||
skip_input_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inputs_shrunk.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (inputs.size() != inputs_shrunk.size()) {
|
||||
inputs.files.swap(inputs_shrunk);
|
||||
}
|
||||
// set covering_the_whole_range to false if there is any file that need to
|
||||
// be compacted in the range of inputs[skip_input_index+1, inputs.size())
|
||||
for (size_t i = skip_input_index + 1; i < inputs.size(); ++i) {
|
||||
if (inputs[i]->fd.GetNumber() < max_file_num_to_ignore) {
|
||||
covering_the_whole_range = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InternalKey key_storage;
|
||||
InternalKey* next_smallest = &key_storage;
|
||||
@@ -724,11 +762,12 @@ Compaction* CompactionPicker::CompactRange(
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style, vstorage->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
mutable_cf_options.max_compaction_bytes,
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, std::move(grandparents),
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
@@ -1073,6 +1112,9 @@ bool LevelCompactionPicker::NeedsCompaction(
|
||||
if (!vstorage->ExpiredTtlFiles().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->BottommostFilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1141,6 +1183,8 @@ class LevelCompactionBuilder {
|
||||
|
||||
void PickExpiredTtlFiles();
|
||||
|
||||
void PickFilesMarkedForPeriodicCompaction();
|
||||
|
||||
const std::string& cf_name_;
|
||||
VersionStorageInfo* vstorage_;
|
||||
CompactionPicker* compaction_picker_;
|
||||
@@ -1203,6 +1247,39 @@ void LevelCompactionBuilder::PickExpiredTtlFiles() {
|
||||
start_level_inputs_.files.clear();
|
||||
}
|
||||
|
||||
void LevelCompactionBuilder::PickFilesMarkedForPeriodicCompaction() {
|
||||
if (vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto continuation = [&](std::pair<int, FileMetaData*> level_file) {
|
||||
// If it's being compacted it has nothing to do here.
|
||||
// If this assert() fails that means that some function marked some
|
||||
// files as being_compacted, but didn't call ComputeCompactionScore()
|
||||
assert(!level_file.second->being_compacted);
|
||||
output_level_ = start_level_ = level_file.first;
|
||||
|
||||
if (start_level_ == 0 &&
|
||||
!compaction_picker_->level0_compactions_in_progress()->empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
start_level_inputs_.files = {level_file.second};
|
||||
start_level_inputs_.level = start_level_;
|
||||
return compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
|
||||
&start_level_inputs_);
|
||||
};
|
||||
|
||||
for (auto& level_file : vstorage_->FilesMarkedForPeriodicCompaction()) {
|
||||
if (continuation(level_file)) {
|
||||
// found the compaction!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
start_level_inputs_.files.clear();
|
||||
}
|
||||
|
||||
void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
// Find the compactions by size on all levels.
|
||||
bool skipped_l0_to_base = false;
|
||||
@@ -1256,7 +1333,6 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
if (start_level_inputs_.empty()) {
|
||||
parent_index_ = base_index_ = -1;
|
||||
|
||||
// PickFilesMarkedForCompaction();
|
||||
compaction_picker_->PickFilesMarkedForCompaction(
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
@@ -1264,7 +1340,10 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottommost Files Compaction on deleting tombstones
|
||||
if (start_level_inputs_.empty()) {
|
||||
size_t i;
|
||||
for (i = 0; i < vstorage_->BottommostFilesMarkedForCompaction().size();
|
||||
++i) {
|
||||
@@ -1285,11 +1364,23 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kBottommostFiles;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(start_level_inputs_.empty());
|
||||
// TTL Compaction
|
||||
if (start_level_inputs_.empty()) {
|
||||
PickExpiredTtlFiles();
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kTtl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic Compaction
|
||||
if (start_level_inputs_.empty()) {
|
||||
PickFilesMarkedForPeriodicCompaction();
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kPeriodicCompaction;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ class CompactionPicker {
|
||||
virtual Compaction* CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
|
||||
@@ -255,12 +256,12 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
int /*input_level*/, int /*output_level*/,
|
||||
uint32_t /*output_path_id*/,
|
||||
uint32_t /*max_subcompactions*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/,
|
||||
const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/,
|
||||
bool* /*manual_conflict*/) override {
|
||||
bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -213,9 +213,10 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
Compaction* FIFOCompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t /*output_path_id*/, uint32_t /*max_subcompactions*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/, const InternalKey* /*end*/,
|
||||
InternalKey** compaction_end, bool* /*manual_conflict*/) {
|
||||
InternalKey** compaction_end, bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/) {
|
||||
#ifdef NDEBUG
|
||||
(void)input_level;
|
||||
(void)output_level;
|
||||
|
||||
@@ -27,9 +27,10 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
virtual Compaction* CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, uint32_t max_subcompactions,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict) override;
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
virtual int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
@@ -395,8 +395,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
}
|
||||
#endif
|
||||
// update statistics
|
||||
MeasureTime(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
RecordInHistogram(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
|
||||
+158
-2
@@ -994,7 +994,7 @@ TEST_F(DBBasicTest, MultiGetMultiCF) {
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
values = MultiGet(cfs, keys, nullptr);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
ASSERT_EQ(values[j], "cf" + std::to_string(j) + "_val2");
|
||||
@@ -1054,7 +1054,7 @@ TEST_F(DBBasicTest, MultiGetMultiCFMutex) {
|
||||
keys.push_back("cf" + std::to_string(i) + "_key");
|
||||
}
|
||||
|
||||
values = MultiGet(cfs, keys);
|
||||
values = MultiGet(cfs, keys, nullptr);
|
||||
ASSERT_TRUE(last_try);
|
||||
ASSERT_EQ(values.size(), 8);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
@@ -1128,6 +1128,162 @@ TEST_F(DBBasicTest, MultiGetMultiCFSnapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSimpleUnsorted) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
|
||||
std::vector<Slice> keys({"no_key", "k5", "k4", "k3", "k2", "k1"});
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
std::vector<Status> s(keys.size());
|
||||
|
||||
db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(),
|
||||
values.data(), s.data(), false);
|
||||
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(std::string(values[5].data(), values[5].size()), "v1");
|
||||
ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v2");
|
||||
ASSERT_EQ(std::string(values[3].data(), values[3].size()), "v3");
|
||||
ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v5");
|
||||
// four kv pairs * two bytes per value
|
||||
ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
ASSERT_TRUE(s[0].IsNotFound());
|
||||
ASSERT_OK(s[1]);
|
||||
ASSERT_TRUE(s[2].IsNotFound());
|
||||
ASSERT_OK(s[3]);
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_OK(s[5]);
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedSimpleSorted) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
|
||||
std::vector<Slice> keys({"k1", "k2", "k3", "k4", "k5", "no_key"});
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
std::vector<ColumnFamilyHandle*> cfs(keys.size(), handles_[1]);
|
||||
std::vector<Status> s(keys.size());
|
||||
|
||||
db_->MultiGet(ReadOptions(), handles_[1], keys.size(), keys.data(),
|
||||
values.data(), s.data(), true);
|
||||
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
ASSERT_EQ(std::string(values[0].data(), values[0].size()), "v1");
|
||||
ASSERT_EQ(std::string(values[1].data(), values[1].size()), "v2");
|
||||
ASSERT_EQ(std::string(values[2].data(), values[2].size()), "v3");
|
||||
ASSERT_EQ(std::string(values[4].data(), values[4].size()), "v5");
|
||||
// four kv pairs * two bytes per value
|
||||
ASSERT_EQ(8, (int)get_perf_context()->multiget_read_bytes);
|
||||
|
||||
ASSERT_OK(s[0]);
|
||||
ASSERT_OK(s[1]);
|
||||
ASSERT_OK(s[2]);
|
||||
ASSERT_TRUE(s[3].IsNotFound());
|
||||
ASSERT_OK(s[4]);
|
||||
ASSERT_TRUE(s[5].IsNotFound());
|
||||
|
||||
SetPerfLevel(kDisable);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
int num_keys = 0;
|
||||
|
||||
for (int i = 0; i < 128; ++i) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
for (int i = 0; i < 128; i += 3) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l1_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
for (int i = 0; i < 128; i += 5) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l0_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
num_keys = 0;
|
||||
}
|
||||
ASSERT_EQ(0, num_keys);
|
||||
|
||||
for (int i = 0; i < 128; i += 9) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 64; i < 80; ++i) {
|
||||
keys.push_back("key_" + std::to_string(i));
|
||||
}
|
||||
|
||||
values = MultiGet(keys, nullptr);
|
||||
ASSERT_EQ(values.size(), 16);
|
||||
for (unsigned int j = 0; j < values.size(); ++j) {
|
||||
int key = j + 64;
|
||||
if (key % 9 == 0) {
|
||||
ASSERT_EQ(values[j], "val_mem_" + std::to_string(key));
|
||||
} else if (key % 5 == 0) {
|
||||
ASSERT_EQ(values[j], "val_l0_" + std::to_string(key));
|
||||
} else if (key % 3 == 0) {
|
||||
ASSERT_EQ(values[j], "val_l1_" + std::to_string(key));
|
||||
} else {
|
||||
ASSERT_EQ(values[j], "val_l2_" + std::to_string(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -346,14 +346,14 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
// 200 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(200, 0, false);
|
||||
// 500 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(500, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20, true));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
ASSERT_OK(Put(1, "longer_key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
size_t index_bytes_insert =
|
||||
@@ -367,7 +367,9 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
ASSERT_OK(Put(1, "key2", "val"));
|
||||
// Note that the second key needs to be no longer than the first one.
|
||||
// Otherwise the second index block may not fit in cache.
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
// cache evicted old index and block entries
|
||||
|
||||
@@ -836,6 +836,25 @@ TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilter) {
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_hit_count);
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtablePrefixBloomOutOfDomain) {
|
||||
constexpr size_t kPrefixSize = 8;
|
||||
const std::string kKey = "key";
|
||||
assert(kKey.size() < kPrefixSize);
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixSize));
|
||||
options.memtable_prefix_bloom_size_ratio = 0.25;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put(kKey, "v"));
|
||||
ASSERT_EQ("v", Get(kKey));
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ReadOptions()));
|
||||
iter->Seek(kKey);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
iter->SeekForPrev(kKey);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class BloomStatsTestWithParam
|
||||
: public DBBloomFilterTest,
|
||||
@@ -985,13 +1004,16 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(value3, iter->value().ToString());
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_sst_hit_count);
|
||||
// The seek doesn't check block-based bloom filter because last index key
|
||||
// starts with the same prefix we're seeking to.
|
||||
uint64_t expected_hits = use_block_based_builder_ ? 1 : 2;
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
|
||||
iter->Seek(key2);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_sst_miss_count);
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_sst_hit_count);
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BloomStatsTestWithParam, BloomStatsTestWithParam,
|
||||
@@ -1316,6 +1338,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
table_options.index_shortening = BlockBasedTableOptions::
|
||||
IndexShorteningMode::kShortenSeparatorsAndSuccessor;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
+290
-5
@@ -353,7 +353,8 @@ TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) {
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
@@ -511,7 +512,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
// Only verifying compaction outputs issues one table cache lookup
|
||||
// for both data block and range deletion block).
|
||||
@@ -2260,6 +2261,8 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 0;
|
||||
// cannot use kForceOptimized here because the compaction here is expected
|
||||
// to generate one output file
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.exclusive_manual_compaction = exclusive_manual_compaction_;
|
||||
@@ -3039,7 +3042,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
// then compacte the bottommost level L3=>L3 (non trivial move)
|
||||
compact_options = CompactRangeOptions();
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
|
||||
ASSERT_EQ(trivial_move, 4);
|
||||
@@ -3348,7 +3351,7 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
options.level0_file_num_compaction_trigger = kNumLevelFiles;
|
||||
// inflate it a bit to account for key/metadata overhead
|
||||
options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100;
|
||||
Reopen(options);
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
@@ -3379,10 +3382,12 @@ TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
ASSERT_NE(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(kMaxSequenceNumber, dbfull()->bottommost_files_mark_threshold_);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
@@ -3515,6 +3520,234 @@ TEST_F(DBCompactionTest, LevelCompactExpiredTtlFiles) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumLevelFiles = 2;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days
|
||||
options.max_open_files = -1; // needed for ttl compaction
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
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();
|
||||
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
// Add 50 hours and do a write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Assert that the files stay in the same level
|
||||
ASSERT_EQ("3", FilesPerLevel());
|
||||
// The two old files go through the periodic compaction process
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ("0,3", FilesPerLevel());
|
||||
|
||||
// Add another 50 hours and do another write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("b", "2"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("1,3", FilesPerLevel());
|
||||
// The three old files now go through the periodic compaction process. 2 + 3.
|
||||
ASSERT_EQ(5, periodic_compactions);
|
||||
|
||||
// Add another 50 hours and do another write
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("c", "3"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("2,3", FilesPerLevel());
|
||||
// The four old files now go through the periodic compaction process. 5 + 4.
|
||||
ASSERT_EQ(9, periodic_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicCompactionWithOldDB) {
|
||||
// This test makes sure that periodic compactions are working with a DB
|
||||
// where file_creation_time of some files is 0.
|
||||
// After compactions the new files are created with a valid file_creation_time
|
||||
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumFiles = 4;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = -1; // needed for ttl compaction
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
bool set_file_creation_time_to_zero = true;
|
||||
bool set_creation_time_to_zero = true;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) {
|
||||
TableProperties* props = reinterpret_cast<TableProperties*>(arg);
|
||||
if (set_file_creation_time_to_zero) {
|
||||
props->file_creation_time = 0;
|
||||
}
|
||||
if (set_creation_time_to_zero) {
|
||||
props->creation_time = 0;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
// Move the first two files to L2.
|
||||
if (i == 1) {
|
||||
MoveFilesToLevel(2);
|
||||
set_creation_time_to_zero = false;
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ("2,0,2", FilesPerLevel());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
|
||||
Close();
|
||||
|
||||
set_file_creation_time_to_zero = false;
|
||||
// Forward the clock by 2 days.
|
||||
env_->addon_time_.fetch_add(2 * 24 * 60 * 60);
|
||||
options.periodic_compaction_seconds = 1 * 24 * 60 * 60; // 1 day
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("2,0,2", FilesPerLevel());
|
||||
// Make sure that all files go through periodic compaction.
|
||||
ASSERT_EQ(kNumFiles, periodic_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, LevelPeriodicAndTtlCompaction) {
|
||||
const int kNumKeysPerFile = 32;
|
||||
const int kNumLevelFiles = 2;
|
||||
const int kValueSize = 100;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.ttl = 10 * 60 * 60; // 10 hours
|
||||
options.periodic_compaction_seconds = 48 * 60 * 60; // 2 days
|
||||
options.max_open_files = -1; // needed for both periodic and ttl compactions
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
env_->addon_time_.store(0);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int periodic_compactions = 0;
|
||||
int ttl_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = reinterpret_cast<Compaction*>(arg);
|
||||
auto compaction_reason = compaction->compaction_reason();
|
||||
if (compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
} else if (compaction_reason == CompactionReason::kTtl) {
|
||||
ttl_compactions++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
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());
|
||||
ASSERT_EQ(0, periodic_compactions);
|
||||
ASSERT_EQ(0, ttl_compactions);
|
||||
|
||||
// Add some time greater than periodic_compaction_time.
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Files in the bottom level go through periodic compactions.
|
||||
ASSERT_EQ("1,0,0,2", FilesPerLevel());
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
ASSERT_EQ(0, ttl_compactions);
|
||||
|
||||
// Add a little more time than ttl
|
||||
env_->addon_time_.fetch_add(11 * 60 * 60);
|
||||
ASSERT_OK(Put("b", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Notice that the previous file in level 1 falls down to the bottom level
|
||||
// due to ttl compactions, one level at a time.
|
||||
// And bottom level files don't get picked up for ttl compactions.
|
||||
ASSERT_EQ("1,0,0,3", FilesPerLevel());
|
||||
ASSERT_EQ(2, periodic_compactions);
|
||||
ASSERT_EQ(3, ttl_compactions);
|
||||
|
||||
// Add some time greater than periodic_compaction_time.
|
||||
env_->addon_time_.fetch_add(50 * 60 * 60);
|
||||
ASSERT_OK(Put("c", "1"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Previous L0 file falls one level at a time to bottom level due to ttl.
|
||||
// And all 4 bottom files go through periodic compactions.
|
||||
ASSERT_EQ("1,0,0,4", FilesPerLevel());
|
||||
ASSERT_EQ(6, periodic_compactions);
|
||||
ASSERT_EQ(6, ttl_compactions);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
@@ -4222,7 +4455,7 @@ TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
{{"max_compaction_bytes", std::to_string(max_compaction_bytes)}}));
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -4266,6 +4499,58 @@ TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// ManualCompactionBottomLevelOptimization tests the bottom level manual
|
||||
// compaction optimization to skip recompacting files created by Ln-1 to Ln
|
||||
// compaction
|
||||
TEST_F(DBCompactionTest, ManualCompactionBottomLevelOptimized) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.num_levels = 3;
|
||||
opts.level0_file_num_compaction_trigger = 5;
|
||||
opts.compression = kNoCompression;
|
||||
opts.merge_operator.reset(new NoopMergeOperator());
|
||||
opts.target_file_size_base = 1024;
|
||||
opts.max_bytes_for_level_multiplier = 2;
|
||||
opts.disable_auto_compactions = true;
|
||||
DestroyAndReopen(opts);
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
InternalStats* internal_stats_ptr = cfd->internal_stats();
|
||||
ASSERT_NE(internal_stats_ptr, nullptr);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 8; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
for (auto i = 0; i < 8; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("bar" + std::to_string(i * 10 + j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
const std::vector<InternalStats::CompactionStats>& comp_stats =
|
||||
internal_stats_ptr->TEST_GetCompactionStats();
|
||||
int num = comp_stats[2].num_input_files_in_output_level;
|
||||
ASSERT_EQ(num, 0);
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
const std::vector<InternalStats::CompactionStats>& comp_stats2 =
|
||||
internal_stats_ptr->TEST_GetCompactionStats();
|
||||
num = comp_stats2[2].num_input_files_in_output_level;
|
||||
ASSERT_EQ(num, 0);
|
||||
}
|
||||
|
||||
// FixFileIngestionCompactionDeadlock tests and verifies that compaction and
|
||||
// file ingestion do not cause deadlock in the event of write stall triggered
|
||||
// by number of L0 files reaching level0_stop_writes_trigger.
|
||||
|
||||
@@ -488,6 +488,63 @@ TEST_P(DBAtomicFlushTest,
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, TriggerFlushAndClose) {
|
||||
bool atomic_flush = GetParam();
|
||||
if (!atomic_flush) {
|
||||
return;
|
||||
}
|
||||
const int kNumKeysTriggerFlush = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kNumKeysTriggerFlush));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
for (int i = 0; i != kNumKeysTriggerFlush; ++i) {
|
||||
ASSERT_OK(Put(0, "key" + std::to_string(i), "value" + std::to_string(i)));
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(Put(0, "key", "value"));
|
||||
Close();
|
||||
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ("value", Get(0, "key"));
|
||||
}
|
||||
|
||||
TEST_P(DBAtomicFlushTest, PickMemtablesRaceWithBackgroundFlush) {
|
||||
bool atomic_flush = GetParam();
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.max_write_buffer_number = 4;
|
||||
// Set min_write_buffer_number_to_merge to be greater than 1, so that
|
||||
// a column family with one memtable in the imm will not cause IsFlushPending
|
||||
// to return true when flush_requested_ is false.
|
||||
options.min_write_buffer_number_to_merge = 2;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ASSERT_OK(dbfull()->PauseBackgroundWork());
|
||||
ASSERT_OK(Put(0, "key00", "value00"));
|
||||
ASSERT_OK(Put(1, "key10", "value10"));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts, handles_));
|
||||
ASSERT_OK(Put(0, "key01", "value01"));
|
||||
// Since max_write_buffer_number is 4, the following flush won't cause write
|
||||
// stall.
|
||||
ASSERT_OK(dbfull()->Flush(flush_opts));
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1]));
|
||||
handles_[1] = nullptr;
|
||||
ASSERT_OK(dbfull()->ContinueBackgroundWork());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[0]));
|
||||
delete handles_[0];
|
||||
handles_.clear();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
+323
-85
@@ -76,7 +76,9 @@
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/block.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "table/multiget_context.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "table/two_level_iterator.h"
|
||||
#include "tools/sst_dump_tool_imp.h"
|
||||
@@ -148,18 +150,23 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
immutable_db_options_(initial_db_options_),
|
||||
mutable_db_options_(initial_db_options_),
|
||||
stats_(immutable_db_options_.statistics.get()),
|
||||
db_lock_(nullptr),
|
||||
mutex_(stats_, env_, DB_MUTEX_WAIT_MICROS,
|
||||
immutable_db_options_.use_adaptive_mutex),
|
||||
default_cf_handle_(nullptr),
|
||||
max_total_in_memory_state_(0),
|
||||
env_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
env_options_for_compaction_(env_->OptimizeForCompactionTableWrite(
|
||||
env_options_, immutable_db_options_)),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
db_lock_(nullptr),
|
||||
shutting_down_(false),
|
||||
bg_cv_(&mutex_),
|
||||
logfile_number_(0),
|
||||
log_dir_synced_(false),
|
||||
log_empty_(true),
|
||||
default_cf_handle_(nullptr),
|
||||
log_sync_cv_(&mutex_),
|
||||
total_log_size_(0),
|
||||
max_total_in_memory_state_(0),
|
||||
is_snapshot_supported_(true),
|
||||
write_buffer_manager_(immutable_db_options_.write_buffer_manager.get()),
|
||||
write_thread_(immutable_db_options_),
|
||||
@@ -186,9 +193,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
next_job_id_(1),
|
||||
has_unpersisted_data_(false),
|
||||
unable_to_release_oldest_log_(false),
|
||||
env_options_(BuildDBOptions(immutable_db_options_, mutable_db_options_)),
|
||||
env_options_for_compaction_(env_->OptimizeForCompactionTableWrite(
|
||||
env_options_, immutable_db_options_)),
|
||||
num_running_ingest_file_(0),
|
||||
#ifndef ROCKSDB_LITE
|
||||
wal_manager_(immutable_db_options_, env_options_, seq_per_batch),
|
||||
@@ -200,8 +204,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
opened_successfully_(false),
|
||||
two_write_queues_(options.two_write_queues),
|
||||
manual_wal_flush_(options.manual_wal_flush),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
// last_sequencee_ is always maintained by the main queue that also writes
|
||||
// to the memtable. When two_write_queues_ is disabled last seq in
|
||||
// memtable is the same as last seq published to the readers. When it is
|
||||
@@ -299,7 +301,8 @@ Status DBImpl::ResumeImpl() {
|
||||
s = Status::ShutdownInProgress();
|
||||
}
|
||||
if (s.ok() && bg_error.severity() > Status::Severity::kHardError) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Fatal/Unrecoverable error");
|
||||
s = bg_error;
|
||||
}
|
||||
@@ -370,8 +373,8 @@ Status DBImpl::ResumeImpl() {
|
||||
// Wake up any waiters - in this case, it could be the shutdown thread
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
// No need to check BGError again. If something happened, event listener would be
|
||||
// notified and the operation causing it would have failed
|
||||
// No need to check BGError again. If something happened, event listener would
|
||||
// be notified and the operation causing it would have failed
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -385,7 +388,6 @@ void DBImpl::WaitForBackgroundWork() {
|
||||
|
||||
// Will lock the mutex_, will wait for completion if wait is true
|
||||
void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Shutdown: canceling all background work");
|
||||
|
||||
@@ -572,7 +574,7 @@ Status DBImpl::CloseHelper() {
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
sfm->Close();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (immutable_db_options_.info_log && own_info_log_) {
|
||||
Status s = immutable_db_options_.info_log->Close();
|
||||
@@ -730,7 +732,8 @@ bool DBImpl::FindStatsByTime(uint64_t start_time, uint64_t end_time,
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetStatsHistory(uint64_t start_time, uint64_t end_time,
|
||||
Status DBImpl::GetStatsHistory(
|
||||
uint64_t start_time, uint64_t end_time,
|
||||
std::unique_ptr<StatsHistoryIterator>* stats_iterator) {
|
||||
if (!stats_iterator) {
|
||||
return Status::InvalidArgument("stats_iterator not preallocated.");
|
||||
@@ -912,19 +915,18 @@ Status DBImpl::SetDBOptions(
|
||||
}
|
||||
if (new_options.stats_dump_period_sec !=
|
||||
mutable_db_options_.stats_dump_period_sec) {
|
||||
if (thread_dump_stats_) {
|
||||
mutex_.Unlock();
|
||||
thread_dump_stats_->cancel();
|
||||
mutex_.Lock();
|
||||
}
|
||||
if (new_options.stats_dump_period_sec > 0) {
|
||||
thread_dump_stats_.reset(new rocksdb::RepeatableThread(
|
||||
[this]() { DBImpl::DumpStats(); }, "dump_st", env_,
|
||||
new_options.stats_dump_period_sec * 1000000));
|
||||
}
|
||||
else {
|
||||
thread_dump_stats_.reset();
|
||||
}
|
||||
if (thread_dump_stats_) {
|
||||
mutex_.Unlock();
|
||||
thread_dump_stats_->cancel();
|
||||
mutex_.Lock();
|
||||
}
|
||||
if (new_options.stats_dump_period_sec > 0) {
|
||||
thread_dump_stats_.reset(new rocksdb::RepeatableThread(
|
||||
[this]() { DBImpl::DumpStats(); }, "dump_st", env_,
|
||||
new_options.stats_dump_period_sec * 1000000));
|
||||
} else {
|
||||
thread_dump_stats_.reset();
|
||||
}
|
||||
}
|
||||
if (new_options.stats_persist_period_sec !=
|
||||
mutable_db_options_.stats_persist_period_sec) {
|
||||
@@ -1112,6 +1114,25 @@ Status DBImpl::SyncWAL() {
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::LockWAL() {
|
||||
log_write_mutex_.Lock();
|
||||
auto cur_log_writer = logs_.back().writer;
|
||||
auto status = cur_log_writer->WriteBuffer();
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s",
|
||||
status.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::UnlockWAL() {
|
||||
log_write_mutex_.Unlock();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
const Status& status) {
|
||||
mutex_.AssertHeld();
|
||||
@@ -1188,7 +1209,15 @@ void DBImpl::BackgroundCallPurge() {
|
||||
// both queues are empty. This is stricter than what is needed, but can make
|
||||
// it easier for us to reason the correctness.
|
||||
while (!purge_queue_.empty() || !logs_to_free_queue_.empty()) {
|
||||
if (!purge_queue_.empty()) {
|
||||
// Check logs_to_free_queue_ first and close log writers.
|
||||
if (!logs_to_free_queue_.empty()) {
|
||||
assert(!logs_to_free_queue_.empty());
|
||||
log::Writer* log_writer = *(logs_to_free_queue_.begin());
|
||||
logs_to_free_queue_.pop_front();
|
||||
mutex_.Unlock();
|
||||
delete log_writer;
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
auto purge_file = purge_queue_.begin();
|
||||
auto fname = purge_file->fname;
|
||||
auto dir_to_sync = purge_file->dir_to_sync;
|
||||
@@ -1200,13 +1229,6 @@ void DBImpl::BackgroundCallPurge() {
|
||||
mutex_.Unlock();
|
||||
DeleteObsoleteFileImpl(job_id, fname, dir_to_sync, type, number);
|
||||
mutex_.Lock();
|
||||
} else {
|
||||
assert(!logs_to_free_queue_.empty());
|
||||
log::Writer* log_writer = *(logs_to_free_queue_.begin());
|
||||
logs_to_free_queue_.pop_front();
|
||||
mutex_.Unlock();
|
||||
delete log_writer;
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
bg_purge_scheduled_--;
|
||||
@@ -1313,7 +1335,8 @@ InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
|
||||
internal_iter = merge_iter_builder.Finish();
|
||||
IterState* cleanup =
|
||||
new IterState(this, &mutex_, super_version,
|
||||
read_options.background_purge_on_iterator_cleanup);
|
||||
read_options.background_purge_on_iterator_cleanup ||
|
||||
immutable_db_options_.avoid_unnecessary_blocking_io);
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
|
||||
|
||||
return internal_iter;
|
||||
@@ -1362,33 +1385,25 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
|
||||
SequenceNumber snapshot;
|
||||
if (read_options.snapshot != nullptr) {
|
||||
// Note: In WritePrepared txns this is not necessary but not harmful
|
||||
// either. Because prep_seq > snapshot => commit_seq > snapshot so if
|
||||
// a snapshot is specified we should be fine with skipping seq numbers
|
||||
// that are greater than that.
|
||||
//
|
||||
// In WriteUnprepared, we cannot set snapshot in the lookup key because we
|
||||
// may skip uncommitted data that should be visible to the transaction for
|
||||
// reading own writes.
|
||||
snapshot =
|
||||
reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
|
||||
if (callback) {
|
||||
snapshot = std::max(snapshot, callback->MaxUnpreparedSequenceNumber());
|
||||
// Already calculated based on read_options.snapshot
|
||||
snapshot = callback->max_visible_seq();
|
||||
} else {
|
||||
snapshot =
|
||||
reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
|
||||
}
|
||||
} else {
|
||||
// Since we get and reference the super version before getting
|
||||
// the snapshot number, without a mutex protection, it is possible
|
||||
// that a memtable switch happened in the middle and not all the
|
||||
// data for this snapshot is available. But it will contain all
|
||||
// the data available in the super version we have, which is also
|
||||
// a valid snapshot to read from.
|
||||
// We shouldn't get snapshot before finding and referencing the super
|
||||
// version because a flush happening in between may compact away data for
|
||||
// the snapshot, but the snapshot is earlier than the data overwriting it,
|
||||
// so users may see wrong results.
|
||||
// Note that the snapshot is assigned AFTER referencing the super
|
||||
// version because otherwise a flush happening in between may compact away
|
||||
// data for the snapshot, so the reader would see neither data that was be
|
||||
// visible to the snapshot before compaction nor the newer data inserted
|
||||
// afterwards.
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
if (callback) {
|
||||
callback->Refresh(snapshot);
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::GetImpl:3");
|
||||
TEST_SYNC_POINT("DBImpl::GetImpl:4");
|
||||
@@ -1447,7 +1462,7 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
}
|
||||
MeasureTime(stats_, BYTES_PER_READ, size);
|
||||
RecordInHistogram(stats_, BYTES_PER_READ, size);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1632,13 +1647,196 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
|
||||
MeasureTime(stats_, BYTES_PER_MULTIGET, bytes_read);
|
||||
RecordInHistogram(stats_, BYTES_PER_MULTIGET, bytes_read);
|
||||
PERF_COUNTER_ADD(multiget_read_bytes, bytes_read);
|
||||
PERF_TIMER_STOP(get_post_process_time);
|
||||
|
||||
return stat_list;
|
||||
}
|
||||
|
||||
// Order keys by CF ID, followed by key contents
|
||||
struct CompareKeyContext {
|
||||
inline bool operator()(const KeyContext* lhs, const KeyContext* rhs) {
|
||||
const Comparator* comparator = cfd->user_comparator();
|
||||
int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
|
||||
if (cmp < 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const ColumnFamilyData* cfd;
|
||||
};
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
key_context.emplace_back(keys[i], &values[i], &statuses[i]);
|
||||
}
|
||||
|
||||
MultiGetImpl(read_options, column_family, key_context, sorted_input, nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGetImpl(
|
||||
const ReadOptions& read_options, ColumnFamilyHandle* column_family,
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE>& key_context,
|
||||
bool sorted_input, ReadCallback* callback, bool* is_blob_index) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_MULTIGET);
|
||||
size_t num_keys = key_context.size();
|
||||
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
{
|
||||
size_t index = 0;
|
||||
for (KeyContext& key : key_context) {
|
||||
#ifndef NDEBUG
|
||||
if (index > 0 && sorted_input) {
|
||||
KeyContext* lhs = &key_context[index-1];
|
||||
KeyContext* rhs = &key_context[index];
|
||||
const Comparator* comparator = cfd->user_comparator();
|
||||
int cmp = comparator->Compare(*(lhs->key), *(rhs->key));
|
||||
assert(cmp <= 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
sorted_keys[index] = &key;
|
||||
index++;
|
||||
}
|
||||
if (!sorted_input) {
|
||||
CompareKeyContext sort_comparator;
|
||||
sort_comparator.cfd = cfd;
|
||||
std::sort(sorted_keys.begin(), sorted_keys.begin() + index,
|
||||
sort_comparator);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of bytes that we read for statistics-recording later
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* super_version = GetAndRefSuperVersion(cfd);
|
||||
SequenceNumber snapshot;
|
||||
if (read_options.snapshot != nullptr) {
|
||||
// Note: In WritePrepared txns this is not necessary but not harmful
|
||||
// either. Because prep_seq > snapshot => commit_seq > snapshot so if
|
||||
// a snapshot is specified we should be fine with skipping seq numbers
|
||||
// that are greater than that.
|
||||
//
|
||||
// In WriteUnprepared, we cannot set snapshot in the lookup key because we
|
||||
// may skip uncommitted data that should be visible to the transaction for
|
||||
// reading own writes.
|
||||
snapshot =
|
||||
reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
|
||||
if (callback) {
|
||||
snapshot = std::max(snapshot, callback->max_visible_seq());
|
||||
}
|
||||
} else {
|
||||
// Since we get and reference the super version before getting
|
||||
// the snapshot number, without a mutex protection, it is possible
|
||||
// that a memtable switch happened in the middle and not all the
|
||||
// data for this snapshot is available. But it will contain all
|
||||
// the data available in the super version we have, which is also
|
||||
// a valid snapshot to read from.
|
||||
// We shouldn't get snapshot before finding and referencing the super
|
||||
// version because a flush happening in between may compact away data for
|
||||
// the snapshot, but the snapshot is earlier than the data overwriting it,
|
||||
// so users may see wrong results.
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
}
|
||||
|
||||
// For each of the given keys, apply the entire "get" process as follows:
|
||||
// First look in the memtable, then in the immutable memtable (if any).
|
||||
// s is both in/out. When in, s could either be OK or MergeInProgress.
|
||||
// merge_operands will contain the sequence of merges in the latter case.
|
||||
size_t keys_left = num_keys;
|
||||
while (keys_left) {
|
||||
size_t batch_size = (keys_left > MultiGetContext::MAX_BATCH_SIZE)
|
||||
? MultiGetContext::MAX_BATCH_SIZE
|
||||
: keys_left;
|
||||
MultiGetContext ctx(&sorted_keys[num_keys - keys_left], batch_size,
|
||||
snapshot);
|
||||
MultiGetRange range = ctx.GetMultiGetRange();
|
||||
bool lookup_current = false;
|
||||
|
||||
keys_left -= batch_size;
|
||||
for (auto mget_iter = range.begin(); mget_iter != range.end();
|
||||
++mget_iter) {
|
||||
MergeContext& merge_context = mget_iter->merge_context;
|
||||
merge_context.Clear();
|
||||
Status& s = *mget_iter->s;
|
||||
PinnableSlice* value = mget_iter->value;
|
||||
s = Status::OK();
|
||||
|
||||
bool skip_memtable =
|
||||
(read_options.read_tier == kPersistedTier &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed));
|
||||
bool done = false;
|
||||
if (!skip_memtable) {
|
||||
if (super_version->mem->Get(*(mget_iter->lkey), value->GetSelf(), &s,
|
||||
&merge_context,
|
||||
&mget_iter->max_covering_tombstone_seq,
|
||||
read_options, callback, is_blob_index)) {
|
||||
done = true;
|
||||
value->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if (super_version->imm->Get(
|
||||
*(mget_iter->lkey), value->GetSelf(), &s, &merge_context,
|
||||
&mget_iter->max_covering_tombstone_seq, read_options,
|
||||
callback, is_blob_index)) {
|
||||
done = true;
|
||||
value->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
}
|
||||
if (done) {
|
||||
range.MarkKeyDone(mget_iter);
|
||||
} else {
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
lookup_current = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lookup_current) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->MultiGet(read_options, &range, callback,
|
||||
is_blob_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
PERF_TIMER_GUARD(get_post_process_time);
|
||||
size_t num_found = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
for (KeyContext& key : key_context) {
|
||||
if (key.s->ok()) {
|
||||
bytes_read += key.value->size();
|
||||
num_found++;
|
||||
}
|
||||
}
|
||||
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
|
||||
RecordTick(stats_, NUMBER_MULTIGET_CALLS);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_READ, num_keys);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_KEYS_FOUND, num_found);
|
||||
RecordTick(stats_, NUMBER_MULTIGET_BYTES_READ, bytes_read);
|
||||
RecordInHistogram(stats_, BYTES_PER_MULTIGET, bytes_read);
|
||||
PERF_COUNTER_ADD(multiget_read_bytes, bytes_read);
|
||||
PERF_TIMER_STOP(get_post_process_time);
|
||||
}
|
||||
|
||||
Status DBImpl::CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) {
|
||||
@@ -1939,7 +2137,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.tailing) {
|
||||
if (read_options.tailing) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
result = nullptr;
|
||||
@@ -2113,6 +2311,18 @@ SnapshotImpl* DBImpl::GetSnapshotImpl(bool is_write_conflict_boundary,
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
namespace {
|
||||
typedef autovector<ColumnFamilyData*, 2> CfdList;
|
||||
bool CfdListContains(const CfdList& list, ColumnFamilyData* cfd) {
|
||||
for (const ColumnFamilyData* t : list) {
|
||||
if (t == cfd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
const SnapshotImpl* casted_s = reinterpret_cast<const SnapshotImpl*>(s);
|
||||
{
|
||||
@@ -2126,15 +2336,35 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
} else {
|
||||
oldest_snapshot = snapshots_.oldest()->number_;
|
||||
}
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
// Avoid to go through every column family by checking a global threshold
|
||||
// first.
|
||||
if (oldest_snapshot > bottommost_files_mark_threshold_) {
|
||||
CfdList cf_scheduled;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(oldest_snapshot);
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate a new threshold, skipping those CFs where compactions are
|
||||
// scheduled. We do not do the same pass as the previous loop because
|
||||
// mutex might be unlocked during the loop, making the result inaccurate.
|
||||
SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (CfdListContains(cf_scheduled, cfd)) {
|
||||
continue;
|
||||
}
|
||||
new_bottommost_files_mark_threshold = std::min(
|
||||
new_bottommost_files_mark_threshold,
|
||||
cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
bottommost_files_mark_threshold_ = new_bottommost_files_mark_threshold;
|
||||
}
|
||||
}
|
||||
delete casted_s;
|
||||
@@ -2753,7 +2983,8 @@ Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
char* buffer = reinterpret_cast<char*>(alloca(static_cast<size_t>(file_size)));
|
||||
char* buffer =
|
||||
reinterpret_cast<char*>(alloca(static_cast<size_t>(file_size)));
|
||||
Slice id;
|
||||
s = id_file_reader->Read(static_cast<size_t>(file_size), &id, buffer);
|
||||
if (!s.ok()) {
|
||||
@@ -2840,13 +3071,13 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
InfoLogPrefix info_log_prefix(!soptions.db_log_dir.empty(), dbname);
|
||||
for (const auto& fname : filenames) {
|
||||
if (ParseFileName(fname, &number, info_log_prefix.prefix, &type) &&
|
||||
type != kDBLockFile) { // Lock file will be deleted at end
|
||||
type != kDBLockFile) { // Lock file will be deleted at end
|
||||
Status del;
|
||||
std::string path_to_delete = dbname + "/" + fname;
|
||||
if (type == kMetaDatabase) {
|
||||
del = DestroyDB(path_to_delete, options);
|
||||
} else if (type == kTableFile) {
|
||||
del = DeleteSSTFile(&soptions, path_to_delete, dbname);
|
||||
} else if (type == kTableFile || type == kLogFile) {
|
||||
del = DeleteDBFile(&soptions, path_to_delete, dbname);
|
||||
} else {
|
||||
del = env->DeleteFile(path_to_delete);
|
||||
}
|
||||
@@ -2878,9 +3109,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
if (env->GetChildren(path, &filenames).ok()) {
|
||||
for (const auto& fname : filenames) {
|
||||
if (ParseFileName(fname, &number, &type) &&
|
||||
type == kTableFile) { // Lock file will be deleted at end
|
||||
type == kTableFile) { // Lock file will be deleted at end
|
||||
std::string table_path = path + "/" + fname;
|
||||
Status del = DeleteSSTFile(&soptions, table_path, dbname);
|
||||
Status del = DeleteDBFile(&soptions, table_path, dbname);
|
||||
if (result.ok() && !del.ok()) {
|
||||
result = del;
|
||||
}
|
||||
@@ -2905,9 +3136,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
if (env->GetChildren(archivedir, &archiveFiles).ok()) {
|
||||
// Delete archival files.
|
||||
for (const auto& file : archiveFiles) {
|
||||
if (ParseFileName(file, &number, &type) &&
|
||||
type == kLogFile) {
|
||||
Status del = env->DeleteFile(archivedir + "/" + file);
|
||||
if (ParseFileName(file, &number, &type) && type == kLogFile) {
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, archivedir + "/" + file, archivedir);
|
||||
if (result.ok() && !del.ok()) {
|
||||
result = del;
|
||||
}
|
||||
@@ -2920,7 +3151,9 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
if (wal_dir_exists) {
|
||||
for (const auto& file : walDirFiles) {
|
||||
if (ParseFileName(file, &number, &type) && type == kLogFile) {
|
||||
Status del = env->DeleteFile(LogFileName(soptions.wal_dir, number));
|
||||
Status del =
|
||||
DeleteDBFile(&soptions, LogFileName(soptions.wal_dir, number),
|
||||
soptions.wal_dir);
|
||||
if (result.ok() && !del.ok()) {
|
||||
result = del;
|
||||
}
|
||||
@@ -3115,7 +3348,7 @@ void DumpRocksDBBuildVersion(Logger* log) {
|
||||
ROCKS_LOG_HEADER(log, "Git sha %s", rocksdb_build_git_sha);
|
||||
ROCKS_LOG_HEADER(log, "Compile date %s", rocksdb_build_compile_date);
|
||||
#else
|
||||
(void)log; // ignore "-Wunused-parameter"
|
||||
(void)log; // ignore "-Wunused-parameter"
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -3305,9 +3538,9 @@ Status DBImpl::IngestExternalFiles(
|
||||
exec_results.emplace_back(false, Status::OK());
|
||||
}
|
||||
// TODO(yanqin) maybe make jobs run in parallel
|
||||
uint64_t start_file_number = next_file_number;
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
uint64_t start_file_number =
|
||||
next_file_number + args[i - 1].external_files.size();
|
||||
start_file_number += args[i - 1].external_files.size();
|
||||
auto* cfd =
|
||||
static_cast<ColumnFamilyHandleImpl*>(args[i].column_family)->cfd();
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
@@ -3553,8 +3786,8 @@ Status DBImpl::VerifyChecksum() {
|
||||
Options opts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
opts = Options(BuildDBOptions(immutable_db_options_,
|
||||
mutable_db_options_), cfd->GetLatestCFOptions());
|
||||
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();
|
||||
@@ -3619,8 +3852,13 @@ Status DBImpl::StartTrace(const TraceOptions& trace_options,
|
||||
|
||||
Status DBImpl::EndTrace() {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
Status s = tracer_->Close();
|
||||
tracer_.reset();
|
||||
Status s;
|
||||
if (tracer_ != nullptr) {
|
||||
s = tracer_->Close();
|
||||
tracer_.reset();
|
||||
} else {
|
||||
return Status::IOError("No trace file to close");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+147
-94
@@ -124,6 +124,25 @@ class DBImpl : public DB {
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
|
||||
// This MultiGet is a batched version, which may be faster than calling Get
|
||||
// multiple times, especially if the keys have some spatial locality that
|
||||
// enables them to be queried in the same SST files/set of files. The larger
|
||||
// the batch size, the more scope for batching and performance improvement
|
||||
// The values and statuses parameters are arrays with number of elements
|
||||
// equal to keys.size(). This allows the storage for those to be alloacted
|
||||
// by the caller on the stack for small batches
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
void MultiGetImpl(
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE>& key_context,
|
||||
bool sorted_input, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
virtual Status CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
const std::string& column_family,
|
||||
ColumnFamilyHandle** handle) override;
|
||||
@@ -178,10 +197,9 @@ class DBImpl : public DB {
|
||||
virtual bool GetAggregatedIntProperty(const Slice& property,
|
||||
uint64_t* aggregated_value) override;
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags
|
||||
= INCLUDE_FILES) override;
|
||||
virtual void GetApproximateSizes(
|
||||
ColumnFamilyHandle* column_family, const Range* range, int n,
|
||||
uint64_t* sizes, uint8_t include_flags = INCLUDE_FILES) override;
|
||||
using DB::GetApproximateMemTableStats;
|
||||
virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
const Range& range,
|
||||
@@ -235,8 +253,10 @@ class DBImpl : public DB {
|
||||
const FlushOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
virtual Status FlushWAL(bool sync) override;
|
||||
bool TEST_WALBufferIsEmpty();
|
||||
bool TEST_WALBufferIsEmpty(bool lock = true);
|
||||
virtual Status SyncWAL() override;
|
||||
virtual Status LockWAL() override;
|
||||
virtual Status UnlockWAL() override;
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override;
|
||||
virtual SequenceNumber GetLastPublishedSequence() const {
|
||||
@@ -284,9 +304,8 @@ class DBImpl : public DB {
|
||||
// Status::NotFound() will be returned if the current DB does not have
|
||||
// any column family match the specified name.
|
||||
// TODO(yhchiang): output parameter is placed in the end in this codebase.
|
||||
virtual void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
Status SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override;
|
||||
@@ -376,12 +395,15 @@ class DBImpl : public DB {
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
// max_file_num_to_ignore allows bottom level compaction to filter out newly
|
||||
// compacted SST files. Setting max_file_num_to_ignore to kMaxUint64 will
|
||||
// disable the filtering
|
||||
Status RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
uint32_t max_subcompactions,
|
||||
int output_level,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive,
|
||||
bool disallow_trivial_move = false);
|
||||
bool exclusive, bool disallow_trivial_move,
|
||||
uint64_t max_file_num_to_ignore);
|
||||
|
||||
// Return an internal iterator over the current state of the database.
|
||||
// The keys of this iterator are internal keys (see format.h).
|
||||
@@ -427,8 +449,8 @@ class DBImpl : public DB {
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes(ColumnFamilyHandle* column_family =
|
||||
nullptr);
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes(
|
||||
ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
// Return the current manifest file no.
|
||||
uint64_t TEST_Current_Manifest_FileNo();
|
||||
@@ -539,6 +561,13 @@ class DBImpl : public DB {
|
||||
|
||||
const SnapshotList& snapshots() const { return snapshots_; }
|
||||
|
||||
void LoadSnapshots(std::vector<SequenceNumber>* snap_vector,
|
||||
SequenceNumber* oldest_write_conflict_snapshot,
|
||||
const SequenceNumber& max_seq) const {
|
||||
InstrumentedMutexLock l(mutex());
|
||||
snapshots().GetAll(snap_vector, oldest_write_conflict_snapshot, max_seq);
|
||||
}
|
||||
|
||||
const ImmutableDBOptions& immutable_db_options() const {
|
||||
return immutable_db_options_;
|
||||
}
|
||||
@@ -717,7 +746,7 @@ class DBImpl : public DB {
|
||||
// Not thread-safe.
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
|
||||
InstrumentedMutex* mutex() { return &mutex_; }
|
||||
InstrumentedMutex* mutex() const { return &mutex_; }
|
||||
|
||||
Status NewDB();
|
||||
|
||||
@@ -758,6 +787,46 @@ class DBImpl : public DB {
|
||||
std::unique_ptr<Tracer> tracer_;
|
||||
InstrumentedMutex trace_mutex_;
|
||||
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
mutable InstrumentedMutex mutex_;
|
||||
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Additonal options for compaction and flush
|
||||
EnvOptions env_options_for_compaction_;
|
||||
|
||||
std::unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
|
||||
// Increase the sequence number after writing each batch, whether memtable is
|
||||
// disabled for that or not. Otherwise the sequence number is increased after
|
||||
// writing each key into memtable. This implies that when disable_memtable is
|
||||
// set, the seq is not increased at all.
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// This determines during recovery whether we expect one writebatch per
|
||||
// recovered transaction, or potentially multiple writebatches per
|
||||
// transaction. For WriteUnprepared, this is set to false, since multiple
|
||||
// batches can exist per transaction.
|
||||
//
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// Except in DB::Open(), WriteOptionsFile can only be called when:
|
||||
// Persist options to options file.
|
||||
// If need_mutex_lock = false, the method will lock DB mutex.
|
||||
@@ -778,13 +847,12 @@ class DBImpl : public DB {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int job_id, TableProperties prop);
|
||||
|
||||
void NotifyOnCompactionBegin(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats, int job_id);
|
||||
|
||||
void NotifyOnCompactionCompleted(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
void NotifyOnCompactionCompleted(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
@@ -845,6 +913,14 @@ class DBImpl : public DB {
|
||||
// Actual implementation of Close()
|
||||
Status CloseImpl();
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
virtual Status Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
@@ -865,6 +941,7 @@ class DBImpl : public DB {
|
||||
friend class CompactedDBImpl;
|
||||
friend class DBTest_ConcurrentFlushWAL_Test;
|
||||
friend class DBTest_MixedSlowdownOptionsStop_Test;
|
||||
friend class DBCompactionTest_CompactBottomLevelFilesWithDeletions_Test;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackTest_WriteWithCallbackTest_Test;
|
||||
@@ -892,13 +969,6 @@ class DBImpl : public DB {
|
||||
struct PrepickedCompaction;
|
||||
struct PurgeFileInfo;
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
Status ResumeImpl();
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
@@ -948,7 +1018,8 @@ class DBImpl : public DB {
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
// Argument required by background flush thread.
|
||||
struct BGFlushArg {
|
||||
@@ -971,19 +1042,26 @@ class DBImpl : public DB {
|
||||
SuperVersionContext* superversion_context_;
|
||||
};
|
||||
|
||||
// Argument passed to flush thread.
|
||||
struct FlushThreadArg {
|
||||
DBImpl* db_;
|
||||
|
||||
Env::Priority thread_pri_;
|
||||
};
|
||||
|
||||
// Flush the memtables of (multiple) column families to multiple files on
|
||||
// persistent storage.
|
||||
Status FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
|
||||
|
||||
Status AtomicFlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
virtual Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
@@ -1122,18 +1200,21 @@ class DBImpl : public DB {
|
||||
// Runs a pre-chosen universal compaction involving bottom level in a
|
||||
// separate, bottom-pri thread pool.
|
||||
static void BGWorkBottomCompaction(void* arg);
|
||||
static void BGWorkFlush(void* db);
|
||||
static void BGWorkFlush(void* arg);
|
||||
static void BGWorkPurge(void* arg);
|
||||
static void UnscheduleCallback(void* arg);
|
||||
static void UnscheduleCompactionCallback(void* arg);
|
||||
static void UnscheduleFlushCallback(void* arg);
|
||||
void BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority bg_thread_pri);
|
||||
void BackgroundCallFlush();
|
||||
Env::Priority thread_pri);
|
||||
void BackgroundCallFlush(Env::Priority thread_pri);
|
||||
void BackgroundCallPurge();
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction);
|
||||
PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason);
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
@@ -1161,7 +1242,8 @@ class DBImpl : public DB {
|
||||
// Return the minimum empty level that could hold the total data in the
|
||||
// input level. Return the input level, if such level could not be found.
|
||||
int FindMinimumEmptyLevelFitting(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options, int level);
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level);
|
||||
|
||||
// Move the files in the input level to the target level.
|
||||
// If target_level < 0, automatically calculate the minimum level that could
|
||||
@@ -1204,12 +1286,6 @@ class DBImpl : public DB {
|
||||
// and log_empty_. Refer to the definition of each variable below for more
|
||||
// details.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
mutable InstrumentedMutex mutex_;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
// This condition variable is signaled on these conditions:
|
||||
@@ -1241,12 +1317,9 @@ class DBImpl : public DB {
|
||||
// read and writes are protected by log_write_mutex_ instead. This is to avoid
|
||||
// expesnive mutex_ lock during WAL write, which update log_empty_.
|
||||
bool log_empty_;
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
std::unique_ptr<ColumnFamilyMemTablesImpl> column_family_memtables_;
|
||||
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number)
|
||||
: number(_number) {}
|
||||
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
uint64_t number;
|
||||
uint64_t size = 0;
|
||||
@@ -1309,12 +1382,7 @@ class DBImpl : public DB {
|
||||
WriteBatch cached_recoverable_state_;
|
||||
std::atomic<bool> cached_recoverable_state_empty_ = {true};
|
||||
std::atomic<uint64_t> total_log_size_;
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
uint64_t max_total_in_memory_state_;
|
||||
// If true, we have only one (default) column family. We use this to optimize
|
||||
// some code-paths
|
||||
bool single_column_family_mode_;
|
||||
|
||||
// If this is non-empty, we need to delete these log files in background
|
||||
// threads. Protected by db mutex.
|
||||
autovector<log::Writer*> logs_to_free_;
|
||||
@@ -1463,15 +1531,15 @@ class DBImpl : public DB {
|
||||
uint32_t output_path_id;
|
||||
Status status;
|
||||
bool done;
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
bool in_progress; // compaction request being processed?
|
||||
bool incomplete; // only part of requested range compacted
|
||||
bool exclusive; // current behavior of only one manual
|
||||
bool disallow_trivial_move; // Force actual compaction to run
|
||||
const InternalKey* begin; // nullptr means beginning of key range
|
||||
const InternalKey* end; // nullptr means end of key range
|
||||
InternalKey* manual_end; // how far we are compacting
|
||||
InternalKey tmp_storage; // Used to keep track of compaction progress
|
||||
InternalKey tmp_storage1; // Used to keep track of compaction progress
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
@@ -1533,12 +1601,6 @@ class DBImpl : public DB {
|
||||
|
||||
std::string db_absolute_path_;
|
||||
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Additonal options for compaction and flush
|
||||
EnvOptions env_options_for_compaction_;
|
||||
|
||||
// Number of running IngestExternalFile() calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_ingest_file_;
|
||||
@@ -1562,6 +1624,10 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// The min threshold to triggere bottommost compaction for removing
|
||||
// garbages, among all column families.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
LogsWithPrepTracker logs_with_prep_tracker_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
@@ -1597,9 +1663,9 @@ class DBImpl : public DB {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props)
|
||||
override;
|
||||
virtual Status GetPropertiesOfAllTables(
|
||||
ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props) override;
|
||||
virtual Status GetPropertiesOfTablesInRange(
|
||||
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
|
||||
TablePropertiesCollection* props) override;
|
||||
@@ -1637,28 +1703,16 @@ class DBImpl : public DB {
|
||||
void MarkAsGrabbedForPurge(uint64_t file_number);
|
||||
|
||||
size_t GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
Env::WriteLifeTimeHint CalculateWALWriteHint() {
|
||||
return Env::WLTH_SHORT;
|
||||
}
|
||||
Env::WriteLifeTimeHint CalculateWALWriteHint() { return Env::WLTH_SHORT; }
|
||||
|
||||
Status CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
size_t preallocate_block_size, log::Writer** new_log);
|
||||
|
||||
// When set, we use a separate queue for writes that dont write to memtable.
|
||||
// In 2PC these are the writes at Prepare phase.
|
||||
const bool two_write_queues_;
|
||||
const bool manual_wal_flush_;
|
||||
// Increase the sequence number after writing each batch, whether memtable is
|
||||
// disabled for that or not. Otherwise the sequence number is increased after
|
||||
// writing each key into memtable. This implies that when disable_memtable is
|
||||
// set, the seq is not increased at all.
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// This determines during recovery whether we expect one writebatch per
|
||||
// recovered transaction, or potentially multiple writebatches per
|
||||
// transaction. For WriteUnprepared, this is set to false, since multiple
|
||||
// batches can exist per transaction.
|
||||
//
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
|
||||
// LastSequence also indicates last published sequence visibile to the
|
||||
// readers. Otherwise LastPublishedSequence should be used.
|
||||
const bool last_seq_same_as_publish_seq_;
|
||||
@@ -1701,8 +1755,7 @@ class DBImpl : public DB {
|
||||
InstrumentedCondVar atomic_flush_install_cv_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
const Options& src);
|
||||
extern Options SanitizeOptions(const std::string& db, const Options& src);
|
||||
|
||||
extern DBOptions SanitizeOptions(const std::string& db, const DBOptions& src);
|
||||
|
||||
|
||||
+155
-61
@@ -135,12 +135,12 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
assert(cfd->imm()->NumNotFlushed() != 0);
|
||||
assert(cfd->imm()->IsFlushPending());
|
||||
|
||||
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
nullptr /* memtable_id */, env_options_for_compaction_, versions_.get(),
|
||||
@@ -149,7 +149,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetDataDir(cfd, 0U),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */);
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
@@ -218,7 +218,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
cfd->ioptions()->cf_paths[0].path, file_meta.fd.GetNumber());
|
||||
sfm->OnAddFile(file_path);
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
Status new_bg_error = Status::SpaceLimit("Max allowed space was reached");
|
||||
Status new_bg_error =
|
||||
Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
|
||||
&new_bg_error);
|
||||
@@ -232,10 +233,10 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
|
||||
Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
return AtomicFlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
return AtomicFlushMemTablesToOutputFiles(
|
||||
bg_flush_args, made_progress, job_context, log_buffer, thread_pri);
|
||||
}
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
@@ -250,7 +251,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
Status s = FlushMemTableToOutputFile(
|
||||
cfd, mutable_cf_options, made_progress, job_context,
|
||||
superversion_context, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, log_buffer);
|
||||
snapshot_checker, log_buffer, thread_pri);
|
||||
if (!s.ok()) {
|
||||
status = s;
|
||||
if (!s.IsShutdownInProgress()) {
|
||||
@@ -274,7 +275,7 @@ Status DBImpl::FlushMemTablesToOutputFiles(
|
||||
*/
|
||||
Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
const autovector<BGFlushArg>& bg_flush_args, bool* made_progress,
|
||||
JobContext* job_context, LogBuffer* log_buffer) {
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -331,7 +332,8 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
snapshot_checker, job_context, log_buffer, directories_.GetDbDir(),
|
||||
data_dir, GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
stats_, &event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
false /* sync_output_directory */, false /* write_manifest */);
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
thread_pri);
|
||||
jobs.back().PickMemTable();
|
||||
}
|
||||
|
||||
@@ -395,12 +397,21 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
// If db is NOT shutting down, and one or more column families have been
|
||||
// dropped.
|
||||
// TODO: use separate status code for db shutdown and column family dropped.
|
||||
if (s.IsShutdownInProgress() &&
|
||||
!shutting_down_.load(std::memory_order_acquire)) {
|
||||
s = Status::OK();
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
// Sync on all distinct output directories.
|
||||
for (auto dir : distinct_output_dirs) {
|
||||
if (dir != nullptr) {
|
||||
s = dir->Fsync();
|
||||
if (!s.ok()) {
|
||||
Status error_status = dir->Fsync();
|
||||
if (!error_status.ok()) {
|
||||
s = error_status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -467,7 +478,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
&job_context->memtables_to_free, directories_.GetDbDir(), log_buffer);
|
||||
}
|
||||
|
||||
if (s.ok() || s.IsShutdownInProgress()) {
|
||||
if (s.ok()) {
|
||||
assert(num_cfs ==
|
||||
static_cast<int>(job_context->superversion_contexts.size()));
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
@@ -682,6 +693,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
}
|
||||
|
||||
int max_level_with_files = 0;
|
||||
// max_file_num_to_ignore can be used to filter out newly created SST files,
|
||||
// useful for bottom level compaction in a manual compaction
|
||||
uint64_t max_file_num_to_ignore = port::kMaxUint64;
|
||||
uint64_t next_file_number = port::kMaxUint64;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
Version* base = cfd->current();
|
||||
@@ -691,9 +706,11 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
max_level_with_files = level;
|
||||
}
|
||||
}
|
||||
next_file_number = versions_->current_next_file_number();
|
||||
}
|
||||
|
||||
int final_output_level = 0;
|
||||
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->NumberLevels() > 1) {
|
||||
// Always compact all files together.
|
||||
@@ -703,8 +720,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
final_output_level--;
|
||||
}
|
||||
s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
|
||||
final_output_level, options.target_path_id,
|
||||
options.max_subcompactions, begin, end, exclusive);
|
||||
final_output_level, options, begin, end, exclusive,
|
||||
false, max_file_num_to_ignore);
|
||||
} else {
|
||||
for (int level = 0; level <= max_level_with_files; level++) {
|
||||
int output_level;
|
||||
@@ -729,6 +746,10 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
continue;
|
||||
}
|
||||
output_level = level;
|
||||
// update max_file_num_to_ignore only for bottom level compaction
|
||||
// because data in newly compacted files in middle levels may still need
|
||||
// to be pushed down
|
||||
max_file_num_to_ignore = next_file_number;
|
||||
} else {
|
||||
output_level = level + 1;
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleLevel &&
|
||||
@@ -737,8 +758,8 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
output_level = ColumnFamilyData::kCompactToBaseLevel;
|
||||
}
|
||||
}
|
||||
s = RunManualCompaction(cfd, level, output_level, options.target_path_id,
|
||||
options.max_subcompactions, begin, end, exclusive);
|
||||
s = RunManualCompaction(cfd, level, output_level, options, begin, end,
|
||||
exclusive, false, max_file_num_to_ignore);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -777,6 +798,31 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class SnapshotListFetchCallbackImpl : public SnapshotListFetchCallback {
|
||||
public:
|
||||
SnapshotListFetchCallbackImpl(DBImpl* db_impl, Env* env,
|
||||
uint64_t snap_refresh_nanos, Logger* info_log)
|
||||
: SnapshotListFetchCallback(env, snap_refresh_nanos),
|
||||
db_impl_(db_impl),
|
||||
info_log_(info_log) {}
|
||||
virtual void Refresh(std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber max) override {
|
||||
size_t prev = snapshots->size();
|
||||
snapshots->clear();
|
||||
db_impl_->LoadSnapshots(snapshots, nullptr, max);
|
||||
size_t now = snapshots->size();
|
||||
ROCKS_LOG_DEBUG(info_log_,
|
||||
"Compaction snapshot count refreshed from %zu to %zu", prev,
|
||||
now);
|
||||
}
|
||||
|
||||
private:
|
||||
DBImpl* db_impl_;
|
||||
Logger* info_log_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
@@ -948,6 +994,9 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJobStats compaction_job_stats;
|
||||
SnapshotListFetchCallbackImpl fetch_callback(
|
||||
this, env_, c->mutable_cf_options()->snap_refresh_nanos,
|
||||
immutable_db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
@@ -957,7 +1006,11 @@ Status DBImpl::CompactFilesImpl(
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
&compaction_job_stats, Env::Priority::USER,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
|
||||
// Creating a compaction influences the compaction score because the score
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
@@ -1061,8 +1114,8 @@ Status DBImpl::ContinueBackgroundWork() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd,
|
||||
Compaction *c, const Status &st,
|
||||
void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
const Status& st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1307,8 +1360,9 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
});
|
||||
s = AtomicFlushMemTables(cfds, flush_options, FlushReason::kManualFlush);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Manual atomic flush finished, status: %s\n",
|
||||
"=====Column families:=====", s.ToString().c_str());
|
||||
"Manual atomic flush finished, status: %s\n"
|
||||
"=====Column families:=====",
|
||||
s.ToString().c_str());
|
||||
for (auto cfh : column_families) {
|
||||
auto cfhi = static_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s",
|
||||
@@ -1320,11 +1374,11 @@ Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
uint32_t max_subcompactions,
|
||||
const Slice* begin, const Slice* end,
|
||||
bool exclusive, bool disallow_trivial_move) {
|
||||
Status DBImpl::RunManualCompaction(
|
||||
ColumnFamilyData* cfd, int input_level, int output_level,
|
||||
const CompactRangeOptions& compact_range_options, const Slice* begin,
|
||||
const Slice* end, bool exclusive, bool disallow_trivial_move,
|
||||
uint64_t max_file_num_to_ignore) {
|
||||
assert(input_level == ColumnFamilyData::kCompactAllLevels ||
|
||||
input_level >= 0);
|
||||
|
||||
@@ -1337,7 +1391,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
manual.cfd = cfd;
|
||||
manual.input_level = input_level;
|
||||
manual.output_level = output_level;
|
||||
manual.output_path_id = output_path_id;
|
||||
manual.output_path_id = compact_range_options.target_path_id;
|
||||
manual.done = false;
|
||||
manual.in_progress = false;
|
||||
manual.incomplete = false;
|
||||
@@ -1412,9 +1466,9 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
(((manual.manual_end = &manual.tmp_storage1) != nullptr) &&
|
||||
((compaction = manual.cfd->CompactRange(
|
||||
*manual.cfd->GetLatestMutableCFOptions(), manual.input_level,
|
||||
manual.output_level, manual.output_path_id, max_subcompactions,
|
||||
manual.begin, manual.end, &manual.manual_end,
|
||||
&manual_conflict)) == nullptr &&
|
||||
manual.output_level, compact_range_options, manual.begin,
|
||||
manual.end, &manual.manual_end, &manual_conflict,
|
||||
max_file_num_to_ignore)) == nullptr &&
|
||||
manual_conflict))) {
|
||||
// exclusive manual compactions should not see a conflict during
|
||||
// CompactRange
|
||||
@@ -1445,7 +1499,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
manual.incomplete = false;
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleCallback);
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
scheduled = true;
|
||||
}
|
||||
}
|
||||
@@ -1622,7 +1676,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
// it against various constrains and delays flush if it'd cause write stall.
|
||||
// Called should check status and flush_needed to see if flush already happened.
|
||||
Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
bool* flush_needed) {
|
||||
bool* flush_needed) {
|
||||
{
|
||||
*flush_needed = true;
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -1770,7 +1824,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
// we paused the background work
|
||||
return;
|
||||
} else if (error_handler_.IsBGWorkStopped() &&
|
||||
!error_handler_.IsRecoveryInProgress()) {
|
||||
!error_handler_.IsRecoveryInProgress()) {
|
||||
// There has been a hard error and this call is not part of the recovery
|
||||
// sequence. Bail out here so we don't get into an endless loop of
|
||||
// scheduling BG work which will again call this function
|
||||
@@ -1785,7 +1839,11 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
while (!is_flush_pool_empty && unscheduled_flushes_ > 0 &&
|
||||
bg_flush_scheduled_ < bg_job_limits.max_flushes) {
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::HIGH, this);
|
||||
FlushThreadArg* fta = new FlushThreadArg;
|
||||
fta->db_ = this;
|
||||
fta->thread_pri_ = Env::Priority::HIGH;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::HIGH, this,
|
||||
&DBImpl::UnscheduleFlushCallback);
|
||||
}
|
||||
|
||||
// special case -- if high-pri (flush) thread pool is empty, then schedule
|
||||
@@ -1795,7 +1853,11 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
bg_flush_scheduled_ + bg_compaction_scheduled_ <
|
||||
bg_job_limits.max_flushes) {
|
||||
bg_flush_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, this, Env::Priority::LOW, this);
|
||||
FlushThreadArg* fta = new FlushThreadArg;
|
||||
fta->db_ = this;
|
||||
fta->thread_pri_ = Env::Priority::LOW;
|
||||
env_->Schedule(&DBImpl::BGWorkFlush, fta, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleFlushCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1825,7 +1887,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
bg_compaction_scheduled_++;
|
||||
unscheduled_compactions_--;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
&DBImpl::UnscheduleCallback);
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1940,10 +2002,13 @@ void DBImpl::SchedulePendingPurge(std::string fname, std::string dir_to_sync,
|
||||
purge_queue_.push_back(std::move(file_info));
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkFlush(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
void DBImpl::BGWorkFlush(void* arg) {
|
||||
FlushThreadArg fta = *(reinterpret_cast<FlushThreadArg*>(arg));
|
||||
delete reinterpret_cast<FlushThreadArg*>(arg);
|
||||
|
||||
IOSTATS_SET_THREAD_POOL_ID(fta.thread_pri_);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush");
|
||||
reinterpret_cast<DBImpl*>(db)->BackgroundCallFlush();
|
||||
reinterpret_cast<DBImpl*>(fta.db_)->BackgroundCallFlush(fta.thread_pri_);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush:done");
|
||||
}
|
||||
|
||||
@@ -1978,7 +2043,7 @@ void DBImpl::BGWorkPurge(void* db) {
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkPurge:end");
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleCallback(void* arg) {
|
||||
void DBImpl::UnscheduleCompactionCallback(void* arg) {
|
||||
CompactionArg ca = *(reinterpret_cast<CompactionArg*>(arg));
|
||||
delete reinterpret_cast<CompactionArg*>(arg);
|
||||
if (ca.prepicked_compaction != nullptr) {
|
||||
@@ -1987,11 +2052,17 @@ void DBImpl::UnscheduleCallback(void* arg) {
|
||||
}
|
||||
delete ca.prepicked_compaction;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleCallback");
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleCompactionCallback");
|
||||
}
|
||||
|
||||
void DBImpl::UnscheduleFlushCallback(void* arg) {
|
||||
delete reinterpret_cast<FlushThreadArg*>(arg);
|
||||
TEST_SYNC_POINT("DBImpl::UnscheduleFlushCallback");
|
||||
}
|
||||
|
||||
Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, FlushReason* reason) {
|
||||
LogBuffer* log_buffer, FlushReason* reason,
|
||||
Env::Priority thread_pri) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status;
|
||||
@@ -2013,6 +2084,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
autovector<BGFlushArg> bg_flush_args;
|
||||
std::vector<SuperVersionContext>& superversion_contexts =
|
||||
job_context->superversion_contexts;
|
||||
autovector<ColumnFamilyData*> column_families_not_to_flush;
|
||||
while (!flush_queue_.empty()) {
|
||||
// This cfd is already referenced
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
@@ -2023,9 +2095,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
if (cfd->IsDropped() || !cfd->imm()->IsFlushPending()) {
|
||||
// can't flush this CF, try next one
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
column_families_not_to_flush.push_back(cfd);
|
||||
continue;
|
||||
}
|
||||
superversion_contexts.emplace_back(SuperVersionContext(true));
|
||||
@@ -2052,7 +2122,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
bg_compaction_scheduled_);
|
||||
}
|
||||
status = FlushMemTablesToOutputFiles(bg_flush_args, made_progress,
|
||||
job_context, log_buffer);
|
||||
job_context, log_buffer, thread_pri);
|
||||
// All the CFDs in the FlushReq must have the same flush reason, so just
|
||||
// grab the first one
|
||||
*reason = bg_flush_args[0].cfd_->GetFlushReason();
|
||||
@@ -2064,10 +2134,15 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto cfd : column_families_not_to_flush) {
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::BackgroundCallFlush() {
|
||||
void DBImpl::BackgroundCallFlush(Env::Priority thread_pri) {
|
||||
bool made_progress = false;
|
||||
JobContext job_context(next_job_id_.fetch_add(1), true);
|
||||
|
||||
@@ -2084,8 +2159,8 @@ void DBImpl::BackgroundCallFlush() {
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
FlushReason reason;
|
||||
|
||||
Status s =
|
||||
BackgroundFlush(&made_progress, &job_context, &log_buffer, &reason);
|
||||
Status s = BackgroundFlush(&made_progress, &job_context, &log_buffer,
|
||||
&reason, thread_pri);
|
||||
if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
reason != FlushReason::kErrorRecovery) {
|
||||
// Wait a little bit before retrying background flush in
|
||||
@@ -2168,12 +2243,12 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
bg_bottom_compaction_scheduled_) ||
|
||||
(bg_thread_pri == Env::Priority::LOW && bg_compaction_scheduled_));
|
||||
Status s = BackgroundCompaction(&made_progress, &job_context, &log_buffer,
|
||||
prepicked_compaction);
|
||||
prepicked_compaction, bg_thread_pri);
|
||||
TEST_SYNC_POINT("BackgroundCallCompaction:1");
|
||||
if (s.IsBusy()) {
|
||||
bg_cv_.SignalAll(); // In case a waiter can proceed despite the error
|
||||
mutex_.Unlock();
|
||||
env_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
env_->SleepForMicroseconds(10000); // prevent hot loop
|
||||
mutex_.Lock();
|
||||
} else if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
// Wait a little bit before retrying background compaction in
|
||||
@@ -2255,7 +2330,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
JobContext* job_context,
|
||||
LogBuffer* log_buffer,
|
||||
PrepickedCompaction* prepicked_compaction) {
|
||||
PrepickedCompaction* prepicked_compaction,
|
||||
Env::Priority thread_pri) {
|
||||
ManualCompactionState* manual_compaction =
|
||||
prepicked_compaction == nullptr
|
||||
? nullptr
|
||||
@@ -2418,8 +2494,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
status = Status::CompactionTooLarge();
|
||||
} else {
|
||||
// update statistics
|
||||
MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
RecordInHistogram(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
// There are three things that can change compaction score:
|
||||
// 1) When flush or compaction finish. This case is covered by
|
||||
// InstallSuperVersionAndScheduleWork
|
||||
@@ -2568,7 +2644,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ca->prepicked_compaction->task_token = std::move(task_token);
|
||||
++bg_bottom_compaction_scheduled_;
|
||||
env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
|
||||
this, &DBImpl::UnscheduleCallback);
|
||||
this, &DBImpl::UnscheduleCompactionCallback);
|
||||
} else {
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
c->column_family_data());
|
||||
@@ -2582,16 +2658,23 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
SnapshotListFetchCallbackImpl fetch_callback(
|
||||
this, env_, c->mutable_cf_options()->snap_refresh_nanos,
|
||||
immutable_db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_,
|
||||
&mutex_, &error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
&mutex_, &error_handler_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
&compaction_job_stats, thread_pri,
|
||||
immutable_db_options_.max_subcompactions <= 1 &&
|
||||
c->mutable_cf_options()->snap_refresh_nanos > 0
|
||||
? &fetch_callback
|
||||
: nullptr);
|
||||
compaction_job.Prepare();
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
@@ -2814,7 +2897,7 @@ void DBImpl::BuildCompactionJobInfo(
|
||||
fmd->fd.GetNumber(), fmd->fd.GetPathId());
|
||||
compaction_job_info->input_files.push_back(fn);
|
||||
if (compaction_job_info->table_properties.count(fn) == 0) {
|
||||
shared_ptr<const TableProperties> tp;
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
auto s = current->GetTableProperties(&tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
compaction_job_info->table_properties[fn] = tp;
|
||||
@@ -2861,6 +2944,17 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_, mutable_cf_options);
|
||||
|
||||
// There may be a small data race here. The snapshot tricking bottommost
|
||||
// compaction may already be released here. But assuming there will always be
|
||||
// newer snapshot created and released frequently, the compaction will be
|
||||
// triggered soon anyway.
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingCompaction(cfd);
|
||||
|
||||
+15
-12
@@ -26,10 +26,16 @@ void DBImpl::TEST_SwitchWAL() {
|
||||
SwitchWAL(&write_context);
|
||||
}
|
||||
|
||||
bool DBImpl::TEST_WALBufferIsEmpty() {
|
||||
InstrumentedMutexLock wl(&log_write_mutex_);
|
||||
bool DBImpl::TEST_WALBufferIsEmpty(bool lock) {
|
||||
if (lock) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
return cur_log_writer->TEST_BufferIsEmpty();
|
||||
auto res = cur_log_writer->TEST_BufferIsEmpty();
|
||||
if (lock) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
@@ -87,8 +93,9 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO)
|
||||
? level
|
||||
: level + 1;
|
||||
return RunManualCompaction(cfd, level, output_level, 0, 0, begin, end, true,
|
||||
disallow_trivial_move);
|
||||
return RunManualCompaction(cfd, level, output_level, CompactRangeOptions(),
|
||||
begin, end, true, disallow_trivial_move,
|
||||
port::kMaxUint64 /*max_file_num_to_ignore*/);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
@@ -101,7 +108,7 @@ Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, bool allow_write_stall,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
fo.allow_write_stall = allow_write_stall;
|
||||
@@ -143,13 +150,9 @@ Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_LockMutex() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
void DBImpl::TEST_LockMutex() { mutex_.Lock(); }
|
||||
|
||||
void DBImpl::TEST_UnlockMutex() {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
void DBImpl::TEST_UnlockMutex() { mutex_.Unlock(); }
|
||||
|
||||
void* DBImpl::TEST_BeginWrite() {
|
||||
auto w = new WriteThread::Writer();
|
||||
|
||||
+23
-19
@@ -108,7 +108,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
versions_->AddLiveFiles(&job_context->sst_live);
|
||||
if (doing_the_full_scan) {
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
dbname_);
|
||||
std::set<std::string> paths;
|
||||
for (size_t path_id = 0; path_id < immutable_db_options_.db_paths.size();
|
||||
path_id++) {
|
||||
@@ -152,8 +152,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
|
||||
// TODO(icanadi) clean up this mess to avoid having one-off "/" prefixes
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
"/" + file, path);
|
||||
job_context->full_scan_candidate_files.emplace_back("/" + file, path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +162,8 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
env_->GetChildren(immutable_db_options_.wal_dir,
|
||||
&log_files); // Ignore errors
|
||||
for (const std::string& log_file : log_files) {
|
||||
job_context->full_scan_candidate_files.emplace_back(log_file,
|
||||
immutable_db_options_.wal_dir);
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
log_file, immutable_db_options_.wal_dir);
|
||||
}
|
||||
}
|
||||
// Add info log files in db_log_dir
|
||||
@@ -174,8 +173,8 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// Ignore errors
|
||||
env_->GetChildren(immutable_db_options_.db_log_dir, &info_log_files);
|
||||
for (std::string& log_file : info_log_files) {
|
||||
job_context->full_scan_candidate_files.emplace_back(log_file,
|
||||
immutable_db_options_.db_log_dir);
|
||||
job_context->full_scan_candidate_files.emplace_back(
|
||||
log_file, immutable_db_options_.db_log_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,14 +259,14 @@ void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
const std::string& path_to_sync,
|
||||
FileType type, uint64_t number) {
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile) {
|
||||
if (type == kTableFile || type == kLogFile) {
|
||||
file_deletion_status =
|
||||
DeleteSSTFile(&immutable_db_options_, fname, path_to_sync);
|
||||
DeleteDBFile(&immutable_db_options_, fname, path_to_sync);
|
||||
} else {
|
||||
file_deletion_status = env_->DeleteFile(fname);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::DeleteObsoleteFileImpl:AfterDeletion",
|
||||
&file_deletion_status);
|
||||
&file_deletion_status);
|
||||
if (file_deletion_status.ok()) {
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[JOB %d] Delete %s type=%d #%" PRIu64 " -- %s\n", job_id,
|
||||
@@ -322,7 +321,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
const char* kDumbDbName = "";
|
||||
for (auto& file : state.sst_delete_files) {
|
||||
candidate_files.emplace_back(
|
||||
MakeTableFileName(kDumbDbName, file.metadata->fd.GetNumber()), file.path);
|
||||
MakeTableFileName(kDumbDbName, file.metadata->fd.GetNumber()),
|
||||
file.path);
|
||||
if (file.metadata->table_reader_handle) {
|
||||
table_cache_->Release(file.metadata->table_reader_handle);
|
||||
}
|
||||
@@ -332,7 +332,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
for (auto file_num : state.log_delete_files) {
|
||||
if (file_num > 0) {
|
||||
candidate_files.emplace_back(LogFileName(kDumbDbName, file_num),
|
||||
immutable_db_options_.wal_dir);
|
||||
immutable_db_options_.wal_dir);
|
||||
}
|
||||
}
|
||||
for (const auto& filename : state.manifest_delete_files) {
|
||||
@@ -465,13 +465,12 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
} else {
|
||||
dir_to_sync =
|
||||
(type == kLogFile) ? immutable_db_options_.wal_dir : dbname_;
|
||||
fname = dir_to_sync
|
||||
+ (
|
||||
(!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
|
||||
(!to_delete.empty() && to_delete.front() == '/')
|
||||
? "" : "/"
|
||||
)
|
||||
+ to_delete;
|
||||
fname = dir_to_sync +
|
||||
((!dir_to_sync.empty() && dir_to_sync.back() == '/') ||
|
||||
(!to_delete.empty() && to_delete.front() == '/')
|
||||
? ""
|
||||
: "/") +
|
||||
to_delete;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -482,6 +481,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
for (const auto w : state.logs_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
w->Close();
|
||||
}
|
||||
|
||||
Status file_deletion_status;
|
||||
if (schedule_only) {
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
|
||||
+70
-33
@@ -188,6 +188,20 @@ static Status ValidateOptions(
|
||||
"TTL is only supported in Block-Based Table format. ");
|
||||
}
|
||||
}
|
||||
|
||||
if (cfd.options.periodic_compaction_seconds > 0) {
|
||||
if (db_options.max_open_files != -1) {
|
||||
return Status::NotSupported(
|
||||
"Periodic Compaction is only supported when files are always "
|
||||
"kept open (set max_open_files = -1). ");
|
||||
}
|
||||
if (cfd.options.table_factory->Name() !=
|
||||
BlockBasedTableFactory().Name()) {
|
||||
return Status::NotSupported(
|
||||
"Periodic Compaction is only supported in "
|
||||
"Block-Based Table format. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options.db_paths.size() > 4) {
|
||||
@@ -580,7 +594,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
immutable_db_options_.wal_recovery_mode);
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
||||
auto logFileDropped = [this, &fname]() {
|
||||
uint64_t bytes;
|
||||
if (env_->GetFileSize(fname, &bytes).ok()) {
|
||||
@@ -629,8 +643,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
log::Reader reader(immutable_db_options_.info_log, std::move(file_reader),
|
||||
&reporter, true /*checksum*/, log_number,
|
||||
false /* retry_after_eof */);
|
||||
&reporter, true /*checksum*/, log_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
@@ -724,7 +737,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
" mode %d log filter %s returned "
|
||||
"more records (%d) than original (%d) which is not allowed. "
|
||||
"Aborting recovery.",
|
||||
log_number, immutable_db_options_.wal_recovery_mode,
|
||||
log_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode),
|
||||
immutable_db_options_.wal_filter->Name(), new_count,
|
||||
original_count);
|
||||
status = Status::NotSupported(
|
||||
@@ -1029,6 +1043,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.sample_for_compression,
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
@@ -1058,7 +1073,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
stats.micros = env_->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(level, stats);
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta.fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
@@ -1091,6 +1106,45 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
}
|
||||
|
||||
Status DBImpl::CreateWAL(uint64_t log_file_num, uint64_t recycle_log_number,
|
||||
size_t preallocate_block_size, log::Writer** new_log) {
|
||||
Status s;
|
||||
std::unique_ptr<WritableFile> lfile;
|
||||
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
EnvOptions opt_env_options =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options);
|
||||
std::string log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, log_file_num);
|
||||
|
||||
if (recycle_log_number) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"reusing log %" PRIu64 " from recycle list\n",
|
||||
recycle_log_number);
|
||||
std::string old_log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
|
||||
s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile,
|
||||
opt_env_options);
|
||||
} else {
|
||||
s = NewWritableFile(env_, log_fname, &lfile, opt_env_options);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(CalculateWALWriteHint());
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
|
||||
const auto& listeners = immutable_db_options_.listeners;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_options,
|
||||
env_, nullptr /* stats */, listeners));
|
||||
*new_log = new log::Writer(std::move(file_writer), log_file_num,
|
||||
immutable_db_options_.recycle_log_file_num > 0,
|
||||
immutable_db_options_.manual_wal_flush);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
@@ -1151,40 +1205,23 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
return s;
|
||||
}
|
||||
impl->mutex_.Lock();
|
||||
auto write_hint = impl->CalculateWALWriteHint();
|
||||
// Handles create_if_missing, error_if_exists
|
||||
s = impl->Recover(column_families);
|
||||
if (s.ok()) {
|
||||
uint64_t new_log_number = impl->versions_->NewFileNumber();
|
||||
std::unique_ptr<WritableFile> lfile;
|
||||
EnvOptions soptions(db_options);
|
||||
EnvOptions opt_env_options =
|
||||
impl->immutable_db_options_.env->OptimizeForLogWrite(
|
||||
soptions, BuildDBOptions(impl->immutable_db_options_,
|
||||
impl->mutable_db_options_));
|
||||
std::string log_fname =
|
||||
LogFileName(impl->immutable_db_options_.wal_dir, new_log_number);
|
||||
s = NewWritableFile(impl->immutable_db_options_.env, log_fname, &lfile,
|
||||
opt_env_options);
|
||||
log::Writer* new_log = nullptr;
|
||||
const size_t preallocate_block_size =
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size);
|
||||
s = impl->CreateWAL(new_log_number, 0 /*recycle_log_number*/,
|
||||
preallocate_block_size, &new_log);
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
lfile->SetPreallocationBlockSize(
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size));
|
||||
{
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
const auto& listeners = impl->immutable_db_options_.listeners;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), log_fname, opt_env_options,
|
||||
impl->env_, nullptr /* stats */, listeners));
|
||||
impl->logs_.emplace_back(
|
||||
new_log_number,
|
||||
new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
impl->immutable_db_options_.recycle_log_file_num > 0,
|
||||
impl->immutable_db_options_.manual_wal_flush));
|
||||
}
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
impl->logs_.emplace_back(new_log_number, new_log);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// set column family handles
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
|
||||
@@ -60,7 +60,7 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
MeasureTime(stats_, BYTES_PER_READ, size);
|
||||
RecordInHistogram(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -122,6 +122,6 @@ class DBImplReadOnly : public DBImpl {
|
||||
DBImplReadOnly(const DBImplReadOnly&);
|
||||
void operator=(const DBImplReadOnly&);
|
||||
};
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -0,0 +1,524 @@
|
||||
// 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).
|
||||
|
||||
#include "db/db_impl_secondary.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/auto_roll_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
DBImplSecondary::DBImplSecondary(const DBOptions& db_options,
|
||||
const std::string& dbname)
|
||||
: DBImpl(db_options, dbname) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Opening the db in secondary mode");
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
}
|
||||
|
||||
DBImplSecondary::~DBImplSecondary() {}
|
||||
|
||||
Status DBImplSecondary::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool /*readonly*/, bool /*error_if_log_file_exist*/,
|
||||
bool /*error_if_data_exists_in_logs*/) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status s;
|
||||
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
||||
->Recover(column_families, &manifest_reader_, &manifest_reporter_,
|
||||
&manifest_reader_status_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
// Initial max_total_in_memory_state_ before recovery logs.
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
auto* mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
max_total_in_memory_state_ += mutable_cf_options->write_buffer_size *
|
||||
mutable_cf_options->max_write_buffer_number;
|
||||
}
|
||||
if (s.ok()) {
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
default_cf_internal_stats_ = default_cf_handle_->cfd()->internal_stats();
|
||||
single_column_family_mode_ =
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1;
|
||||
|
||||
// Recover from all newer log files than the ones named in the
|
||||
// descriptor.
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
if (s.IsNotFound()) {
|
||||
return Status::InvalidArgument("Failed to open wal_dir",
|
||||
immutable_db_options_.wal_dir);
|
||||
} else if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> logs;
|
||||
// if log_readers_ is non-empty, it means we have applied all logs with log
|
||||
// numbers smaller than the smallest log in log_readers_, so there is no
|
||||
// need to pass these logs to RecoverLogFiles
|
||||
uint64_t log_number_min = 0;
|
||||
if (log_readers_.size() > 0) {
|
||||
log_number_min = log_readers_.begin()->first;
|
||||
}
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kLogFile &&
|
||||
number >= log_number_min) {
|
||||
logs.push_back(number);
|
||||
}
|
||||
}
|
||||
|
||||
if (!logs.empty()) {
|
||||
// Recover in the order in which the logs were generated
|
||||
std::sort(logs.begin(), logs.end());
|
||||
SequenceNumber next_sequence(kMaxSequenceNumber);
|
||||
s = RecoverLogFiles(logs, &next_sequence, true /*read_only*/);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: update options_file_number_ needed?
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// try to find log reader using log_number from log_readers_ map, initialize
|
||||
// if it doesn't exist
|
||||
Status DBImplSecondary::MaybeInitLogReader(
|
||||
uint64_t log_number, log::FragmentBufferedReader** log_reader) {
|
||||
auto iter = log_readers_.find(log_number);
|
||||
// make sure the log file is still present
|
||||
if (iter == log_readers_.end() ||
|
||||
iter->second->reader_->GetLogNumber() != log_number) {
|
||||
// delete the obsolete log reader if log number mismatch
|
||||
if (iter != log_readers_.end()) {
|
||||
log_readers_.erase(iter);
|
||||
}
|
||||
// initialize log reader from log_number
|
||||
// TODO: min_log_number_to_keep_2pc check needed?
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Recovering log #%" PRIu64 " mode %d", log_number,
|
||||
static_cast<int>(immutable_db_options_.wal_recovery_mode));
|
||||
|
||||
std::unique_ptr<SequentialFileReader> file_reader;
|
||||
{
|
||||
std::unique_ptr<SequentialFile> file;
|
||||
Status status = env_->NewSequentialFile(
|
||||
fname, &file, env_->OptimizeForLogRead(env_options_));
|
||||
if (!status.ok()) {
|
||||
*log_reader = nullptr;
|
||||
return status;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), fname));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
LogReaderContainer* log_reader_container = new LogReaderContainer(
|
||||
env_, immutable_db_options_.info_log, std::move(fname),
|
||||
std::move(file_reader), log_number);
|
||||
log_readers_.insert(std::make_pair(
|
||||
log_number, std::unique_ptr<LogReaderContainer>(log_reader_container)));
|
||||
}
|
||||
iter = log_readers_.find(log_number);
|
||||
assert(iter != log_readers_.end());
|
||||
*log_reader = iter->second->reader_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// After manifest recovery, replay WALs and refresh log_readers_ if necessary
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status DBImplSecondary::RecoverLogFiles(
|
||||
const std::vector<uint64_t>& log_numbers, SequenceNumber* next_sequence,
|
||||
bool /*read_only*/) {
|
||||
mutex_.AssertHeld();
|
||||
Status status;
|
||||
for (auto log_number : log_numbers) {
|
||||
log::FragmentBufferedReader* reader = nullptr;
|
||||
status = MaybeInitLogReader(log_number, &reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
assert(reader != nullptr);
|
||||
}
|
||||
for (auto log_number : log_numbers) {
|
||||
auto it = log_readers_.find(log_number);
|
||||
assert(it != log_readers_.end());
|
||||
log::FragmentBufferedReader* reader = it->second->reader_;
|
||||
// Manually update the file number allocation counter in VersionSet.
|
||||
versions_->MarkFileNumberUsed(log_number);
|
||||
|
||||
// Determine if we should tolerate incomplete records at the tail end of the
|
||||
// Read all the records and add to a memtable
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
WriteBatch batch;
|
||||
|
||||
while (reader->ReadRecord(&record, &scratch,
|
||||
immutable_db_options_.wal_recovery_mode) &&
|
||||
status.ok()) {
|
||||
if (record.size() < WriteBatchInternal::kHeader) {
|
||||
reader->GetReporter()->Corruption(
|
||||
record.size(), Status::Corruption("log record too small"));
|
||||
continue;
|
||||
}
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
// do not check sequence number because user may toggle disableWAL
|
||||
// between writes which breaks sequence number continuity guarantee
|
||||
|
||||
// If column family was not found, it might mean that the WAL write
|
||||
// batch references to the column family that was dropped after the
|
||||
// insert. We don't want to fail the whole write batch in that case --
|
||||
// we just ignore the update.
|
||||
// That's why we set ignore missing column families to true
|
||||
// passing null flush_scheduler will disable memtable flushing which is
|
||||
// needed for secondary instances
|
||||
bool has_valid_writes = false;
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), nullptr /* flush_scheduler */,
|
||||
true, log_number, this, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
// blocks that do not form coherent data
|
||||
reader->GetReporter()->Corruption(record.size(), status);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto last_sequence = *next_sequence - 1;
|
||||
if ((*next_sequence != kMaxSequenceNumber) &&
|
||||
(versions_->LastSequence() <= last_sequence)) {
|
||||
versions_->SetLastAllocatedSequence(last_sequence);
|
||||
versions_->SetLastPublishedSequence(last_sequence);
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
}
|
||||
// remove logreaders from map after successfully recovering the WAL
|
||||
if (log_readers_.size() > 1) {
|
||||
auto eraseIter = log_readers_.begin();
|
||||
std::advance(eraseIter, log_readers_.size() - 1);
|
||||
log_readers_.erase(log_readers_.begin(), eraseIter);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Implementation of the DB interface
|
||||
Status DBImplSecondary::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return GetImpl(read_options, column_family, key, value);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* pinnable_val) {
|
||||
assert(pinnable_val != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_GET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
auto cfh = static_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_) {
|
||||
tracer_->Get(column_family, key);
|
||||
}
|
||||
}
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* super_version = GetAndRefSuperVersion(cfd);
|
||||
SequenceNumber snapshot = versions_->LastSequence();
|
||||
MergeContext merge_context;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
Status s;
|
||||
LookupKey lkey(key, snapshot);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
bool done = false;
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
if (!done && !s.ok() && !s.IsMergeInProgress()) {
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
return s;
|
||||
}
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val, &s,
|
||||
&merge_context, &max_covering_tombstone_seq);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
{
|
||||
PERF_TIMER_GUARD(get_post_process_time);
|
||||
ReturnAndCleanupSuperVersion(cfd, super_version);
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
size_t size = pinnable_val->size();
|
||||
RecordTick(stats_, BYTES_READ, size);
|
||||
RecordTimeToHistogram(stats_, BYTES_PER_READ, size);
|
||||
PERF_COUNTER_ADD(get_read_bytes, size);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Iterator* DBImplSecondary::NewIterator(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (read_options.managed) {
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("Managed iterator is not supported anymore."));
|
||||
}
|
||||
if (read_options.read_tier == kPersistedTier) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"ReadTier::kPersistedData is not yet supported in iterators."));
|
||||
}
|
||||
Iterator* result = nullptr;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (read_options.tailing) {
|
||||
return NewErrorIterator(Status::NotSupported(
|
||||
"tailing iterator not supported in secondary mode"));
|
||||
} else if (read_options.snapshot != nullptr) {
|
||||
// TODO (yanqin) support snapshot.
|
||||
return NewErrorIterator(
|
||||
Status::NotSupported("snapshot not supported in secondary mode"));
|
||||
} else {
|
||||
auto snapshot = versions_->LastSequence();
|
||||
result = NewIteratorImpl(read_options, cfd, snapshot, read_callback);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
const ReadOptions& read_options, ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot, ReadCallback* read_callback) {
|
||||
assert(nullptr != cfd);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, *cfd->ioptions(), super_version->mutable_cf_options,
|
||||
snapshot,
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number, read_callback);
|
||||
auto internal_iter =
|
||||
NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator(), snapshot);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::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.");
|
||||
}
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
}
|
||||
iterators->clear();
|
||||
iterators->reserve(column_families.size());
|
||||
if (read_options.tailing) {
|
||||
return Status::NotSupported(
|
||||
"tailing iterator not supported in secondary mode");
|
||||
} else if (read_options.snapshot != nullptr) {
|
||||
// TODO (yanqin) support snapshot.
|
||||
return Status::NotSupported("snapshot not supported in secondary mode");
|
||||
} else {
|
||||
SequenceNumber read_seq = versions_->LastSequence();
|
||||
for (auto cfh : column_families) {
|
||||
ColumnFamilyData* cfd = static_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
iterators->push_back(
|
||||
NewIteratorImpl(read_options, cfd, read_seq, read_callback));
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
assert(versions_.get() != nullptr);
|
||||
assert(manifest_reader_.get() != nullptr);
|
||||
Status s;
|
||||
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
s = static_cast<ReactiveVersionSet*>(versions_.get())
|
||||
->ReadAndApply(&mutex_, &manifest_reader_, &cfds_changed);
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(true /* create_superversion */);
|
||||
for (auto cfd : cfds_changed) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &mutex_);
|
||||
}
|
||||
sv_context.Clean();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& options, const std::string& dbname,
|
||||
const std::string& secondary_path, DB** dbptr) {
|
||||
*dbptr = nullptr;
|
||||
|
||||
DBOptions db_options(options);
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options);
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
|
||||
Status s = DB::OpenAsSecondary(db_options, dbname, secondary_path,
|
||||
column_families, &handles, dbptr);
|
||||
if (s.ok()) {
|
||||
assert(handles.size() == 1);
|
||||
delete handles[0];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(
|
||||
const DBOptions& db_options, const std::string& dbname,
|
||||
const std::string& secondary_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
*dbptr = nullptr;
|
||||
if (db_options.max_open_files != -1) {
|
||||
// TODO (yanqin) maybe support max_open_files != -1 by creating hard links
|
||||
// on SST files so that db secondary can still have access to old SSTs
|
||||
// while primary instance may delete original.
|
||||
return Status::InvalidArgument("require max_open_files to be -1");
|
||||
}
|
||||
|
||||
DBOptions tmp_opts(db_options);
|
||||
if (nullptr == tmp_opts.info_log) {
|
||||
Env* env = tmp_opts.env;
|
||||
assert(env != nullptr);
|
||||
std::string secondary_abs_path;
|
||||
env->GetAbsolutePath(secondary_path, &secondary_abs_path);
|
||||
std::string fname = InfoLogFileName(secondary_path, secondary_abs_path,
|
||||
tmp_opts.db_log_dir);
|
||||
|
||||
env->CreateDirIfMissing(secondary_path);
|
||||
if (tmp_opts.log_file_time_to_roll > 0 || tmp_opts.max_log_file_size > 0) {
|
||||
AutoRollLogger* result = new AutoRollLogger(
|
||||
env, secondary_path, tmp_opts.db_log_dir, tmp_opts.max_log_file_size,
|
||||
tmp_opts.log_file_time_to_roll, tmp_opts.info_log_level);
|
||||
Status s = result->GetStatus();
|
||||
if (!s.ok()) {
|
||||
delete result;
|
||||
} else {
|
||||
tmp_opts.info_log.reset(result);
|
||||
}
|
||||
}
|
||||
if (nullptr == tmp_opts.info_log) {
|
||||
env->RenameFile(
|
||||
fname, OldInfoLogFileName(secondary_path, env->NowMicros(),
|
||||
secondary_abs_path, tmp_opts.db_log_dir));
|
||||
Status s = env->NewLogger(fname, &(tmp_opts.info_log));
|
||||
if (tmp_opts.info_log != nullptr) {
|
||||
tmp_opts.info_log->SetInfoLogLevel(tmp_opts.info_log_level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(tmp_opts.info_log != nullptr);
|
||||
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->env_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->mutex_.Lock();
|
||||
Status s = impl->Recover(column_families, true, false, false);
|
||||
if (s.ok()) {
|
||||
for (auto cf : column_families) {
|
||||
auto cfd =
|
||||
impl->versions_->GetColumnFamilySet()->GetColumnFamily(cf.name);
|
||||
if (nullptr == cfd) {
|
||||
s = Status::InvalidArgument("Column family not found: ", cf.name);
|
||||
break;
|
||||
}
|
||||
handles->push_back(new ColumnFamilyHandleImpl(cfd, impl, &impl->mutex_));
|
||||
}
|
||||
}
|
||||
SuperVersionContext sv_context(true /* create_superversion */);
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
sv_context.Clean();
|
||||
if (s.ok()) {
|
||||
*dbptr = impl;
|
||||
for (auto h : *handles) {
|
||||
impl->NewThreadStatusCfInfo(
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(h)->cfd());
|
||||
}
|
||||
} else {
|
||||
for (auto h : *handles) {
|
||||
delete h;
|
||||
}
|
||||
handles->clear();
|
||||
delete impl;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#else // !ROCKSDB_LITE
|
||||
|
||||
Status DB::OpenAsSecondary(const Options& /*options*/,
|
||||
const std::string& /*name*/,
|
||||
const std::string& /*secondary_path*/,
|
||||
DB** /*dbptr*/) {
|
||||
return Status::NotSupported("Not supported in ROCKSDB_LITE.");
|
||||
}
|
||||
|
||||
Status DB::OpenAsSecondary(
|
||||
const DBOptions& /*db_options*/, const std::string& /*dbname*/,
|
||||
const std::string& /*secondary_path*/,
|
||||
const std::vector<ColumnFamilyDescriptor>& /*column_families*/,
|
||||
std::vector<ColumnFamilyHandle*>* /*handles*/, DB** /*dbptr*/) {
|
||||
return Status::NotSupported("Not supported in ROCKSDB_LITE.");
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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 <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class LogReaderContainer {
|
||||
public:
|
||||
LogReaderContainer()
|
||||
: reader_(nullptr), reporter_(nullptr), status_(nullptr) {}
|
||||
LogReaderContainer(Env* env, std::shared_ptr<Logger> info_log,
|
||||
std::string fname,
|
||||
std::unique_ptr<SequentialFileReader>&& file_reader,
|
||||
uint64_t log_number) {
|
||||
LogReporter* reporter = new LogReporter();
|
||||
status_ = new Status();
|
||||
reporter->env = env;
|
||||
reporter->info_log = info_log.get();
|
||||
reporter->fname = std::move(fname);
|
||||
reporter->status = status_;
|
||||
reporter_ = reporter;
|
||||
// We intentially make log::Reader do checksumming even if
|
||||
// paranoid_checks==false so that corruptions cause entire commits
|
||||
// to be skipped instead of propagating bad information (like overly
|
||||
// large sequence numbers).
|
||||
reader_ = new log::FragmentBufferedReader(info_log, std::move(file_reader),
|
||||
reporter, true /*checksum*/,
|
||||
log_number);
|
||||
}
|
||||
log::FragmentBufferedReader* reader_;
|
||||
log::Reader::Reporter* reporter_;
|
||||
Status* status_;
|
||||
~LogReaderContainer() {
|
||||
delete reader_;
|
||||
delete reporter_;
|
||||
delete status_;
|
||||
}
|
||||
private:
|
||||
struct LogReporter : public log::Reader::Reporter {
|
||||
Env* env;
|
||||
Logger* info_log;
|
||||
std::string fname;
|
||||
Status* status; // nullptr if immutable_db_options_.paranoid_checks==false
|
||||
void Corruption(size_t bytes, const Status& s) override {
|
||||
ROCKS_LOG_WARN(info_log, "%s%s: dropping %d bytes; %s",
|
||||
(this->status == nullptr ? "(ignoring error) " : ""),
|
||||
fname.c_str(), static_cast<int>(bytes),
|
||||
s.ToString().c_str());
|
||||
if (this->status != nullptr && this->status->ok()) {
|
||||
*this->status = s;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class DBImplSecondary : public DBImpl {
|
||||
public:
|
||||
DBImplSecondary(const DBOptions& options, const std::string& dbname);
|
||||
~DBImplSecondary() override;
|
||||
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, bool error_if_log_file_exist,
|
||||
bool error_if_data_exists_in_logs) override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DB::Get;
|
||||
Status Get(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value) override;
|
||||
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value);
|
||||
|
||||
using DBImpl::NewIterator;
|
||||
Iterator* NewIterator(const ReadOptions&,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& read_options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback);
|
||||
|
||||
Status NewIterators(const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
Status Put(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Merge;
|
||||
Status Merge(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Delete;
|
||||
Status Delete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::SingleDelete;
|
||||
Status SingleDelete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* /*updates*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::CompactRange;
|
||||
Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*begin*/, const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::CompactFiles;
|
||||
Status CompactFiles(
|
||||
const CompactionOptions& /*compact_options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr,
|
||||
CompactionJobInfo* /*compaction_job_info*/ = nullptr) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status DisableFileDeletions() override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Flush;
|
||||
Status Flush(const FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::SyncWAL;
|
||||
Status SyncWAL() override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::IngestExternalFile;
|
||||
Status IngestExternalFile(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
// Try to catch up with the primary by reading as much as possible from the
|
||||
// log files until there is nothing more to read or encounters an error. If
|
||||
// the amount of information in the log files to process is huge, this
|
||||
// method can take long time due to all the I/O and CPU costs.
|
||||
Status TryCatchUpWithPrimary() override;
|
||||
|
||||
Status MaybeInitLogReader(uint64_t log_number,
|
||||
log::FragmentBufferedReader** log_reader);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
|
||||
// No copying allowed
|
||||
DBImplSecondary(const DBImplSecondary&);
|
||||
void operator=(const DBImplSecondary&);
|
||||
|
||||
using DBImpl::Recover;
|
||||
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence,
|
||||
bool read_only) override;
|
||||
|
||||
std::unique_ptr<log::FragmentBufferedReader> manifest_reader_;
|
||||
std::unique_ptr<log::Reader::Reporter> manifest_reporter_;
|
||||
std::unique_ptr<Status> manifest_reader_status_;
|
||||
|
||||
// cache log readers for each log number, used for continue WAL replay
|
||||
// after recovery
|
||||
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+95
-116
@@ -146,17 +146,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
|
||||
// we're responsible for exit batch group
|
||||
for (auto* writer : *(w.write_group)) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(myabandeh): propagate status to write_group
|
||||
auto last_sequence = w.write_group->last_sequence;
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
@@ -279,7 +268,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
concurrent_update);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
|
||||
}
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
if (write_options.disableWAL) {
|
||||
has_unpersisted_data_.store(true, std::memory_order_relaxed);
|
||||
@@ -309,6 +298,35 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
|
||||
// PreReleaseCallback is called after WAL write and before memtable write
|
||||
if (status.ok()) {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
// logic in WriteBatchInternal::InsertInto(write_group...) as well as
|
||||
// with WriteBatchInternal::InsertInto(write_batch...) that is called on
|
||||
// the merged batch during recovery from the WAL.
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (writer->pre_release_callback) {
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, disable_memtable, writer->log_used);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
PERF_TIMER_GUARD(write_memtable_time);
|
||||
|
||||
@@ -320,23 +338,6 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
} else {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
// logic in WriteBatchInternal::InsertInto(write_group...) as well as
|
||||
// with WriteBatchInternal::InsertInto(write_batch...) that is called on
|
||||
// the merged batch during recovery from the WAL.
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
write_group.last_sequence = last_sequence;
|
||||
write_thread_.LaunchParallelMemTableWriters(&write_group);
|
||||
in_parallel_group = true;
|
||||
@@ -388,17 +389,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
if (should_exit_batch_group) {
|
||||
if (status.ok()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: if we are to resume after non-OK statuses we need to revisit how
|
||||
// we reacts to non-OK statuses here.
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
@@ -471,7 +463,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
|
||||
stats->AddDBStats(InternalStats::BYTES_WRITTEN, total_byte_size);
|
||||
RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
@@ -602,7 +594,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
concurrent_update);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, write_done_by_other);
|
||||
}
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
RecordInHistogram(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
@@ -657,8 +649,8 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
DISABLE_MEMTABLE);
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, DISABLE_MEMTABLE, writer->log_used);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
@@ -876,7 +868,7 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (status.ok() && need_log_sync) {
|
||||
@@ -952,7 +944,7 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
|
||||
@@ -1001,8 +993,9 @@ Status DBImpl::WriteRecoverableState() {
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
for (uint64_t sub_batch_seq = seq + 1;
|
||||
sub_batch_seq < next_seq && status.ok(); sub_batch_seq++) {
|
||||
uint64_t const no_log_num = 0;
|
||||
status = recoverable_state_pre_release_callback_->Callback(
|
||||
sub_batch_seq, !DISABLE_MEMTABLE);
|
||||
sub_batch_seq, !DISABLE_MEMTABLE, no_log_num);
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -1072,16 +1065,17 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!flush_wont_release_oldest_log) {
|
||||
// we only mark this log as getting flushed if we have successfully
|
||||
// flushed all data in this log. If this log contains outstanding prepared
|
||||
// transactions then we cannot flush this log until those transactions are commited.
|
||||
// transactions then we cannot flush this log until those transactions are
|
||||
// commited.
|
||||
unable_to_release_oldest_log_ = false;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64
|
||||
" while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -1133,7 +1127,7 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing column family with largest mem table size. Write buffer is "
|
||||
"using %" PRIu64 " bytes out of a total of %" PRIu64 ".",
|
||||
"using %" ROCKSDB_PRIszt " bytes out of a total of %" ROCKSDB_PRIszt ".",
|
||||
write_buffer_manager_->memory_usage(),
|
||||
write_buffer_manager_->buffer_size());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
@@ -1424,58 +1418,19 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// Log this later after lock release. It may be outdated, e.g., if background
|
||||
// flush happens before logging, but that should be ok.
|
||||
int num_imm_unflushed = cfd->imm()->NumNotFlushed();
|
||||
DBOptions db_options =
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
const auto preallocate_block_size =
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
auto write_hint = CalculateWALWriteHint();
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
mutex_.Unlock();
|
||||
{
|
||||
std::string log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, new_log_number);
|
||||
if (creating_new_log) {
|
||||
EnvOptions opt_env_opt =
|
||||
env_->OptimizeForLogWrite(env_options_, db_options);
|
||||
if (recycle_log_number) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"reusing log %" PRIu64 " from recycle list\n",
|
||||
recycle_log_number);
|
||||
std::string old_log_fname =
|
||||
LogFileName(immutable_db_options_.wal_dir, recycle_log_number);
|
||||
s = env_->ReuseWritableFile(log_fname, old_log_fname, &lfile,
|
||||
opt_env_opt);
|
||||
} else {
|
||||
s = NewWritableFile(env_, log_fname, &lfile, opt_env_opt);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Our final size should be less than write_buffer_size
|
||||
// (compression, etc) but err on the side of caution.
|
||||
|
||||
// use preallocate_block_size instead
|
||||
// of calling GetWalPreallocateBlockSize()
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(lfile), log_fname, opt_env_opt, env_, nullptr /* stats */,
|
||||
immutable_db_options_.listeners));
|
||||
new_log = new log::Writer(
|
||||
std::move(file_writer), new_log_number,
|
||||
immutable_db_options_.recycle_log_file_num > 0, manual_wal_flush_);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// PLEASE NOTE: We assume that there are no failable operations
|
||||
// after lock is acquired below since we are already notifying
|
||||
// client about mem table becoming immutable.
|
||||
NotifyOnMemTableSealed(cfd, memtable_info);
|
||||
#endif //ROCKSDB_LITE
|
||||
if (creating_new_log) {
|
||||
// TODO: Write buffer size passed in should be max of all CF's instead
|
||||
// of mutable_cf_options.write_buffer_size.
|
||||
s = CreateWAL(new_log_number, recycle_log_number, preallocate_block_size,
|
||||
&new_log);
|
||||
}
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] New memtable created with log file: #%" PRIu64
|
||||
@@ -1484,10 +1439,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
mutex_.Lock();
|
||||
if (s.ok() && creating_new_log) {
|
||||
log_write_mutex_.Lock();
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
@@ -1495,21 +1447,41 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64
|
||||
" WAL file -- %s\n",
|
||||
" WAL file\n",
|
||||
cfd->GetName().c_str(), cur_log_writer->get_log_number(),
|
||||
new_log_number);
|
||||
}
|
||||
}
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
if (s.ok()) {
|
||||
logfile_number_ = new_log_number;
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_new_log);
|
||||
assert(!new_mem);
|
||||
assert(!new_log);
|
||||
if (new_mem) {
|
||||
delete new_mem;
|
||||
}
|
||||
if (new_log) {
|
||||
delete new_log;
|
||||
}
|
||||
SuperVersion* new_superversion =
|
||||
context->superversion_context.new_superversion.release();
|
||||
if (new_superversion != nullptr) {
|
||||
delete new_superversion;
|
||||
}
|
||||
// We may have lost data from the WritableFileBuffer in-memory buffer for
|
||||
// the current log, so treat it as a fatal error and set bg_error
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
// Read back bg_error in order to get the right severity
|
||||
s = error_handler_.GetBGError();
|
||||
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
@@ -1536,6 +1508,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
cfd->SetMemtable(new_mem);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
mutable_cf_options);
|
||||
#ifndef ROCKSDB_LITE
|
||||
mutex_.Unlock();
|
||||
// Notify client that memtable is sealed, now that we have successfully
|
||||
// installed a new memtable
|
||||
NotifyOnMemTableSealed(cfd, memtable_info);
|
||||
mutex_.Lock();
|
||||
#endif // ROCKSDB_LITE
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
@@ -1544,13 +1523,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
|
||||
size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const {
|
||||
mutex_.AssertHeld();
|
||||
size_t bsize = static_cast<size_t>(
|
||||
write_buffer_size / 10 + write_buffer_size);
|
||||
size_t bsize =
|
||||
static_cast<size_t>(write_buffer_size / 10 + write_buffer_size);
|
||||
// Some users might set very high write_buffer_size and rely on
|
||||
// max_total_wal_size or other parameters to control the WAL size.
|
||||
if (mutable_db_options_.max_total_wal_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, static_cast<size_t>(
|
||||
mutable_db_options_.max_total_wal_size));
|
||||
bsize = std::min<size_t>(
|
||||
bsize, static_cast<size_t>(mutable_db_options_.max_total_wal_size));
|
||||
}
|
||||
if (immutable_db_options_.db_write_buffer_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, immutable_db_options_.db_write_buffer_size);
|
||||
|
||||
+175
-167
@@ -22,12 +22,14 @@
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/iterator_wrapper.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/trace_replay.h"
|
||||
#include "util/user_comparator_wrapper.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -117,29 +119,29 @@ class DBIter final: public Iterator {
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(_env),
|
||||
: env_(_env),
|
||||
logger_(cf_options.info_log),
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(cf_options.merge_operator),
|
||||
iter_(iter),
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(cf_options.statistics),
|
||||
num_internal_keys_skipped_(0),
|
||||
iterate_lower_bound_(read_options.iterate_lower_bound),
|
||||
iterate_upper_bound_(read_options.iterate_upper_bound),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
prefix_same_as_start_(read_options.prefix_same_as_start),
|
||||
pin_thru_lifetime_(read_options.pin_data),
|
||||
total_order_seek_(read_options.total_order_seek),
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
read_callback_(read_callback),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
allow_blob_(allow_blob),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
start_seqnum_(read_options.iter_start_seqnum) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
prefix_extractor_ = mutable_cf_options.prefix_extractor.get();
|
||||
@@ -148,8 +150,8 @@ class DBIter final: public Iterator {
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
if (iter_) {
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
if (iter_.iter()) {
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
~DBIter() override {
|
||||
@@ -160,16 +162,12 @@ class DBIter final: public Iterator {
|
||||
RecordTick(statistics_, NO_ITERATOR_DELETED);
|
||||
ResetInternalKeysSkippedCounter();
|
||||
local_stats_.BumpGlobalStatistics(statistics_);
|
||||
if (!arena_mode_) {
|
||||
delete iter_;
|
||||
} else {
|
||||
iter_->~InternalIterator();
|
||||
}
|
||||
iter_.DeleteIter(arena_mode_);
|
||||
}
|
||||
virtual void SetIter(InternalIterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
assert(iter_.iter() == nullptr);
|
||||
iter_.Set(iter);
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual ReadRangeDelAggregator* GetRangeDelAggregator() {
|
||||
return &range_del_agg_;
|
||||
@@ -193,12 +191,12 @@ class DBIter final: public Iterator {
|
||||
} else if (direction_ == kReverse) {
|
||||
return pinned_value_;
|
||||
} else {
|
||||
return iter_->value();
|
||||
return iter_.value();
|
||||
}
|
||||
}
|
||||
Status status() const override {
|
||||
if (status_.ok()) {
|
||||
return iter_->status();
|
||||
return iter_.status();
|
||||
} else {
|
||||
assert(!valid_);
|
||||
return status_;
|
||||
@@ -215,7 +213,7 @@ class DBIter final: public Iterator {
|
||||
}
|
||||
if (prop_name == "rocksdb.iterator.super-version-number") {
|
||||
// First try to pass the value returned from inner iterator.
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
return iter_.iter()->GetProperty(prop_name, prop);
|
||||
} else if (prop_name == "rocksdb.iterator.is-key-pinned") {
|
||||
if (valid_) {
|
||||
*prop = (pin_thru_lifetime_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
@@ -230,14 +228,19 @@ class DBIter final: public Iterator {
|
||||
return Status::InvalidArgument("Unidentified property.");
|
||||
}
|
||||
|
||||
void Next() override;
|
||||
void Prev() override;
|
||||
void Seek(const Slice& target) override;
|
||||
void SeekForPrev(const Slice& target) override;
|
||||
void SeekToFirst() override;
|
||||
void SeekToLast() override;
|
||||
inline void Next() final override;
|
||||
inline void Prev() final override;
|
||||
inline void Seek(const Slice& target) final override;
|
||||
inline void SeekForPrev(const Slice& target) final override;
|
||||
inline void SeekToFirst() final override;
|
||||
inline void SeekToLast() final override;
|
||||
Env* env() { return env_; }
|
||||
void set_sequence(uint64_t s) { sequence_ = s; }
|
||||
void set_sequence(uint64_t s) {
|
||||
sequence_ = s;
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(s);
|
||||
}
|
||||
}
|
||||
void set_valid(bool v) { valid_ = v; }
|
||||
|
||||
private:
|
||||
@@ -251,13 +254,13 @@ class DBIter final: public Iterator {
|
||||
bool FindValueForCurrentKeyUsingSeek();
|
||||
bool FindUserKeyBeforeSavedKey();
|
||||
inline bool FindNextUserEntry(bool skipping, bool prefix_check);
|
||||
bool FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
inline bool FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
bool MergeValuesNewToOld();
|
||||
|
||||
void PrevInternal();
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
bool IsVisible(SequenceNumber sequence);
|
||||
inline bool IsVisible(SequenceNumber sequence);
|
||||
|
||||
// CanReseekToSkip() returns whether the iterator can use the optimization
|
||||
// where it reseek by sequence number to get the next key when there are too
|
||||
@@ -265,12 +268,6 @@ class DBIter final: public Iterator {
|
||||
// sequence number does not guarantee that it is visible.
|
||||
inline bool CanReseekToSkip();
|
||||
|
||||
// MaxVisibleSequenceNumber() returns the maximum visible sequence number
|
||||
// for this snapshot. This sequence number may be greater than snapshot
|
||||
// seqno because uncommitted data written to DB for write unprepared will
|
||||
// have a higher sequence number.
|
||||
inline SequenceNumber MaxVisibleSequenceNumber();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
@@ -304,15 +301,16 @@ class DBIter final: public Iterator {
|
||||
}
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
bool arena_mode_;
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
UserComparatorWrapper user_comparator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
InternalIterator* iter_;
|
||||
IteratorWrapper iter_;
|
||||
ReadCallback* read_callback_;
|
||||
// Max visible sequence number. It is normally the snapshot seq unless we have
|
||||
// uncommitted data in db as in WriteUnCommitted.
|
||||
SequenceNumber sequence_;
|
||||
|
||||
Status status_;
|
||||
IterKey saved_key_;
|
||||
// Reusable internal key data structure. This is only used inside one function
|
||||
// and should not be used across functions. Reusing this object can reduce
|
||||
@@ -320,9 +318,6 @@ class DBIter final: public Iterator {
|
||||
ParsedInternalKey ikey_;
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
// for prefix seek mode to support prev()
|
||||
Statistics* statistics_;
|
||||
uint64_t max_skip_;
|
||||
@@ -330,23 +325,29 @@ class DBIter final: public Iterator {
|
||||
uint64_t num_internal_keys_skipped_;
|
||||
const Slice* iterate_lower_bound_;
|
||||
const Slice* iterate_upper_bound_;
|
||||
|
||||
IterKey prefix_start_buf_;
|
||||
|
||||
Status status_;
|
||||
Slice prefix_start_key_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
const bool prefix_same_as_start_;
|
||||
// Means that we will pin all data blocks we read as long the Iterator
|
||||
// is not deleted, will be true if ReadOptions::pin_data is true
|
||||
const bool pin_thru_lifetime_;
|
||||
const bool total_order_seek_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
ReadRangeDelAggregator range_del_agg_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ReadCallback* read_callback_;
|
||||
DBImpl* db_impl_;
|
||||
ColumnFamilyData* cfd_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
// for diff snapshots we want the lower bound on the seqnum;
|
||||
// if this value > 0 iterator will return internal keys
|
||||
SequenceNumber start_seqnum_;
|
||||
@@ -357,11 +358,11 @@ class DBIter final: public Iterator {
|
||||
};
|
||||
|
||||
inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
if (!ParseInternalKey(iter_->key(), ikey)) {
|
||||
if (!ParseInternalKey(iter_.key(), ikey)) {
|
||||
status_ = Status::Corruption("corrupted internal key in DBIter");
|
||||
valid_ = false;
|
||||
ROCKS_LOG_ERROR(logger_, "corrupted internal key in DBIter: %s",
|
||||
iter_->key().ToString(true).c_str());
|
||||
iter_.key().ToString(true).c_str());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@@ -372,28 +373,30 @@ void DBIter::Next() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, env_);
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
num_internal_keys_skipped_ = 0;
|
||||
bool ok = true;
|
||||
if (direction_ == kReverse) {
|
||||
if (!ReverseToForward()) {
|
||||
ok = false;
|
||||
}
|
||||
} else if (iter_->Valid() && !current_entry_is_merged_) {
|
||||
} else if (!current_entry_is_merged_) {
|
||||
// If the current value is not a merge, the iter position is the
|
||||
// current key, which is already returned. We can safely issue a
|
||||
// Next() without checking the current key.
|
||||
// If the current key is a merge, very likely iter already points
|
||||
// to the next internal position.
|
||||
iter_->Next();
|
||||
assert(iter_.Valid());
|
||||
iter_.Next();
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
if (statistics_ != nullptr) {
|
||||
local_stats_.next_count_++;
|
||||
}
|
||||
if (ok && iter_->Valid()) {
|
||||
local_stats_.next_count_++;
|
||||
if (ok && iter_.Valid()) {
|
||||
FindNextUserEntry(true /* skipping the current user key */,
|
||||
prefix_same_as_start_);
|
||||
} else {
|
||||
@@ -425,9 +428,9 @@ inline bool DBIter::FindNextUserEntry(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
// Actual implementation of DBIter::FindNextUserEntry()
|
||||
bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
inline bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// Loop until we hit an acceptable entry to yield
|
||||
assert(iter_->Valid());
|
||||
assert(iter_.Valid());
|
||||
assert(status_.ok());
|
||||
assert(direction_ == kForward);
|
||||
current_entry_is_merged_ = false;
|
||||
@@ -452,7 +455,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -467,8 +470,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
}
|
||||
|
||||
if (IsVisible(ikey_.sequence)) {
|
||||
if (skipping && user_comparator_->Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) <= 0) {
|
||||
if (skipping && user_comparator_.Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
@@ -489,8 +492,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
return true;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
ikey_.user_key, !pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
}
|
||||
@@ -510,14 +513,16 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
} else {
|
||||
// this key and all previous versions shouldn't be included,
|
||||
// skipping
|
||||
saved_key_.SetUserKey(ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
}
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
ikey_.user_key, !pin_thru_lifetime_ ||
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
@@ -547,7 +552,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
case kTypeMerge:
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
!pin_thru_lifetime_ || !iter_.iter()->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
@@ -575,13 +580,13 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// If this happens too many times in a row for the same user key, we want
|
||||
// to seek to the target sequence number.
|
||||
int cmp =
|
||||
user_comparator_->Compare(ikey_.user_key, saved_key_.GetUserKey());
|
||||
user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey());
|
||||
if (cmp == 0 || (skipping && cmp <= 0)) {
|
||||
num_skipped++;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = false;
|
||||
num_skipped = 0;
|
||||
}
|
||||
@@ -610,20 +615,20 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
}
|
||||
iter_->Seek(last_key);
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
} else {
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
} while (iter_->Valid());
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
return iter_->status().ok();
|
||||
return iter_.status().ok();
|
||||
}
|
||||
|
||||
// Merge values of the same user key starting from the current iter_ position
|
||||
// Scan from the newer entries to older entries.
|
||||
// PRE: iter_->key() points to the first merge type entry
|
||||
// PRE: iter_.key() points to the first merge type entry
|
||||
// saved_key_ stores the user key
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
@@ -639,19 +644,19 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
TempPinData();
|
||||
merge_context_.Clear();
|
||||
// Start the merge process by pushing the first operand
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
Status s;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
for (iter_.Next(); iter_.Valid(); iter_.Next()) {
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
// hit the next user key, stop right here
|
||||
break;
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
|
||||
@@ -659,12 +664,12 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
break;
|
||||
} else if (kTypeValue == ikey.type) {
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
const Slice val = iter_->value();
|
||||
const Slice val = iter_.value();
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_, &pinned_value_, true);
|
||||
@@ -674,8 +679,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
return false;
|
||||
}
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
if (!iter_->status().ok()) {
|
||||
iter_.Next();
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -683,8 +688,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
} else if (kTypeMerge == ikey.type) {
|
||||
// hit a merge, add the value as an operand and run associative merge.
|
||||
// when complete, add result to operands and continue.
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
if (!allow_blob_) {
|
||||
@@ -703,7 +708,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -729,6 +734,8 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, env_);
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
bool ok = true;
|
||||
@@ -750,33 +757,32 @@ void DBIter::Prev() {
|
||||
}
|
||||
|
||||
bool DBIter::ReverseToForward() {
|
||||
assert(iter_->status().ok());
|
||||
assert(iter_.status().ok());
|
||||
|
||||
// When moving backwards, iter_ is positioned on _previous_ key, which may
|
||||
// not exist or may have different prefix than the current key().
|
||||
// If that's the case, seek iter_ to current key.
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) {
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_.Valid()) {
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
}
|
||||
|
||||
direction_ = kForward;
|
||||
// Skip keys less than the current key() (a.k.a. saved_key_).
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) >=
|
||||
0) {
|
||||
if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) >= 0) {
|
||||
return true;
|
||||
}
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -786,14 +792,14 @@ bool DBIter::ReverseToForward() {
|
||||
|
||||
// Move iter_ to the key before saved_key_.
|
||||
bool DBIter::ReverseToBackward() {
|
||||
assert(iter_->status().ok());
|
||||
assert(iter_.status().ok());
|
||||
|
||||
// When current_entry_is_merged_ is true, iter_ may be positioned on the next
|
||||
// key, which may not exist or may have prefix different from current.
|
||||
// If that's the case, seek to saved_key_.
|
||||
if (current_entry_is_merged_ &&
|
||||
((prefix_extractor_ != nullptr && !total_order_seek_) ||
|
||||
!iter_->Valid())) {
|
||||
!iter_.Valid())) {
|
||||
IterKey last_key;
|
||||
// Using kMaxSequenceNumber and kValueTypeForSeek
|
||||
// (not kValueTypeForSeekForPrev) to seek to a key strictly smaller
|
||||
@@ -801,15 +807,15 @@ bool DBIter::ReverseToBackward() {
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
iter_->SeekForPrev(last_key.GetInternalKey());
|
||||
iter_.SeekForPrev(last_key.GetInternalKey());
|
||||
} else {
|
||||
// Some iterators may not support SeekForPrev(), so we avoid using it
|
||||
// when prefix seek mode is disabled. This is somewhat expensive
|
||||
// (an extra Prev(), as well as an extra change of direction of iter_),
|
||||
// so we may need to reconsider it later.
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
if (!iter_->Valid() && iter_->status().ok()) {
|
||||
iter_->SeekToLast();
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
if (!iter_.Valid() && iter_.status().ok()) {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -819,10 +825,10 @@ bool DBIter::ReverseToBackward() {
|
||||
}
|
||||
|
||||
void DBIter::PrevInternal() {
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
saved_key_.SetUserKey(
|
||||
ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
ExtractUserKey(iter_.key()),
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
|
||||
if (prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetUserKey())
|
||||
@@ -833,8 +839,8 @@ void DBIter::PrevInternal() {
|
||||
}
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
// We've iterated earlier than the user-specified lower bound.
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -876,7 +882,7 @@ void DBIter::PrevInternal() {
|
||||
// POST: iter_ is positioned on one of the entries equal to saved_key_, or on
|
||||
// the entry just before them, or on the entry just after them.
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
assert(iter_.Valid());
|
||||
merge_context_.Clear();
|
||||
current_entry_is_merged_ = false;
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
@@ -888,14 +894,14 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsVisible(ikey.sequence) ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
if (TooManyInternalKeysSkipped()) {
|
||||
@@ -918,8 +924,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
pinned_value_ = iter_.value();
|
||||
}
|
||||
merge_context_.Clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
@@ -940,7 +946,8 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
} else {
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(
|
||||
iter_->value(), iter_->IsValuePinned() /* operand_pinned */);
|
||||
iter_.value(),
|
||||
iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
}
|
||||
break;
|
||||
@@ -949,11 +956,11 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
iter_->Prev();
|
||||
iter_.Prev();
|
||||
++num_skipped;
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1033,22 +1040,22 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
|
||||
sequence_, kValueTypeForSeek));
|
||||
iter_->Seek(last_key);
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
|
||||
// In case read_callback presents, the value we seek to may not be visible.
|
||||
// Find the next value that's visible.
|
||||
ParsedInternalKey ikey;
|
||||
while (true) {
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_.Valid()) {
|
||||
valid_ = false;
|
||||
return iter_->status().ok();
|
||||
return iter_.status().ok();
|
||||
}
|
||||
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
// No visible values for this key, even though FindValueForCurrentKey()
|
||||
// has seen some. This is possible if we're using a tailing iterator, and
|
||||
// the entries were discarded in a compaction.
|
||||
@@ -1060,7 +1067,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
break;
|
||||
}
|
||||
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
@@ -1078,8 +1085,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
return false;
|
||||
}
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
assert(iter_.iter()->IsValuePinned());
|
||||
pinned_value_ = iter_.value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -1089,13 +1096,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
assert(ikey.type == kTypeMerge);
|
||||
current_entry_is_merged_ = true;
|
||||
merge_context_.Clear();
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
while (true) {
|
||||
iter_->Next();
|
||||
iter_.Next();
|
||||
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.Valid()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1104,7 +1111,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
if (!user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1113,7 +1120,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
break;
|
||||
} else if (ikey.type == kTypeValue) {
|
||||
const Slice val = iter_->value();
|
||||
const Slice val = iter_.value();
|
||||
Status s = MergeHelper::TimedFullMerge(
|
||||
merge_operator_, saved_key_.GetUserKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
@@ -1126,8 +1133,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
valid_ = true;
|
||||
return true;
|
||||
} else if (ikey.type == kTypeMerge) {
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
merge_context_.PushOperand(
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
if (!allow_blob_) {
|
||||
@@ -1159,13 +1166,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// Make sure we leave iter_ in a good state. If it's valid and we don't care
|
||||
// about prefixes, that's already good enough. Otherwise it needs to be
|
||||
// seeked to the current key.
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_->Valid()) {
|
||||
if ((prefix_extractor_ != nullptr && !total_order_seek_) || !iter_.Valid()) {
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
iter_->SeekForPrev(last_key);
|
||||
iter_.SeekForPrev(last_key);
|
||||
} else {
|
||||
iter_->Seek(last_key);
|
||||
if (!iter_->Valid() && iter_->status().ok()) {
|
||||
iter_->SeekToLast();
|
||||
iter_.Seek(last_key);
|
||||
if (!iter_.Valid() && iter_.status().ok()) {
|
||||
iter_.SeekToLast();
|
||||
}
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
@@ -1180,13 +1187,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
assert(status_.ok());
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid()) {
|
||||
while (iter_.Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
|
||||
if (user_comparator_.Compare(ikey.user_key, saved_key_.GetUserKey()) < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1208,19 +1215,19 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
saved_key_.GetUserKey(), kMaxSequenceNumber, kValueTypeForSeek));
|
||||
// It would be more efficient to use SeekForPrev() here, but some
|
||||
// iterators may not support it.
|
||||
iter_->Seek(last_key.GetInternalKey());
|
||||
iter_.Seek(last_key.GetInternalKey());
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
if (!iter_->Valid()) {
|
||||
if (!iter_.Valid()) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
++num_skipped;
|
||||
}
|
||||
|
||||
iter_->Prev();
|
||||
iter_.Prev();
|
||||
}
|
||||
|
||||
if (!iter_->status().ok()) {
|
||||
if (!iter_.status().ok()) {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1241,30 +1248,25 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) {
|
||||
}
|
||||
|
||||
bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
return sequence <= MaxVisibleSequenceNumber() &&
|
||||
(read_callback_ == nullptr || read_callback_->IsVisible(sequence));
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence <= sequence_;
|
||||
} else {
|
||||
return read_callback_->IsVisible(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
bool DBIter::CanReseekToSkip() {
|
||||
return read_callback_ == nullptr ||
|
||||
read_callback_->MaxUnpreparedSequenceNumber() == 0;
|
||||
}
|
||||
|
||||
SequenceNumber DBIter::MaxVisibleSequenceNumber() {
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence_;
|
||||
}
|
||||
|
||||
return std::max(sequence_, read_callback_->MaxUnpreparedSequenceNumber());
|
||||
return read_callback_ == nullptr || read_callback_->CanReseekToSkip();
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
SequenceNumber seq = MaxVisibleSequenceNumber();
|
||||
SequenceNumber seq = sequence_;
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, seq);
|
||||
|
||||
@@ -1275,19 +1277,19 @@ void DBIter::Seek(const Slice& target) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(), *iterate_lower_bound_) <
|
||||
0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, seq);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetInternalKey());
|
||||
iter_.Seek(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
@@ -1316,6 +1318,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
void DBIter::SeekForPrev(const Slice& target) {
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
@@ -1326,15 +1329,15 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
kValueTypeForSeekForPrev);
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekForPrev(saved_key_.GetInternalKey());
|
||||
iter_.SeekForPrev(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
@@ -1345,7 +1348,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
@@ -1376,6 +1379,7 @@ void DBIter::SeekToFirst() {
|
||||
Seek(*iterate_lower_bound_);
|
||||
return;
|
||||
}
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
@@ -1389,15 +1393,15 @@ void DBIter::SeekToFirst() {
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
iter_.SeekToFirst();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (iter_.Valid()) {
|
||||
saved_key_.SetUserKey(
|
||||
ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
ExtractUserKey(iter_.key()),
|
||||
!iter_.iter()->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
FindNextUserEntry(false /* not skipping */, false /* no prefix check */);
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
@@ -1420,13 +1424,14 @@ void DBIter::SeekToLast() {
|
||||
if (iterate_upper_bound_ != nullptr) {
|
||||
// Seek to last key strictly less than ReadOptions.iterate_upper_bound.
|
||||
SeekForPrev(*iterate_upper_bound_);
|
||||
if (Valid() && user_comparator_->Equal(*iterate_upper_bound_, key())) {
|
||||
if (Valid() && user_comparator_.Equal(*iterate_upper_bound_, key())) {
|
||||
ReleaseTempPinnedData();
|
||||
PrevInternal();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, env_);
|
||||
// Don't use iter_::Seek() if we set a prefix extractor
|
||||
// because prefix seek will be used.
|
||||
if (prefix_extractor_ != nullptr && !total_order_seek_) {
|
||||
@@ -1440,7 +1445,7 @@ void DBIter::SeekToLast() {
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
iter_.SeekToLast();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
PrevInternal();
|
||||
@@ -1547,6 +1552,9 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
new (&arena_) Arena();
|
||||
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex());
|
||||
if (read_callback_) {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_,
|
||||
|
||||
+57
-77
@@ -15,12 +15,16 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/flush_block_policy.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A dumb ReadCallback which saying every key is committed.
|
||||
class DummyReadCallback : public ReadCallback {
|
||||
bool IsVisible(SequenceNumber /*seq*/) override { return true; }
|
||||
public:
|
||||
DummyReadCallback() : ReadCallback(kMaxSequenceNumber) {}
|
||||
bool IsVisibleFullCheck(SequenceNumber /*seq*/) override { return true; }
|
||||
void SetSnapshot(SequenceNumber seq) { max_visible_seq_ = seq; }
|
||||
};
|
||||
|
||||
// Test param:
|
||||
@@ -40,41 +44,20 @@ class DBIteratorTest : public DBTestBase,
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: db_->GetLatestSequenceNumber();
|
||||
bool use_read_callback = GetParam();
|
||||
ReadCallback* read_callback = use_read_callback ? &read_callback_ : nullptr;
|
||||
DummyReadCallback* read_callback = nullptr;
|
||||
if (use_read_callback) {
|
||||
read_callback = new DummyReadCallback();
|
||||
read_callback->SetSnapshot(seq);
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
read_callbacks_.push_back(
|
||||
std::unique_ptr<DummyReadCallback>(read_callback));
|
||||
}
|
||||
return dbfull()->NewIteratorImpl(read_options, cfd, seq, read_callback);
|
||||
}
|
||||
|
||||
private:
|
||||
DummyReadCallback read_callback_;
|
||||
};
|
||||
|
||||
class FlushBlockEveryKeyPolicy : public FlushBlockPolicy {
|
||||
public:
|
||||
bool Update(const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
if (!start_) {
|
||||
start_ = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool start_ = false;
|
||||
};
|
||||
|
||||
class FlushBlockEveryKeyPolicyFactory : public FlushBlockPolicyFactory {
|
||||
public:
|
||||
explicit FlushBlockEveryKeyPolicyFactory() {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "FlushBlockEveryKeyPolicyFactory";
|
||||
}
|
||||
|
||||
FlushBlockPolicy* NewFlushBlockPolicy(
|
||||
const BlockBasedTableOptions& /*table_options*/,
|
||||
const BlockBuilder& /*data_block_builder*/) const override {
|
||||
return new FlushBlockEveryKeyPolicy;
|
||||
}
|
||||
InstrumentedMutex mutex_;
|
||||
std::vector<std::unique_ptr<DummyReadCallback>> read_callbacks_;
|
||||
};
|
||||
|
||||
TEST_P(DBIteratorTest, IteratorProperty) {
|
||||
@@ -1023,48 +1006,48 @@ TEST_P(DBIteratorTest, DBIteratorBoundMultiSeek) {
|
||||
#endif
|
||||
|
||||
TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) {
|
||||
int upper_bound_hits = 0;
|
||||
Options options = CurrentOptions();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::BlockEntryIteratorState::KeyReachedUpperBound",
|
||||
[&upper_bound_hits](void* arg) {
|
||||
assert(arg != nullptr);
|
||||
upper_bound_hits += (*static_cast<bool*>(arg) ? 1 : 0);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
for (auto format_version : {2, 3, 4}) {
|
||||
int upper_bound_hits = 0;
|
||||
Options options = CurrentOptions();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableIterator:out_of_bound",
|
||||
[&upper_bound_hits](void*) { upper_bound_hits++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor = nullptr;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.format_version = format_version;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Flush());
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Slice ub("foo3");
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
Slice ub("foo3");
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ro));
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ro));
|
||||
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo2")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo2")), 0);
|
||||
ASSERT_EQ(upper_bound_hits, 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(upper_bound_hits, 1);
|
||||
}
|
||||
}
|
||||
// TODO(3.13): fix the issue of Seek() + Prev() which might not necessary
|
||||
// return the biggest key which is smaller than the seek key.
|
||||
@@ -1960,8 +1943,8 @@ TEST_P(DBIteratorTest, ReadAhead) {
|
||||
delete iter;
|
||||
int64_t num_file_closes_readahead =
|
||||
TestGetTickerCount(options, NO_FILE_CLOSES);
|
||||
ASSERT_EQ(num_file_opens + 3, num_file_opens_readahead);
|
||||
ASSERT_EQ(num_file_closes + 3, num_file_closes_readahead);
|
||||
ASSERT_EQ(num_file_opens, num_file_opens_readahead);
|
||||
ASSERT_EQ(num_file_closes, num_file_closes_readahead);
|
||||
ASSERT_GT(bytes_read_readahead, bytes_read);
|
||||
ASSERT_GT(bytes_read_readahead, read_options.readahead_size * 3);
|
||||
|
||||
@@ -2476,15 +2459,12 @@ class DBIteratorWithReadCallbackTest : public DBIteratorTest {};
|
||||
TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber last_visible_seq)
|
||||
: last_visible_seq_(last_visible_seq) {}
|
||||
explicit TestReadCallback(SequenceNumber _max_visible_seq)
|
||||
: ReadCallback(_max_visible_seq) {}
|
||||
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
return seq <= last_visible_seq_;
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return seq <= max_visible_seq_;
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber last_visible_seq_;
|
||||
};
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
@@ -18,9 +18,11 @@ class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
TestReadCallback(SnapshotChecker* snapshot_checker,
|
||||
SequenceNumber snapshot_seq)
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
: ReadCallback(snapshot_seq),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return snapshot_checker_->CheckInSnapshot(seq, snapshot_seq_) ==
|
||||
SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
@@ -527,6 +527,17 @@ TEST_F(DBOptionsTest, RunStatsDumpPeriodSec) {
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::DumpStats:1", [&](void* /*arg*/) {
|
||||
counter++;
|
||||
@@ -556,6 +567,17 @@ TEST_F(DBOptionsTest, StatsPersistScheduling) {
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
|
||||
@@ -581,6 +603,17 @@ TEST_F(DBOptionsTest, PersistentStatsFreshInstall) {
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
int counter = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PersistStats:Entry", [&](void* /*arg*/) { counter++; });
|
||||
@@ -616,6 +649,19 @@ TEST_F(DBOptionsTest, GetStatsHistory) {
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
|
||||
CreateColumnFamilies({"pikachu"}, options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
@@ -658,6 +704,19 @@ TEST_F(DBOptionsTest, InMemoryStatsHistoryPurging) {
|
||||
mock_env.reset(new rocksdb::MockTimeEnv(env_));
|
||||
mock_env->set_current_time(0); // in seconds
|
||||
options.env = mock_env.get();
|
||||
#if defined(OS_MACOSX) && !defined(NDEBUG)
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"InstrumentedCondVar::TimedWaitInternal", [&](void* arg) {
|
||||
uint64_t time_us = *reinterpret_cast<uint64_t*>(arg);
|
||||
if (time_us < mock_env->RealNowMicros()) {
|
||||
*reinterpret_cast<uint64_t*>(arg) = mock_env->RealNowMicros() + 1000;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif // OS_MACOSX && !NDEBUG
|
||||
|
||||
CreateColumnFamilies({"pikachu"}, options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
|
||||
+28
-3
@@ -438,9 +438,10 @@ TEST_F(DBRangeDelTest, ValidUniversalSubcompactionBoundaries) {
|
||||
ASSERT_OK(dbfull()->RunManualCompaction(
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd(),
|
||||
1 /* input_level */, 2 /* output_level */, 0 /* output_path_id */,
|
||||
0 /* max_subcompactions */, nullptr /* begin */, nullptr /* end */,
|
||||
true /* exclusive */, true /* disallow_trivial_move */));
|
||||
1 /* input_level */, 2 /* output_level */, CompactRangeOptions(),
|
||||
nullptr /* begin */, nullptr /* end */, true /* exclusive */,
|
||||
true /* disallow_trivial_move */,
|
||||
port::kMaxUint64 /* max_file_num_to_ignore */));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -490,6 +491,30 @@ TEST_F(DBRangeDelTest, CompactionRemovesCoveredMergeOperands) {
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, PutDeleteRangeMergeFlush) {
|
||||
// Test the sequence of operations: (1) Put, (2) DeleteRange, (3) Merge, (4)
|
||||
// Flush. The `CompactionIterator` previously had a bug where we forgot to
|
||||
// check for covering range tombstones when processing the (1) Put, causing
|
||||
// it to reappear after the flush.
|
||||
Options opts = CurrentOptions();
|
||||
opts.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
Reopen(opts);
|
||||
|
||||
std::string val;
|
||||
PutFixed64(&val, 1);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key", val));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
"key", "key_"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", val));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string expected, actual;
|
||||
ASSERT_OK(db_->Get(read_opts, "key", &actual));
|
||||
PutFixed64(&expected, 1);
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NumTableFilesAtLevel() is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) {
|
||||
|
||||
@@ -0,0 +1,524 @@
|
||||
// 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).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_impl_secondary.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBSecondaryTest : public DBTestBase {
|
||||
public:
|
||||
DBSecondaryTest()
|
||||
: DBTestBase("/db_secondary_test"),
|
||||
secondary_path_(),
|
||||
handles_secondary_(),
|
||||
db_secondary_(nullptr) {
|
||||
secondary_path_ =
|
||||
test::PerThreadDBPath(env_, "/db_secondary_test_secondary");
|
||||
}
|
||||
|
||||
~DBSecondaryTest() override {
|
||||
CloseSecondary();
|
||||
if (getenv("KEEP_DB") != nullptr) {
|
||||
fprintf(stdout, "Secondary DB is still at %s\n", secondary_path_.c_str());
|
||||
} else {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
EXPECT_OK(DestroyDB(secondary_path_, options));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Status ReopenAsSecondary(const Options& options) {
|
||||
return DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_);
|
||||
}
|
||||
|
||||
void OpenSecondary(const Options& options);
|
||||
|
||||
void OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options);
|
||||
|
||||
void CloseSecondary() {
|
||||
for (auto h : handles_secondary_) {
|
||||
db_secondary_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
handles_secondary_.clear();
|
||||
delete db_secondary_;
|
||||
db_secondary_ = nullptr;
|
||||
}
|
||||
|
||||
DBImplSecondary* db_secondary_full() {
|
||||
return static_cast<DBImplSecondary*>(db_secondary_);
|
||||
}
|
||||
|
||||
void CheckFileTypeCounts(const std::string& dir, int expected_log,
|
||||
int expected_sst, int expected_manifest) const;
|
||||
|
||||
std::string secondary_path_;
|
||||
std::vector<ColumnFamilyHandle*> handles_secondary_;
|
||||
DB* db_secondary_;
|
||||
};
|
||||
|
||||
void DBSecondaryTest::OpenSecondary(const Options& options) {
|
||||
Status s =
|
||||
DB::OpenAsSecondary(options, dbname_, secondary_path_, &db_secondary_);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void DBSecondaryTest::OpenSecondaryWithColumnFamilies(
|
||||
const std::vector<std::string>& column_families, const Options& options) {
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options);
|
||||
for (const auto& cf_name : column_families) {
|
||||
cf_descs.emplace_back(cf_name, options);
|
||||
}
|
||||
Status s = DB::OpenAsSecondary(options, dbname_, secondary_path_, cf_descs,
|
||||
&handles_secondary_, &db_secondary_);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
void DBSecondaryTest::CheckFileTypeCounts(const std::string& dir,
|
||||
int expected_log, int expected_sst,
|
||||
int expected_manifest) const {
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(dir, &filenames);
|
||||
|
||||
int log_cnt = 0, sst_cnt = 0, manifest_cnt = 0;
|
||||
for (auto file : filenames) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &number, &type)) {
|
||||
log_cnt += (type == kLogFile);
|
||||
sst_cnt += (type == kTableFile);
|
||||
manifest_cnt += (type == kDescriptorFile);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(expected_log, log_cnt);
|
||||
ASSERT_EQ(expected_sst, sst_cnt);
|
||||
ASSERT_EQ(expected_manifest, manifest_cnt);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, ReopenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "foo_value"));
|
||||
ASSERT_OK(Put("bar", "bar_value"));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
Close();
|
||||
|
||||
ASSERT_OK(ReopenAsSecondary(options));
|
||||
ASSERT_EQ("foo_value", Get("foo"));
|
||||
ASSERT_EQ("bar_value", Get("bar"));
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
auto db1 = static_cast<DBImplSecondary*>(db_);
|
||||
ASSERT_NE(nullptr, db1);
|
||||
Iterator* iter = db1->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
if (0 == count) {
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value", iter->value().ToString());
|
||||
} else if (1 == count) {
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value", iter->value().ToString());
|
||||
}
|
||||
++count;
|
||||
}
|
||||
delete iter;
|
||||
ASSERT_EQ(2, count);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondary) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
const auto verify_db_func = [&](const std::string& foo_val,
|
||||
const std::string& bar_val) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ(foo_val, value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ(bar_val, value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ(foo_val, iter->value().ToString());
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ(bar_val, iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
};
|
||||
|
||||
verify_db_func("foo_value2", "bar_value2");
|
||||
|
||||
ASSERT_OK(Put("foo", "new_foo_value"));
|
||||
ASSERT_OK(Put("bar", "new_bar_value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
verify_db_func("new_foo_value", "new_bar_value");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenAsSecondaryWALTailing) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
}
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
const auto verify_db_func = [&](const std::string& foo_val,
|
||||
const std::string& bar_val) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ(foo_val, value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ(bar_val, value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ(foo_val, iter->value().ToString());
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ(bar_val, iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
};
|
||||
|
||||
verify_db_func("foo_value2", "bar_value2");
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithNonExistColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, options1);
|
||||
cf_descs.emplace_back("pikachu", options1);
|
||||
cf_descs.emplace_back("eevee", options1);
|
||||
Status s = DB::OpenAsSecondary(options1, dbname_, secondary_path_, cf_descs,
|
||||
&handles_secondary_, &db_secondary_);
|
||||
ASSERT_NOK(s);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, OpenWithSubsetOfColumnFamilies) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ASSERT_EQ(0, handles_secondary_.size());
|
||||
ASSERT_NE(nullptr, db_secondary_);
|
||||
|
||||
ASSERT_OK(Put(0 /*cf*/, "foo", "foo_value"));
|
||||
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_value"));
|
||||
ASSERT_OK(Flush(0 /*cf*/));
|
||||
ASSERT_OK(Flush(1 /*cf*/));
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value", value);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchToNewManifestDuringOpen) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:0",
|
||||
"VersionSet::ProcessManifestWrites:BeforeNewManifest"},
|
||||
{"VersionSet::ProcessManifestWrites:AfterNewManifest",
|
||||
"ReactiveVersionSet::MaybeSwitchManifest:AfterGetCurrentManifestPath:"
|
||||
"1"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Make sure db calls RecoverLogFiles so as to trigger a manifest write,
|
||||
// which causes the db to switch to a new MANIFEST upon start.
|
||||
port::Thread ro_db_thread([&]() {
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
CloseSecondary();
|
||||
});
|
||||
Reopen(options);
|
||||
ro_db_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, MissingTableFileDuringOpen) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, MissingTableFile) {
|
||||
int table_files_not_exist = 0;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReactiveVersionSet::ReadAndApply:AfterLoadTableHandlers",
|
||||
[&](void* arg) {
|
||||
Status s = *reinterpret_cast<Status*>(arg);
|
||||
if (s.IsPathNotFound()) {
|
||||
++table_files_not_exist;
|
||||
} else if (!s.ok()) {
|
||||
assert(false); // Should not reach here
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
for (int i = 0; i != options.level0_file_num_compaction_trigger; ++i) {
|
||||
ASSERT_OK(Put("foo", "foo_value" + std::to_string(i)));
|
||||
ASSERT_OK(Put("bar", "bar_value" + std::to_string(i)));
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_NE(nullptr, db_secondary_full());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_NOK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_NOK(db_secondary_->Get(ropts, "bar", &value));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ASSERT_EQ(options.level0_file_num_compaction_trigger, table_files_not_exist);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "foo", &value));
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
ASSERT_OK(db_secondary_->Get(ropts, "bar", &value));
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
value);
|
||||
Iterator* iter = db_secondary_->NewIterator(ropts);
|
||||
ASSERT_NE(nullptr, iter);
|
||||
iter->Seek("bar");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
ASSERT_EQ("bar_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
ASSERT_EQ("foo_value" +
|
||||
std::to_string(options.level0_file_num_compaction_trigger - 1),
|
||||
iter->value().ToString());
|
||||
size_t count = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
ASSERT_EQ(2, count);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, PrimaryDropColumnFamily) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
const std::string kCfName1 = "pikachu";
|
||||
CreateAndReopenWithCF({kCfName1}, options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondaryWithColumnFamilies({kCfName1}, options1);
|
||||
ASSERT_EQ(2, handles_secondary_.size());
|
||||
|
||||
ASSERT_OK(Put(1 /*cf*/, "foo", "foo_val_1"));
|
||||
ASSERT_OK(Flush(1 /*cf*/));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
ReadOptions ropts;
|
||||
ropts.verify_checksums = true;
|
||||
std::string value;
|
||||
ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value));
|
||||
ASSERT_EQ("foo_val_1", value);
|
||||
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
Close();
|
||||
CheckFileTypeCounts(dbname_, 1, 0, 1);
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
value.clear();
|
||||
ASSERT_OK(db_secondary_->Get(ropts, handles_secondary_[1], "foo", &value));
|
||||
ASSERT_EQ("foo_val_1", value);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, SwitchManifest) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
Reopen(options);
|
||||
|
||||
Options options1;
|
||||
options1.env = env_;
|
||||
options1.max_open_files = -1;
|
||||
OpenSecondary(options1);
|
||||
|
||||
const int kNumFiles = options.level0_file_num_compaction_trigger - 1;
|
||||
// Keep it smaller than 10 so that key0, key1, ..., key9 are sorted as 0, 1,
|
||||
// ..., 9.
|
||||
const int kNumKeys = 10;
|
||||
// Create two sst
|
||||
for (int i = 0; i != kNumFiles; ++i) {
|
||||
for (int j = 0; j != kNumKeys; ++j) {
|
||||
ASSERT_OK(Put("key" + std::to_string(j), "value_" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
const auto& range_scan_db = [&]() {
|
||||
ReadOptions tmp_ropts;
|
||||
tmp_ropts.total_order_seek = true;
|
||||
tmp_ropts.verify_checksums = true;
|
||||
std::unique_ptr<Iterator> iter(db_secondary_->NewIterator(tmp_ropts));
|
||||
int cnt = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), ++cnt) {
|
||||
ASSERT_EQ("key" + std::to_string(cnt), iter->key().ToString());
|
||||
ASSERT_EQ("value_" + std::to_string(kNumFiles - 1),
|
||||
iter->value().ToString());
|
||||
}
|
||||
};
|
||||
|
||||
range_scan_db();
|
||||
|
||||
// While secondary instance still keeps old MANIFEST open, we close primary,
|
||||
// restart primary, performs full compaction, close again, restart again so
|
||||
// that next time secondary tries to catch up with primary, the secondary
|
||||
// will skip the MANIFEST in middle.
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
|
||||
ASSERT_OK(db_secondary_->TryCatchUpWithPrimary());
|
||||
range_scan_db();
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+88
-11
@@ -356,6 +356,14 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
env_->time_elapse_only_sleep_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// Need to disable stats dumping and persisting which also use
|
||||
// RepeatableThread, one of whose member variables is of type
|
||||
// InstrumentedCondVar. The callback for
|
||||
// InstrumentedCondVar::TimedWaitInternal can be triggered by stats dumping
|
||||
// and persisting threads and cause time_spent_deleting measurement to become
|
||||
// incorrect.
|
||||
options.stats_dump_period_sec = 0;
|
||||
options.stats_persist_period_sec = 0;
|
||||
options.env = env_;
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
@@ -367,14 +375,16 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
@@ -408,6 +418,57 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, RateLimitedWALDelete) {
|
||||
Destroy(last_options_);
|
||||
|
||||
std::vector<uint64_t> penalties;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
[&](void* arg) { penalties.push_back(*(static_cast<uint64_t*>(arg))); });
|
||||
|
||||
env_->no_slowdown_ = true;
|
||||
env_->time_elapse_only_sleep_ = true;
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
Status s;
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 0, false, &s, 0));
|
||||
ASSERT_OK(s);
|
||||
options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(2.1);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
ASSERT_EQ("4", FilesPerLevel(0));
|
||||
|
||||
// Compaction will move the 4 files in L0 to trash and create 1 L1 file
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact(true));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(penalties.size(), 8);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -446,7 +507,6 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile",
|
||||
[&](void* /*arg*/) { bg_delete_file++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -464,10 +524,14 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
DestroyAndReopen(options);
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.disableWAL = true;
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A')));
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A'), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
@@ -483,7 +547,7 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 4; i < 8; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B')));
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B'), wo));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("4,1", FilesPerLevel(0));
|
||||
@@ -501,7 +565,7 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
// Compaction will delete both files and regenerate a file in L1 in second
|
||||
// db path. The deleted files should still be cleaned up via delete scheduler.
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
@@ -538,14 +602,27 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
// Close DB and destroy it using DeleteScheduler
|
||||
Close();
|
||||
|
||||
int num_sst_files = 0;
|
||||
int num_wal_files = 0;
|
||||
std::vector<std::string> db_files;
|
||||
env_->GetChildren(dbname_, &db_files);
|
||||
for (std::string f : db_files) {
|
||||
if (f.substr(f.find_last_of(".") + 1) == "sst") {
|
||||
num_sst_files++;
|
||||
} else if (f.substr(f.find_last_of(".") + 1) == "log") {
|
||||
num_wal_files++;
|
||||
}
|
||||
}
|
||||
ASSERT_GT(num_sst_files, 0);
|
||||
ASSERT_GT(num_wal_files, 0);
|
||||
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
// We have deleted the 4 sst files in the delete_scheduler
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
ASSERT_EQ(bg_delete_file, num_sst_files + num_wal_files);
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
|
||||
@@ -46,7 +46,7 @@ TEST_F(DBStatisticsTest, CompressionStatsTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = type;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = StatsLevel::kExceptTimeForMutex;
|
||||
options.statistics->set_stats_level(StatsLevel::kExceptTimeForMutex);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int kNumKeysWritten = 100000;
|
||||
@@ -105,7 +105,7 @@ TEST_F(DBStatisticsTest, MutexWaitStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = StatsLevel::kAll;
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
const uint64_t kMutexWaitDelay = 100;
|
||||
ThreadStatusUtil::TEST_SetStateDelay(ThreadStatus::STATE_MUTEX_WAIT,
|
||||
|
||||
+36
-7
@@ -2153,6 +2153,7 @@ struct MTState {
|
||||
struct MTThread {
|
||||
MTState* state;
|
||||
int id;
|
||||
bool multiget_batched;
|
||||
};
|
||||
|
||||
static void MTThreadBody(void* arg) {
|
||||
@@ -2201,8 +2202,28 @@ static void MTThreadBody(void* arg) {
|
||||
// same)
|
||||
std::vector<Slice> keys(kColumnFamilies, Slice(keybuf));
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> statuses =
|
||||
db->MultiGet(ReadOptions(), t->state->test->handles_, keys, &values);
|
||||
std::vector<Status> statuses;
|
||||
if (!t->multiget_batched) {
|
||||
statuses = db->MultiGet(ReadOptions(), t->state->test->handles_, keys,
|
||||
&values);
|
||||
} else {
|
||||
std::vector<PinnableSlice> pin_values(keys.size());
|
||||
statuses.resize(keys.size());
|
||||
const Snapshot* snapshot = db->GetSnapshot();
|
||||
ReadOptions ro;
|
||||
ro.snapshot = snapshot;
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
db->MultiGet(ro, t->state->test->handles_[cf], 1, &keys[cf],
|
||||
&pin_values[cf], &statuses[cf]);
|
||||
}
|
||||
db->ReleaseSnapshot(snapshot);
|
||||
values.resize(keys.size());
|
||||
for (int cf = 0; cf < kColumnFamilies; ++cf) {
|
||||
if (statuses[cf].ok()) {
|
||||
values[cf].assign(pin_values[cf].data(), pin_values[cf].size());
|
||||
}
|
||||
}
|
||||
}
|
||||
Status s = statuses[0];
|
||||
// all statuses have to be the same
|
||||
for (size_t i = 1; i < statuses.size(); ++i) {
|
||||
@@ -2244,10 +2265,13 @@ static void MTThreadBody(void* arg) {
|
||||
|
||||
} // namespace
|
||||
|
||||
class MultiThreadedDBTest : public DBTest,
|
||||
public ::testing::WithParamInterface<int> {
|
||||
class MultiThreadedDBTest
|
||||
: public DBTest,
|
||||
public ::testing::WithParamInterface<std::tuple<int, bool>> {
|
||||
public:
|
||||
void SetUp() override { option_config_ = GetParam(); }
|
||||
void SetUp() override {
|
||||
std::tie(option_config_, multiget_batched_) = GetParam();
|
||||
}
|
||||
|
||||
static std::vector<int> GenerateOptionConfigs() {
|
||||
std::vector<int> optionConfigs;
|
||||
@@ -2256,6 +2280,8 @@ class MultiThreadedDBTest : public DBTest,
|
||||
}
|
||||
return optionConfigs;
|
||||
}
|
||||
|
||||
bool multiget_batched_;
|
||||
};
|
||||
|
||||
TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
@@ -2282,6 +2308,7 @@ TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
for (int id = 0; id < kNumThreads; id++) {
|
||||
thread[id].state = &mt;
|
||||
thread[id].id = id;
|
||||
thread[id].multiget_batched = multiget_batched_;
|
||||
env_->StartThread(MTThreadBody, &thread[id]);
|
||||
}
|
||||
|
||||
@@ -2299,7 +2326,9 @@ TEST_P(MultiThreadedDBTest, MultiThreaded) {
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
MultiThreaded, MultiThreadedDBTest,
|
||||
::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()));
|
||||
::testing::Combine(
|
||||
::testing::ValuesIn(MultiThreadedDBTest::GenerateOptionConfigs()),
|
||||
::testing::Bool()));
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Group commit test:
|
||||
@@ -5102,7 +5131,7 @@ TEST_P(DBTestWithParam, FilterCompactionTimeTest) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = kExceptTimeForMutex;
|
||||
options.statistics->set_stats_level(kExceptTimeForMutex);
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
+354
-16
@@ -200,7 +200,7 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
// The total soft write buffer size is about 105000
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(4 * 1024 * 1024, 2);
|
||||
ASSERT_LT(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
|
||||
if (use_old_interface_) {
|
||||
options.db_write_buffer_size = 120000; // this is the real limit
|
||||
@@ -236,14 +236,14 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(30000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
|
||||
}
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(60000), wo));
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
ASSERT_LE(cache->GetUsage(), 2 * 256 * 1024);
|
||||
}
|
||||
wait_flush();
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1), wo));
|
||||
@@ -339,11 +339,11 @@ TEST_P(DBTestSharedWriteBufferAcrossCFs, SharedWriteBufferAcrossCFs) {
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
if (cost_cache_) {
|
||||
ASSERT_GE(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_GE(cache->GetUsage(), 256 * 1024);
|
||||
Close();
|
||||
options.write_buffer_manager.reset();
|
||||
last_options_.write_buffer_manager.reset();
|
||||
ASSERT_LT(cache->GetUsage(), 1024 * 1024);
|
||||
ASSERT_LT(cache->GetUsage(), 256 * 1024);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -467,8 +467,8 @@ TEST_F(DBTest2, TestWriteBufferNoLimitWithCache) {
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
// One dummy entry is 1MB.
|
||||
ASSERT_GT(cache->GetUsage(), 500000);
|
||||
// One dummy entry is 256KB.
|
||||
ASSERT_GT(cache->GetUsage(), 128000);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1194,7 +1194,7 @@ TEST_F(DBTest2, PresetCompressionDictLocality) {
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions compact_range_opts;
|
||||
compact_range_opts.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(compact_range_opts, nullptr, nullptr));
|
||||
|
||||
// Dictionary compression should not be so good as to compress four totally
|
||||
@@ -1712,7 +1712,7 @@ TEST_F(DBTest2, MaxCompactionBytesTest) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
}
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,8", FilesPerLevel(0));
|
||||
|
||||
@@ -1805,7 +1805,7 @@ class MockPersistentCache : public PersistentCache {
|
||||
#ifdef OS_LINUX
|
||||
// Make sure that in CPU time perf context counters, Env::NowCPUNanos()
|
||||
// is used, rather than Env::CPUNanos();
|
||||
TEST_F(DBTest2, TestPerfContextCpuTime) {
|
||||
TEST_F(DBTest2, TestPerfContextGetCpuTime) {
|
||||
// force resizing table cache so table handle is not preloaded so that
|
||||
// we can measure find_table_nanos during Get().
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
@@ -1836,6 +1836,91 @@ TEST_F(DBTest2, TestPerfContextCpuTime) {
|
||||
SetPerfLevel(PerfLevel::kDisable);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TestPerfContextIterCpuTime) {
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
// force resizing table cache so table handle is not preloaded so that
|
||||
// we can measure find_table_nanos during iteration
|
||||
dbfull()->TEST_table_cache()->SetCapacity(0);
|
||||
|
||||
const size_t kNumEntries = 10;
|
||||
for (size_t i = 0; i < kNumEntries; ++i) {
|
||||
ASSERT_OK(Put("k" + ToString(i), "v" + ToString(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (size_t i = 0; i < kNumEntries; ++i) {
|
||||
ASSERT_EQ("v" + ToString(i), Get("k" + ToString(i)));
|
||||
}
|
||||
std::string last_key = "k" + ToString(kNumEntries - 1);
|
||||
std::string last_value = "v" + ToString(kNumEntries - 1);
|
||||
env_->now_cpu_count_.store(0);
|
||||
|
||||
// CPU timing is not enabled with kEnableTimeExceptForMutex
|
||||
SetPerfLevel(PerfLevel::kEnableTimeExceptForMutex);
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k0");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
iter->SeekForPrev(last_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(last_value, iter->value().ToString());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_seek_cpu_nanos);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v1", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_next_cpu_nanos);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_EQ(0, get_perf_context()->iter_prev_cpu_nanos);
|
||||
ASSERT_EQ(0, env_->now_cpu_count_.load());
|
||||
delete iter;
|
||||
|
||||
uint64_t kDummyAddonTime = uint64_t{1000000000000};
|
||||
|
||||
// Add time to NowNanos() reading.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::FindTable:0",
|
||||
[&](void* /*arg*/) { env_->addon_time_.fetch_add(kDummyAddonTime); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex);
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k0");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
iter->SeekForPrev(last_key);
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(last_value, iter->value().ToString());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_seek_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_seek_cpu_nanos, kDummyAddonTime);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v1", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_next_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_next_cpu_nanos, kDummyAddonTime);
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("v0", iter->value().ToString());
|
||||
ASSERT_GT(get_perf_context()->iter_prev_cpu_nanos, 0);
|
||||
ASSERT_LT(get_perf_context()->iter_prev_cpu_nanos, kDummyAddonTime);
|
||||
ASSERT_GE(env_->now_cpu_count_.load(), 12);
|
||||
ASSERT_GT(get_perf_context()->find_table_nanos, kDummyAddonTime);
|
||||
|
||||
SetPerfLevel(PerfLevel::kDisable);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
delete iter;
|
||||
}
|
||||
#endif // OS_LINUX
|
||||
|
||||
#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
|
||||
@@ -2218,7 +2303,7 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
// Run a manual compaction that will compact the 2 files in L2
|
||||
// into 1 file in L2
|
||||
cro.exclusive_manual_compaction = false;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -2292,7 +2377,7 @@ TEST_F(DBTest2, ManualCompactionOverlapManualCompaction) {
|
||||
// into 1 file in L1
|
||||
CompactRangeOptions cro;
|
||||
cro.exclusive_manual_compaction = false;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
bg_thread.join();
|
||||
|
||||
@@ -2311,6 +2396,41 @@ TEST_F(DBTest2, OptimizeForPointLookup) {
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, OptimizeForSmallDB) {
|
||||
Options options = CurrentOptions();
|
||||
Close();
|
||||
options.OptimizeForSmallDb();
|
||||
|
||||
// Find the cache object
|
||||
ASSERT_EQ(std::string(BlockBasedTableFactory::kName),
|
||||
std::string(options.table_factory->Name()));
|
||||
BlockBasedTableOptions* table_options =
|
||||
reinterpret_cast<BlockBasedTableOptions*>(
|
||||
options.table_factory->GetOptions());
|
||||
ASSERT_TRUE(table_options != nullptr);
|
||||
std::shared_ptr<Cache> cache = table_options->block_cache;
|
||||
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
|
||||
// memtable size is costed to the block cache
|
||||
ASSERT_NE(0, cache->GetUsage());
|
||||
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Flush();
|
||||
|
||||
size_t prev_size = cache->GetUsage();
|
||||
// Remember block cache size, so that we can find that
|
||||
// it is filled after Get().
|
||||
// Use pinnable slice so that it can ping the block so that
|
||||
// when we check the size it is not evicted.
|
||||
PinnableSlice value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), "foo", &value));
|
||||
ASSERT_GT(cache->GetUsage(), prev_size);
|
||||
value.Reset();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, GetRaceFlush1) {
|
||||
@@ -2644,8 +2764,11 @@ TEST_F(DBTest2, ReadCallbackTest) {
|
||||
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber snapshot) : snapshot_(snapshot) {}
|
||||
bool IsVisible(SequenceNumber seq) override { return seq <= snapshot_; }
|
||||
explicit TestReadCallback(SequenceNumber snapshot)
|
||||
: ReadCallback(snapshot), snapshot_(snapshot) {}
|
||||
bool IsVisibleFullCheck(SequenceNumber seq) override {
|
||||
return seq <= snapshot_;
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber snapshot_;
|
||||
@@ -2787,6 +2910,8 @@ TEST_F(DBTest2, TraceAndReplay) {
|
||||
Random rnd(301);
|
||||
Iterator* single_iter = nullptr;
|
||||
|
||||
ASSERT_TRUE(db_->EndTrace().IsIOError());
|
||||
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
@@ -3017,6 +3142,177 @@ TEST_F(DBTest2, TraceWithSampling) {
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, TraceWithFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
ReadOptions ro;
|
||||
WriteOptions wo;
|
||||
TraceOptions trace_opts;
|
||||
EnvOptions env_opts;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
Iterator* single_iter = nullptr;
|
||||
|
||||
trace_opts.filter = TraceFilterType::kTraceFilterWrite;
|
||||
|
||||
std::string trace_filename = dbname_ + "/rocksdb.trace";
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(NewFileTraceWriter(env_, env_opts, trace_filename, &trace_writer));
|
||||
ASSERT_OK(db_->StartTrace(trace_opts, std::move(trace_writer)));
|
||||
|
||||
ASSERT_OK(Put(0, "a", "1"));
|
||||
ASSERT_OK(Merge(0, "b", "2"));
|
||||
ASSERT_OK(Delete(0, "c"));
|
||||
ASSERT_OK(SingleDelete(0, "d"));
|
||||
ASSERT_OK(db_->DeleteRange(wo, dbfull()->DefaultColumnFamily(), "e", "f"));
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("f", "11"));
|
||||
ASSERT_OK(batch.Merge("g", "12"));
|
||||
ASSERT_OK(batch.Delete("h"));
|
||||
ASSERT_OK(batch.SingleDelete("i"));
|
||||
ASSERT_OK(batch.DeleteRange("j", "k"));
|
||||
ASSERT_OK(db_->Write(wo, &batch));
|
||||
|
||||
single_iter = db_->NewIterator(ro);
|
||||
single_iter->Seek("f");
|
||||
single_iter->SeekForPrev("g");
|
||||
delete single_iter;
|
||||
|
||||
ASSERT_EQ("1", Get(0, "a"));
|
||||
ASSERT_EQ("12", Get(0, "g"));
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "rocksdb", "rocks"));
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "leveldb"));
|
||||
|
||||
ASSERT_OK(db_->EndTrace());
|
||||
// These should not get into the trace file as it is after EndTrace.
|
||||
Put("hello", "world");
|
||||
Merge("foo", "bar");
|
||||
|
||||
// Open another db, replay, and verify the data
|
||||
std::string value;
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_replay";
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Using a different name than db2, to pacify infer's use-after-lifetime
|
||||
// warnings (http://fbinfer.com).
|
||||
DB* db2_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DB::Open(options, dbname2, &db2_init));
|
||||
ColumnFamilyHandle* cf;
|
||||
ASSERT_OK(
|
||||
db2_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf));
|
||||
delete cf;
|
||||
delete db2_init;
|
||||
|
||||
DB* db2 = nullptr;
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname2, column_families, &handles, &db2));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(NewFileTraceReader(env_, env_opts, trace_filename, &trace_reader));
|
||||
Replayer replayer(db2, handles_, std::move(trace_reader));
|
||||
ASSERT_OK(replayer.Replay());
|
||||
|
||||
// All the key-values should not present since we filter out the WRITE ops.
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "hello", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "world", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "foo", &value).IsNotFound());
|
||||
ASSERT_TRUE(db2->Get(ro, handles[0], "rocksdb", &value).IsNotFound());
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db2;
|
||||
ASSERT_OK(DestroyDB(dbname2, options));
|
||||
|
||||
// Set up a new db.
|
||||
std::string dbname3 = test::TmpDir(env_) + "/db_not_trace_read";
|
||||
ASSERT_OK(DestroyDB(dbname3, options));
|
||||
|
||||
DB* db3_init = nullptr;
|
||||
options.create_if_missing = true;
|
||||
ColumnFamilyHandle* cf3;
|
||||
ASSERT_OK(DB::Open(options, dbname3, &db3_init));
|
||||
ASSERT_OK(
|
||||
db3_init->CreateColumnFamily(ColumnFamilyOptions(), "pikachu", &cf3));
|
||||
delete cf3;
|
||||
delete db3_init;
|
||||
|
||||
column_families.clear();
|
||||
column_families.push_back(ColumnFamilyDescriptor("default", cf_options));
|
||||
column_families.push_back(
|
||||
ColumnFamilyDescriptor("pikachu", ColumnFamilyOptions()));
|
||||
handles.clear();
|
||||
|
||||
DB* db3 = nullptr;
|
||||
ASSERT_OK(DB::Open(DBOptions(), dbname3, column_families, &handles, &db3));
|
||||
|
||||
env_->SleepForMicroseconds(100);
|
||||
// Verify that the keys don't already exist
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "a", &value).IsNotFound());
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "g", &value).IsNotFound());
|
||||
|
||||
//The tracer will not record the READ ops.
|
||||
trace_opts.filter = TraceFilterType::kTraceFilterGet;
|
||||
std::string trace_filename3 = dbname_ + "/rocksdb.trace_3";
|
||||
std::unique_ptr<TraceWriter> trace_writer3;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, env_opts, trace_filename3, &trace_writer3));
|
||||
ASSERT_OK(db3->StartTrace(trace_opts, std::move(trace_writer3)));
|
||||
|
||||
ASSERT_OK(db3->Put(wo, handles[0], "a", "1"));
|
||||
ASSERT_OK(db3->Merge(wo, handles[0], "b", "2"));
|
||||
ASSERT_OK(db3->Delete(wo, handles[0], "c"));
|
||||
ASSERT_OK(db3->SingleDelete(wo, handles[0], "d"));
|
||||
|
||||
ASSERT_OK(db3->Get(ro, handles[0], "a", &value));
|
||||
ASSERT_EQ(value, "1");
|
||||
ASSERT_TRUE(db3->Get(ro, handles[0], "c", &value).IsNotFound());
|
||||
|
||||
ASSERT_OK(db3->EndTrace());
|
||||
|
||||
for (auto handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
delete db3;
|
||||
ASSERT_OK(DestroyDB(dbname3, options));
|
||||
|
||||
std::unique_ptr<TraceReader> trace_reader3;
|
||||
ASSERT_OK(
|
||||
NewFileTraceReader(env_, env_opts, trace_filename3, &trace_reader3));
|
||||
|
||||
// Count the number of records in the trace file;
|
||||
int count = 0;
|
||||
std::string data;
|
||||
Status s;
|
||||
while (true) {
|
||||
s = trace_reader3->Read(&data);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
// We also need to count the header and footer
|
||||
// 4 WRITE + HEADER + FOOTER = 6
|
||||
ASSERT_EQ(count, 6);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
@@ -3400,6 +3696,48 @@ TEST_F(DBTest2, MultiDBParallelOpenTest) {
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
namespace {
|
||||
class DummyOldStats : public Statistics {
|
||||
public:
|
||||
uint64_t getTickerCount(uint32_t /*ticker_type*/) const override { return 0; }
|
||||
void recordTick(uint32_t /* ticker_type */, uint64_t /* count */) override {
|
||||
num_rt++;
|
||||
}
|
||||
void setTickerCount(uint32_t /*ticker_type*/, uint64_t /*count*/) override {}
|
||||
uint64_t getAndResetTickerCount(uint32_t /*ticker_type*/) override {
|
||||
return 0;
|
||||
}
|
||||
void measureTime(uint32_t /*histogram_type*/, uint64_t /*count*/) override {
|
||||
num_mt++;
|
||||
}
|
||||
void histogramData(uint32_t /*histogram_type*/,
|
||||
rocksdb::HistogramData* const /*data*/) const override {}
|
||||
std::string getHistogramString(uint32_t /*type*/) const override {
|
||||
return "";
|
||||
}
|
||||
bool HistEnabledForType(uint32_t /*type*/) const override { return false; }
|
||||
std::string ToString() const override { return ""; }
|
||||
int num_rt = 0;
|
||||
int num_mt = 0;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBTest2, OldStatsInterface) {
|
||||
DummyOldStats* dos = new DummyOldStats();
|
||||
std::shared_ptr<Statistics> stats(dos);
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = stats;
|
||||
Reopen(options);
|
||||
|
||||
Put("foo", "bar");
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
ASSERT_GT(dos->num_rt, 0);
|
||||
ASSERT_GT(dos->num_mt, 0);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -780,6 +780,34 @@ std::vector<std::string> DBTestBase::MultiGet(std::vector<int> cfs,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> DBTestBase::MultiGet(const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
options.snapshot = snapshot;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> result;
|
||||
std::vector<Status> statuses(k.size());
|
||||
std::vector<PinnableSlice> pin_values(k.size());
|
||||
|
||||
for (unsigned int i = 0; i < k.size(); ++i) {
|
||||
keys.push_back(k[i]);
|
||||
}
|
||||
db_->MultiGet(options, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), pin_values.data(), statuses.data());
|
||||
result.resize(k.size());
|
||||
for (auto iter = result.begin(); iter != result.end(); ++iter) {
|
||||
iter->assign(pin_values[iter - result.begin()].data(),
|
||||
pin_values[iter - result.begin()].size());
|
||||
}
|
||||
for (unsigned int i = 0; i < statuses.size(); ++i) {
|
||||
if (statuses[i].IsNotFound()) {
|
||||
result[i] = "NOT_FOUND";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status DBTestBase::Get(const std::string& k, PinnableSlice* v) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
|
||||
@@ -438,6 +438,12 @@ class SpecialEnv : public EnvWrapper {
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status Prefetch(uint64_t offset, size_t n) override {
|
||||
Status s = target_->Prefetch(offset, n);
|
||||
*bytes_read_ += n;
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> target_;
|
||||
anon::AtomicCounter* counter_;
|
||||
@@ -842,6 +848,9 @@ class DBTestBase : public testing::Test {
|
||||
const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot = nullptr);
|
||||
|
||||
std::vector<std::string> MultiGet(const std::vector<std::string>& k,
|
||||
const Snapshot* snapshot = nullptr);
|
||||
|
||||
uint64_t GetNumSnapshots();
|
||||
|
||||
uint64_t GetTimeOldestSnapshots();
|
||||
|
||||
@@ -140,6 +140,51 @@ TEST_P(DBWriteTest, IOErrorOnWALWriteTriggersReadOnlyMode) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnSwitchMemtable) {
|
||||
Random rnd(301);
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetOptions();
|
||||
options.env = mock_env.get();
|
||||
options.writable_file_max_buffer_size = 4 * 1024 * 1024;
|
||||
options.write_buffer_size = 3 * 512 * 1024;
|
||||
options.wal_bytes_per_sync = 256 * 1024;
|
||||
options.manual_wal_flush = true;
|
||||
Reopen(options);
|
||||
mock_env->SetFilesystemActive(false, Status::IOError("Not active"));
|
||||
Status s;
|
||||
for (int i = 0; i < 4 * 512; ++i) {
|
||||
s = Put(Key(i), RandomString(&rnd, 1024));
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kFatalError);
|
||||
|
||||
mock_env->SetFilesystemActive(true);
|
||||
// Close before mock_env destruct.
|
||||
Close();
|
||||
}
|
||||
|
||||
// Test that db->LockWAL() flushes the WAL after locking.
|
||||
TEST_P(DBWriteTest, LockWalInEffect) {
|
||||
Options options = GetOptions();
|
||||
Reopen(options);
|
||||
// try the 1st WAL created during open
|
||||
ASSERT_OK(Put("key" + ToString(0), "value"));
|
||||
ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
|
||||
ASSERT_OK(dbfull()->LockWAL());
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
|
||||
ASSERT_OK(dbfull()->UnlockWAL());
|
||||
// try the 2nd wal created during SwitchWAL
|
||||
dbfull()->TEST_SwitchWAL();
|
||||
ASSERT_OK(Put("key" + ToString(0), "value"));
|
||||
ASSERT_TRUE(options.manual_wal_flush != dbfull()->TEST_WALBufferIsEmpty());
|
||||
ASSERT_OK(dbfull()->LockWAL());
|
||||
ASSERT_TRUE(dbfull()->TEST_WALBufferIsEmpty(false));
|
||||
ASSERT_OK(dbfull()->UnlockWAL());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
|
||||
testing::Values(DBTestBase::kDefault,
|
||||
DBTestBase::kConcurrentWALWrites,
|
||||
|
||||
+12
-14
@@ -106,9 +106,7 @@ std::string InternalKey::DebugString(bool hex) const {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* InternalKeyComparator::Name() const {
|
||||
return name_.c_str();
|
||||
}
|
||||
const char* InternalKeyComparator::Name() const { return name_.c_str(); }
|
||||
|
||||
int InternalKeyComparator::Compare(const ParsedInternalKey& a,
|
||||
const ParsedInternalKey& b) const {
|
||||
@@ -116,8 +114,7 @@ int InternalKeyComparator::Compare(const ParsedInternalKey& a,
|
||||
// increasing user key (according to user-supplied comparator)
|
||||
// decreasing sequence number
|
||||
// decreasing type (though sequence# should be enough to disambiguate)
|
||||
int r = user_comparator_->Compare(a.user_key, b.user_key);
|
||||
PERF_COUNTER_ADD(user_key_comparison_count, 1);
|
||||
int r = user_comparator_.Compare(a.user_key, b.user_key);
|
||||
if (r == 0) {
|
||||
if (a.sequence > b.sequence) {
|
||||
r = -1;
|
||||
@@ -132,19 +129,19 @@ int InternalKeyComparator::Compare(const ParsedInternalKey& a,
|
||||
return r;
|
||||
}
|
||||
|
||||
void InternalKeyComparator::FindShortestSeparator(
|
||||
std::string* start,
|
||||
const Slice& limit) const {
|
||||
void InternalKeyComparator::FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const {
|
||||
// Attempt to shorten the user portion of the key
|
||||
Slice user_start = ExtractUserKey(*start);
|
||||
Slice user_limit = ExtractUserKey(limit);
|
||||
std::string tmp(user_start.data(), user_start.size());
|
||||
user_comparator_->FindShortestSeparator(&tmp, user_limit);
|
||||
user_comparator_.FindShortestSeparator(&tmp, user_limit);
|
||||
if (tmp.size() <= user_start.size() &&
|
||||
user_comparator_->Compare(user_start, tmp) < 0) {
|
||||
user_comparator_.Compare(user_start, tmp) < 0) {
|
||||
// User key has become shorter physically, but larger logically.
|
||||
// Tack on the earliest possible number to the shortened user key.
|
||||
PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));
|
||||
PutFixed64(&tmp,
|
||||
PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek));
|
||||
assert(this->Compare(*start, tmp) < 0);
|
||||
assert(this->Compare(tmp, limit) < 0);
|
||||
start->swap(tmp);
|
||||
@@ -154,12 +151,13 @@ void InternalKeyComparator::FindShortestSeparator(
|
||||
void InternalKeyComparator::FindShortSuccessor(std::string* key) const {
|
||||
Slice user_key = ExtractUserKey(*key);
|
||||
std::string tmp(user_key.data(), user_key.size());
|
||||
user_comparator_->FindShortSuccessor(&tmp);
|
||||
user_comparator_.FindShortSuccessor(&tmp);
|
||||
if (tmp.size() <= user_key.size() &&
|
||||
user_comparator_->Compare(user_key, tmp) < 0) {
|
||||
user_comparator_.Compare(user_key, tmp) < 0) {
|
||||
// User key has become shorter physically, but larger logically.
|
||||
// Tack on the earliest possible number to the shortened user key.
|
||||
PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));
|
||||
PutFixed64(&tmp,
|
||||
PackSequenceAndType(kMaxSequenceNumber, kValueTypeForSeek));
|
||||
assert(this->Compare(*key, tmp) < 0);
|
||||
key->swap(tmp);
|
||||
}
|
||||
|
||||
+29
-72
@@ -9,8 +9,11 @@
|
||||
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "db/lookup_key.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -21,6 +24,7 @@
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/user_comparator_wrapper.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -80,8 +84,7 @@ inline bool IsExtendedValueType(ValueType t) {
|
||||
|
||||
// We leave eight bits empty at the bottom so a type and sequence#
|
||||
// can be packed together into 64-bits.
|
||||
static const SequenceNumber kMaxSequenceNumber =
|
||||
((0x1ull << 56) - 1);
|
||||
static const SequenceNumber kMaxSequenceNumber = ((0x1ull << 56) - 1);
|
||||
|
||||
static const SequenceNumber kDisableGlobalSequenceNumber = port::kMaxUint64;
|
||||
|
||||
@@ -92,9 +95,9 @@ struct ParsedInternalKey {
|
||||
|
||||
ParsedInternalKey()
|
||||
: sequence(kMaxSequenceNumber) // Make code analyzer happy
|
||||
{} // Intentionally left uninitialized (for speed)
|
||||
{} // Intentionally left uninitialized (for speed)
|
||||
ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
|
||||
: user_key(u), sequence(seq), type(t) { }
|
||||
: user_key(u), sequence(seq), type(t) {}
|
||||
std::string DebugString(bool hex = false) const;
|
||||
|
||||
void clear() {
|
||||
@@ -160,13 +163,14 @@ class InternalKeyComparator
|
||||
#endif
|
||||
: public Comparator {
|
||||
private:
|
||||
const Comparator* user_comparator_;
|
||||
UserComparatorWrapper user_comparator_;
|
||||
std::string name_;
|
||||
|
||||
public:
|
||||
explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c),
|
||||
name_("rocksdb.InternalKeyComparator:" +
|
||||
std::string(user_comparator_->Name())) {
|
||||
}
|
||||
explicit InternalKeyComparator(const Comparator* c)
|
||||
: user_comparator_(c),
|
||||
name_("rocksdb.InternalKeyComparator:" +
|
||||
std::string(user_comparator_.Name())) {}
|
||||
virtual ~InternalKeyComparator() {}
|
||||
|
||||
virtual const char* Name() const override;
|
||||
@@ -177,12 +181,14 @@ class InternalKeyComparator
|
||||
const Slice& limit) const override;
|
||||
virtual void FindShortSuccessor(std::string* key) const override;
|
||||
|
||||
const Comparator* user_comparator() const { return user_comparator_; }
|
||||
const Comparator* user_comparator() const {
|
||||
return user_comparator_.user_comparator();
|
||||
}
|
||||
|
||||
int Compare(const InternalKey& a, const InternalKey& b) const;
|
||||
int Compare(const ParsedInternalKey& a, const ParsedInternalKey& b) const;
|
||||
virtual const Comparator* GetRootComparator() const override {
|
||||
return user_comparator_->GetRootComparator();
|
||||
return user_comparator_.GetRootComparator();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -192,8 +198,9 @@ class InternalKeyComparator
|
||||
class InternalKey {
|
||||
private:
|
||||
std::string rep_;
|
||||
|
||||
public:
|
||||
InternalKey() { } // Leave rep_ as empty to indicate it is invalid
|
||||
InternalKey() {} // Leave rep_ as empty to indicate it is invalid
|
||||
InternalKey(const Slice& _user_key, SequenceNumber s, ValueType t) {
|
||||
AppendInternalKey(&rep_, ParsedInternalKey(_user_key, s, t));
|
||||
}
|
||||
@@ -250,8 +257,8 @@ class InternalKey {
|
||||
std::string DebugString(bool hex = false) const;
|
||||
};
|
||||
|
||||
inline int InternalKeyComparator::Compare(
|
||||
const InternalKey& a, const InternalKey& b) const {
|
||||
inline int InternalKeyComparator::Compare(const InternalKey& a,
|
||||
const InternalKey& b) const {
|
||||
return Compare(a.Encode(), b.Encode());
|
||||
}
|
||||
|
||||
@@ -288,60 +295,13 @@ inline uint64_t GetInternalKeySeqno(const Slice& internal_key) {
|
||||
return num >> 8;
|
||||
}
|
||||
|
||||
|
||||
// A helper class useful for DBImpl::Get()
|
||||
class LookupKey {
|
||||
public:
|
||||
// Initialize *this for looking up user_key at a snapshot with
|
||||
// the specified sequence number.
|
||||
LookupKey(const Slice& _user_key, SequenceNumber sequence);
|
||||
|
||||
~LookupKey();
|
||||
|
||||
// Return a key suitable for lookup in a MemTable.
|
||||
Slice memtable_key() const {
|
||||
return Slice(start_, static_cast<size_t>(end_ - start_));
|
||||
}
|
||||
|
||||
// Return an internal key (suitable for passing to an internal iterator)
|
||||
Slice internal_key() const {
|
||||
return Slice(kstart_, static_cast<size_t>(end_ - kstart_));
|
||||
}
|
||||
|
||||
// Return the user key
|
||||
Slice user_key() const {
|
||||
return Slice(kstart_, static_cast<size_t>(end_ - kstart_ - 8));
|
||||
}
|
||||
|
||||
private:
|
||||
// We construct a char array of the form:
|
||||
// klength varint32 <-- start_
|
||||
// userkey char[klength] <-- kstart_
|
||||
// tag uint64
|
||||
// <-- end_
|
||||
// The array is a suitable MemTable key.
|
||||
// The suffix starting with "userkey" can be used as an InternalKey.
|
||||
const char* start_;
|
||||
const char* kstart_;
|
||||
const char* end_;
|
||||
char space_[200]; // Avoid allocation for short keys
|
||||
|
||||
// No copying allowed
|
||||
LookupKey(const LookupKey&);
|
||||
void operator=(const LookupKey&);
|
||||
};
|
||||
|
||||
inline LookupKey::~LookupKey() {
|
||||
if (start_ != space_) delete[] start_;
|
||||
}
|
||||
|
||||
class IterKey {
|
||||
public:
|
||||
IterKey()
|
||||
: buf_(space_),
|
||||
buf_size_(sizeof(space_)),
|
||||
key_(buf_),
|
||||
key_size_(0),
|
||||
buf_size_(sizeof(space_)),
|
||||
is_user_key_(true) {}
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
@@ -496,9 +456,9 @@ class IterKey {
|
||||
|
||||
private:
|
||||
char* buf_;
|
||||
size_t buf_size_;
|
||||
const char* key_;
|
||||
size_t key_size_;
|
||||
size_t buf_size_;
|
||||
char space_[32]; // Avoid allocation for short keys
|
||||
bool is_user_key_;
|
||||
|
||||
@@ -633,14 +593,13 @@ struct RangeTombstone {
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const {
|
||||
inline int InternalKeyComparator::Compare(const Slice& akey,
|
||||
const Slice& bkey) const {
|
||||
// Order by:
|
||||
// increasing user key (according to user-supplied comparator)
|
||||
// decreasing sequence number
|
||||
// decreasing type (though sequence# should be enough to disambiguate)
|
||||
int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
|
||||
PERF_COUNTER_ADD(user_key_comparison_count, 1);
|
||||
int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
|
||||
if (r == 0) {
|
||||
const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);
|
||||
const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);
|
||||
@@ -653,14 +612,12 @@ int InternalKeyComparator::Compare(const Slice& akey, const Slice& bkey) const {
|
||||
return r;
|
||||
}
|
||||
|
||||
inline
|
||||
int InternalKeyComparator::CompareKeySeq(const Slice& akey,
|
||||
const Slice& bkey) const {
|
||||
inline int InternalKeyComparator::CompareKeySeq(const Slice& akey,
|
||||
const Slice& bkey) const {
|
||||
// Order by:
|
||||
// increasing user key (according to user-supplied comparator)
|
||||
// decreasing sequence number
|
||||
int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
|
||||
PERF_COUNTER_ADD(user_key_comparison_count, 1);
|
||||
int r = user_comparator_.Compare(ExtractUserKey(akey), ExtractUserKey(bkey));
|
||||
if (r == 0) {
|
||||
// Shift the number to exclude the last byte which contains the value type
|
||||
const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8) >> 8;
|
||||
|
||||
+51
-2
@@ -71,7 +71,9 @@ class DeleteFileTest : public testing::Test {
|
||||
}
|
||||
db_ = nullptr;
|
||||
options_.create_if_missing = create;
|
||||
return DB::Open(options_, dbname_, &db_);
|
||||
Status s = DB::Open(options_, dbname_, &db_);
|
||||
assert(db_);
|
||||
return s;
|
||||
}
|
||||
|
||||
void CloseDB() {
|
||||
@@ -241,7 +243,7 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeTest) {
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeIteratorTest) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
@@ -279,6 +281,53 @@ TEST_F(DeleteFileTest, BackgroundPurgeTest) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeCFDropTest) {
|
||||
auto do_test = [&](bool bg_purge) {
|
||||
ColumnFamilyOptions co;
|
||||
WriteOptions wo;
|
||||
FlushOptions fo;
|
||||
ColumnFamilyHandle* cfh = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CreateColumnFamily(co, "dropme", &cfh));
|
||||
|
||||
ASSERT_OK(db_->Put(wo, cfh, "pika", "chu"));
|
||||
ASSERT_OK(db_->Flush(fo, cfh));
|
||||
// Expect 1 sst file.
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
// Still 1 file, it won't be deleted while ColumnFamilyHandle is alive.
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
delete cfh;
|
||||
test::SleepingBackgroundTask sleeping_task_after;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_after, Env::Priority::HIGH);
|
||||
// If background purge is enabled, the file should still be there.
|
||||
CheckFileTypeCounts(dbname_, 0, bg_purge ? 1 : 0, 1);
|
||||
|
||||
// Execute background purges.
|
||||
sleeping_task_after.WakeUp();
|
||||
sleeping_task_after.WaitUntilDone();
|
||||
// The file should have been deleted.
|
||||
CheckFileTypeCounts(dbname_, 0, 0, 1);
|
||||
};
|
||||
|
||||
{
|
||||
SCOPED_TRACE("avoid_unnecessary_blocking_io = false");
|
||||
do_test(false);
|
||||
}
|
||||
|
||||
options_.avoid_unnecessary_blocking_io = true;
|
||||
ASSERT_OK(ReopenDB(false));
|
||||
{
|
||||
SCOPED_TRACE("avoid_unnecessary_blocking_io = true");
|
||||
do_test(true);
|
||||
}
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
// This test is to reproduce a bug that read invalid ReadOption in iterator
|
||||
// cleanup function
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) {
|
||||
|
||||
+25
-2
@@ -87,7 +87,13 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
|
||||
// basic properties:
|
||||
jwriter << "data_size" << table_properties.data_size << "index_size"
|
||||
<< table_properties.index_size << "filter_size"
|
||||
<< table_properties.index_size << "index_partitions"
|
||||
<< table_properties.index_partitions << "top_level_index_size"
|
||||
<< table_properties.top_level_index_size
|
||||
<< "index_key_is_user_key"
|
||||
<< table_properties.index_key_is_user_key
|
||||
<< "index_value_is_delta_encoded"
|
||||
<< table_properties.index_value_is_delta_encoded << "filter_size"
|
||||
<< table_properties.filter_size << "raw_key_size"
|
||||
<< table_properties.raw_key_size << "raw_average_key_size"
|
||||
<< SafeDivide(table_properties.raw_key_size,
|
||||
@@ -98,7 +104,24 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
table_properties.num_entries)
|
||||
<< "num_data_blocks" << table_properties.num_data_blocks
|
||||
<< "num_entries" << table_properties.num_entries
|
||||
<< "filter_policy_name" << table_properties.filter_policy_name;
|
||||
<< "num_deletions" << table_properties.num_deletions
|
||||
<< "num_merge_operands" << table_properties.num_merge_operands
|
||||
<< "num_range_deletions" << table_properties.num_merge_operands
|
||||
<< "format_version" << table_properties.format_version
|
||||
<< "fixed_key_len" << table_properties.fixed_key_len
|
||||
<< "filter_policy" << table_properties.filter_policy_name
|
||||
<< "column_family_name" << table_properties.column_family_name
|
||||
<< "column_family_id" << table_properties.column_family_id
|
||||
<< "comparator" << table_properties.comparator_name
|
||||
<< "merge_operator" << table_properties.merge_operator_name
|
||||
<< "prefix_extractor_name"
|
||||
<< table_properties.prefix_extractor_name << "property_collectors"
|
||||
<< table_properties.property_collectors_names << "compression"
|
||||
<< table_properties.compression_name << "compression_options"
|
||||
<< table_properties.compression_options << "creation_time"
|
||||
<< table_properties.creation_time << "oldest_key_time"
|
||||
<< table_properties.oldest_key_time << "file_creation_time"
|
||||
<< table_properties.file_creation_time;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : table_properties.readable_properties) {
|
||||
|
||||
@@ -124,7 +124,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// We failed, remove all files that we copied into the db
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
@@ -227,7 +227,8 @@ void ExternalSstFileIngestionJob::UpdateStats() {
|
||||
stats.bytes_moved = f.fd.GetFileSize();
|
||||
}
|
||||
stats.num_output_files = 1;
|
||||
cfd_->internal_stats()->AddCompactionStats(f.picked_level, stats);
|
||||
cfd_->internal_stats()->AddCompactionStats(f.picked_level,
|
||||
Env::Priority::USER, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE,
|
||||
f.fd.GetFileSize());
|
||||
total_keys += f.num_entries;
|
||||
@@ -254,6 +255,9 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
|
||||
// We failed to add the files to the database
|
||||
// remove all the files we copied
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.internal_file_path.empty()) {
|
||||
continue;
|
||||
}
|
||||
Status s = env_->DeleteFile(f.internal_file_path);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
|
||||
+101
-130
@@ -83,7 +83,8 @@ class ExternalSSTFileTest
|
||||
const Options options,
|
||||
std::vector<std::pair<std::string, std::string>> data, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
// Generate a file id if not provided
|
||||
@@ -128,66 +129,7 @@ class ExternalSSTFileTest
|
||||
ifo.allow_global_seqno = allow_global_seqno;
|
||||
ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
s = db_->IngestExternalFile({file_path}, ifo);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && true_data) {
|
||||
for (auto& entry : data) {
|
||||
(*true_data)[entry.first] = entry.second;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFileIngestBehind(
|
||||
const Options options, const IngestExternalFileOptions ifo,
|
||||
std::vector<std::pair<std::string, std::string>> data, int file_id = -1,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
// Generate a file id if not provided
|
||||
if (file_id == -1) {
|
||||
file_id = last_file_id_ + 1;
|
||||
last_file_id_++;
|
||||
}
|
||||
|
||||
// Sort data if asked to do so
|
||||
if (sort_data) {
|
||||
std::sort(data.begin(), data.end(),
|
||||
[&](const std::pair<std::string, std::string>& e1,
|
||||
const std::pair<std::string, std::string>& e2) {
|
||||
return options.comparator->Compare(e1.first, e2.first) < 0;
|
||||
});
|
||||
auto uniq_iter = std::unique(
|
||||
data.begin(), data.end(),
|
||||
[&](const std::pair<std::string, std::string>& e1,
|
||||
const std::pair<std::string, std::string>& e2) {
|
||||
return options.comparator->Compare(e1.first, e2.first) == 0;
|
||||
});
|
||||
data.resize(uniq_iter - data.begin());
|
||||
}
|
||||
std::string file_path = sst_files_dir_ + ToString(file_id);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, cfh);
|
||||
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
for (auto& entry : data) {
|
||||
s = sst_file_writer.Put(entry.first, entry.second);
|
||||
if (!s.ok()) {
|
||||
sst_file_writer.Finish();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = sst_file_writer.Finish();
|
||||
|
||||
if (s.ok()) {
|
||||
ifo.ingest_behind = ingest_behind;
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
@@ -243,31 +185,35 @@ class ExternalSSTFileTest
|
||||
const Options options, std::vector<std::pair<int, std::string>> data,
|
||||
int file_id = -1, bool allow_global_seqno = false,
|
||||
bool write_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& entry : data) {
|
||||
file_data.emplace_back(Key(entry.first), entry.second);
|
||||
}
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest,
|
||||
ingest_behind, sort_data, true_data, cfh);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, int file_id = -1,
|
||||
bool allow_global_seqno = false, bool write_global_seqno = false,
|
||||
bool verify_checksums_before_ingest = true, bool sort_data = false,
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& k : keys) {
|
||||
file_data.emplace_back(Key(k), Key(k) + ToString(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest,
|
||||
ingest_behind, sort_data, true_data, cfh);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
@@ -1246,12 +1192,12 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
|
||||
// File 0 will go to last level (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 10}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,0,1");
|
||||
|
||||
// File 1 will go to level L2 (since it overlap with file 0 in L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 9}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "0,0,1,1");
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
@@ -1281,12 +1227,12 @@ TEST_P(ExternalSSTFileTest, PickedLevel) {
|
||||
// This file overlaps with file 0 (L3), file 1 (L2) and the
|
||||
// output of compaction going to L1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {4, 7}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,1");
|
||||
|
||||
// This file does not overlap with any file or with the running compaction
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
true, false, &true_data));
|
||||
false, false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,1,2");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -1517,12 +1463,12 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
// This file overlaps with the output of the compaction (going to L3)
|
||||
// so the file will be added to L0 since L3 is the base level
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {31, 32, 33, 34}, -1, false,
|
||||
false, true, false, &true_data));
|
||||
false, true, false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5");
|
||||
|
||||
// This file does not overlap with the current running compactiong
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {9000, 9001}, -1, false, false,
|
||||
true, false, &true_data));
|
||||
true, false, false, &true_data));
|
||||
EXPECT_EQ(FilesPerLevel(), "5,0,0,1");
|
||||
|
||||
// Hold compaction from finishing
|
||||
@@ -1537,25 +1483,25 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 15, 19}, -1, false, false,
|
||||
true, false, &true_data));
|
||||
true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,3");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1000, 1001, 1002}, -1, false,
|
||||
false, true, false, &true_data));
|
||||
false, true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,4");
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {500, 600, 700}, -1, false,
|
||||
false, true, false, &true_data));
|
||||
false, true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "1,0,0,5");
|
||||
|
||||
// File 5 overlaps with file 2 (L3 / base level)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {2, 10}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "2,0,0,5");
|
||||
|
||||
// File 6 overlaps with file 2 (L3 / base level) and file 5 (L0)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 9}, -1, false, false, true,
|
||||
false, &true_data));
|
||||
false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "3,0,0,5");
|
||||
|
||||
// Verify data in files
|
||||
@@ -1574,7 +1520,7 @@ TEST_F(ExternalSSTFileTest, PickedLevelDynamic) {
|
||||
|
||||
// File 7 overlaps with file 4 (L3)
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {650, 651, 652}, -1, false,
|
||||
false, true, false, &true_data));
|
||||
false, true, false, false, &true_data));
|
||||
ASSERT_EQ(FilesPerLevel(), "5,0,0,5");
|
||||
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
@@ -1741,7 +1687,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
} else {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, random_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, &true_data));
|
||||
verify_checksums_before_ingest, false, true, &true_data));
|
||||
}
|
||||
}
|
||||
size_t kcnt = 0;
|
||||
@@ -1774,7 +1720,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB, will go to L4
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel());
|
||||
@@ -1786,7 +1732,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file overlap with the memtable, so it will flush it and add
|
||||
// it self to L0
|
||||
@@ -1799,7 +1745,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB and fit in L4 as well
|
||||
ASSERT_EQ("2,0,0,0,2", FilesPerLevel());
|
||||
@@ -1811,7 +1757,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
}
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it in L3
|
||||
ASSERT_EQ("2,0,0,1,2", FilesPerLevel());
|
||||
@@ -1839,7 +1785,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
// No need for flush
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {90, 100, 110}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
@@ -1847,7 +1793,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {19, 20, 21}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -1863,15 +1809,15 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoMemtableFlush) {
|
||||
// No need for flush, this file keys fit between the memtable keys
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {202, 203, 204}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, &true_data));
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_GE(entries_in_memtable, 1);
|
||||
|
||||
// This file will flush the memtable
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {206, 207}, -1, true, false, write_global_seqno,
|
||||
verify_checksums_before_ingest, &true_data));
|
||||
options, {206, 207}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&entries_in_memtable);
|
||||
ASSERT_EQ(entries_in_memtable, 0);
|
||||
@@ -1892,13 +1838,13 @@ TEST_P(ExternalSSTFileTest, L0SortingIssue) {
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
// No Flush needed, No global seqno needed, Ingest in L1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true,
|
||||
write_global_seqno,
|
||||
verify_checksums_before_ingest, false));
|
||||
ASSERT_OK(
|
||||
GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false));
|
||||
// No Flush needed, but need a global seqno, Ingest in L0
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {7, 8}, -1, true,
|
||||
write_global_seqno,
|
||||
verify_checksums_before_ingest, false));
|
||||
ASSERT_OK(
|
||||
GenerateAndAddExternalFile(options, {7, 8}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false));
|
||||
printf("%s\n", FilesPerLevel().c_str());
|
||||
|
||||
// Overwrite what we added using external files
|
||||
@@ -2139,7 +2085,7 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
// Ingest into default cf
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[0]));
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[0]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 1);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "default");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2151,7 +2097,7 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
// Ingest into cf1
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[1]));
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[1]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 2);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "koko");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2163,7 +2109,7 @@ TEST_P(ExternalSSTFileTest, IngestionListener) {
|
||||
// Ingest into cf2
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 2}, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, true, nullptr, handles_[2]));
|
||||
verify_checksums_before_ingest, false, true, nullptr, handles_[2]));
|
||||
ASSERT_EQ(listener->ingested_files.size(), 3);
|
||||
ASSERT_EQ(listener->ingested_files.back().cf_name, "toto");
|
||||
ASSERT_EQ(listener->ingested_files.back().global_seqno, 0);
|
||||
@@ -2226,16 +2172,16 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
file_data.emplace_back(Key(i), "ingest_behind");
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.allow_global_seqno = true;
|
||||
ifo.ingest_behind = true;
|
||||
ifo.write_global_seqno = std::get<0>(GetParam());
|
||||
ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
bool allow_global_seqno = true;
|
||||
bool ingest_behind = true;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
|
||||
// Can't ingest behind since allow_ingest_behind isn't set to true
|
||||
ASSERT_NOK(GenerateAndAddExternalFileIngestBehind(options, ifo,
|
||||
file_data, -1, false,
|
||||
&true_data));
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
|
||||
&true_data));
|
||||
|
||||
options.allow_ingest_behind = true;
|
||||
// check that we still can open the DB, as num_levels should be
|
||||
@@ -2253,14 +2199,16 @@ TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
// Universal picker should go at second from the bottom level
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
ASSERT_OK(GenerateAndAddExternalFileIngestBehind(options, ifo,
|
||||
file_data, -1, false,
|
||||
&true_data));
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
// this time ingest should fail as the file doesn't fit to the bottom level
|
||||
ASSERT_NOK(GenerateAndAddExternalFileIngestBehind(options, ifo,
|
||||
file_data, -1, false,
|
||||
&true_data));
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
// bottom level should be empty
|
||||
@@ -2352,10 +2300,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2369,6 +2318,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2376,8 +2328,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_Success) {
|
||||
-1, true, true_data);
|
||||
ASSERT_OK(s);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2409,10 +2362,11 @@ TEST_P(ExternalSSTFileTest,
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
const std::vector<std::map<std::string, std::string>> data_before_ingestion =
|
||||
{{{"foo1", "fv1_0"}, {"foo2", "fv2_0"}, {"foo3", "fv3_0"}},
|
||||
{{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}}};
|
||||
{{"bar1", "bv1_0"}, {"bar2", "bv2_0"}, {"bar3", "bv3_0"}},
|
||||
{{"bar4", "bv4_0"}, {"bar5", "bv5_0"}, {"bar6", "bv6_0"}}};
|
||||
for (size_t i = 0; i != handles_.size(); ++i) {
|
||||
int cf = static_cast<int>(i);
|
||||
const auto& orig_data = data_before_ingestion[i];
|
||||
@@ -2425,6 +2379,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2438,6 +2393,8 @@ TEST_P(ExternalSSTFileTest,
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2491,10 +2448,11 @@ TEST_P(ExternalSSTFileTest,
|
||||
dbfull()->ReleaseSnapshot(read_opts.snapshot);
|
||||
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
// Should see consistent state after ingestion for all column families even
|
||||
// without snapshot.
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2524,10 +2482,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeLastJobPrepare:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2541,6 +2500,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2560,8 +2522,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_PrepareFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2590,10 +2553,11 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
"DBImpl::IngestExternalFiles:BeforeJobsRun:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2607,6 +2571,8 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2626,8 +2592,9 @@ TEST_P(ExternalSSTFileTest, IngestFilesIntoMultipleColumnFamilies_CommitFail) {
|
||||
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
@@ -2647,7 +2614,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
|
||||
SyncPoint::GetInstance()->ClearTrace();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -2665,6 +2632,7 @@ TEST_P(ExternalSSTFileTest,
|
||||
std::vector<ColumnFamilyHandle*> column_families;
|
||||
column_families.push_back(handles_[0]);
|
||||
column_families.push_back(handles_[1]);
|
||||
column_families.push_back(handles_[2]);
|
||||
std::vector<IngestExternalFileOptions> ifos(column_families.size());
|
||||
for (auto& ifo : ifos) {
|
||||
ifo.allow_global_seqno = true; // Always allow global_seqno
|
||||
@@ -2678,6 +2646,8 @@ TEST_P(ExternalSSTFileTest,
|
||||
{std::make_pair("foo1", "fv1"), std::make_pair("foo2", "fv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar1", "bv1"), std::make_pair("bar2", "bv2")});
|
||||
data.push_back(
|
||||
{std::make_pair("bar3", "bv3"), std::make_pair("bar4", "bv4")});
|
||||
// Resize the true_data vector upon construction to avoid re-alloc
|
||||
std::vector<std::map<std::string, std::string>> true_data(
|
||||
column_families.size());
|
||||
@@ -2698,8 +2668,9 @@ TEST_P(ExternalSSTFileTest,
|
||||
fault_injection_env->DropUnsyncedFileData();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Close();
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
ASSERT_EQ(3, handles_.size());
|
||||
int cf = 0;
|
||||
for (const auto& verify_map : true_data) {
|
||||
for (const auto& elem : verify_map) {
|
||||
|
||||
+9
-6
@@ -100,7 +100,8 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest)
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri)
|
||||
: dbname_(dbname),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
@@ -125,7 +126,8 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
write_manifest_(write_manifest),
|
||||
edit_(nullptr),
|
||||
base_(nullptr),
|
||||
pick_memtable_called(false) {
|
||||
pick_memtable_called(false),
|
||||
thread_pri_(thread_pri) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -372,11 +374,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
output_compression_, cfd_->ioptions()->compression_opts,
|
||||
output_compression_, mutable_cf_options_.sample_for_compression,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, event_logger_, job_context_->job_id,
|
||||
Env::IO_HIGH, &table_properties_, 0 /* level */, current_time,
|
||||
oldest_key_time, write_hint);
|
||||
oldest_key_time, write_hint, current_time);
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -415,8 +418,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.cpu_micros = db_options_.env->NowCPUNanos() / 1000 - start_cpu_micros;
|
||||
stats.bytes_written = meta_.fd.GetFileSize();
|
||||
MeasureTime(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
RecordTimeToHistogram(stats_, FLUSH_TIME, stats.micros);
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, thread_pri_, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta_.fd.GetFileSize());
|
||||
RecordFlushIOStats();
|
||||
|
||||
+3
-1
@@ -68,7 +68,8 @@ class FlushJob {
|
||||
LogBuffer* log_buffer, Directory* db_directory,
|
||||
Directory* output_file_directory, CompressionType output_compression,
|
||||
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest);
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
~FlushJob();
|
||||
|
||||
@@ -137,6 +138,7 @@ class FlushJob {
|
||||
VersionEdit* edit_;
|
||||
Version* base_;
|
||||
bool pick_memtable_called;
|
||||
Env::Priority thread_pri_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+34
-29
@@ -118,13 +118,14 @@ TEST_F(FlushJobTest, Empty) {
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), nullptr /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_, {},
|
||||
kMaxSequenceNumber, snapshot_checker, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger, false,
|
||||
true /* sync_output_directory */, true /* write_manifest */);
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
nullptr /* memtable_id */, env_options_, versions_.get(),
|
||||
&mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, nullptr, &event_logger, false,
|
||||
true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
flush_job.PickMemTable();
|
||||
@@ -165,13 +166,14 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), nullptr /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_, {},
|
||||
kMaxSequenceNumber, snapshot_checker, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */);
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
nullptr /* memtable_id */, env_options_, versions_.get(),
|
||||
&mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, db_options_.statistics.get(),
|
||||
&event_logger, true, true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER);
|
||||
|
||||
HistogramData hist;
|
||||
FileMetaData file_meta;
|
||||
@@ -228,13 +230,14 @@ TEST_F(FlushJobTest, FlushMemTablesSingleColumnFamily) {
|
||||
uint64_t smallest_memtable_id = memtable_ids.front();
|
||||
uint64_t flush_memtable_id = smallest_memtable_id + num_mems_to_flush - 1;
|
||||
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), &flush_memtable_id, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, {}, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */, true /* write_manifest */);
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
&flush_memtable_id, env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, {}, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER);
|
||||
HistogramData hist;
|
||||
FileMetaData file_meta;
|
||||
mutex_.Lock();
|
||||
@@ -304,7 +307,8 @@ TEST_F(FlushJobTest, FlushMemtablesMultipleColumnFamilies) {
|
||||
&shutting_down_, snapshot_seqs, kMaxSequenceNumber, snapshot_checker,
|
||||
&job_context, nullptr, nullptr, nullptr, kNoCompression,
|
||||
db_options_.statistics.get(), &event_logger, true,
|
||||
false /* sync_output_directory */, false /* write_manifest */);
|
||||
false /* sync_output_directory */, false /* write_manifest */,
|
||||
Env::Priority::USER);
|
||||
k++;
|
||||
}
|
||||
HistogramData hist;
|
||||
@@ -413,13 +417,14 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
SnapshotChecker* snapshot_checker = nullptr; // not relavant
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), nullptr /* memtable_id */,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_, snapshots,
|
||||
kMaxSequenceNumber, snapshot_checker, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, db_options_.statistics.get(), &event_logger,
|
||||
true, true /* sync_output_directory */, true /* write_manifest */);
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
nullptr /* memtable_id */, env_options_, versions_.get(),
|
||||
&mutex_, &shutting_down_, snapshots, kMaxSequenceNumber,
|
||||
snapshot_checker, &job_context, nullptr, nullptr, nullptr,
|
||||
kNoCompression, db_options_.statistics.get(),
|
||||
&event_logger, true, true /* sync_output_directory */,
|
||||
true /* write_manifest */, Env::Priority::USER);
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
|
||||
@@ -265,16 +265,18 @@ void ForwardIterator::SVCleanup() {
|
||||
if (sv_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
bool background_purge =
|
||||
read_options_.background_purge_on_iterator_cleanup ||
|
||||
db_->immutable_db_options().avoid_unnecessary_blocking_io;
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
// pinned_iters_mgr_ tells us to make sure that all visited key-value slices
|
||||
// are alive until pinned_iters_mgr_->ReleasePinnedData() is called.
|
||||
// The slices may point into some memtables owned by sv_, so we need to keep
|
||||
// sv_ referenced until pinned_iters_mgr_ unpins everything.
|
||||
auto p = new SVCleanupParams{
|
||||
db_, sv_, read_options_.background_purge_on_iterator_cleanup};
|
||||
auto p = new SVCleanupParams{db_, sv_, background_purge};
|
||||
pinned_iters_mgr_->PinPtr(p, &ForwardIterator::DeferredSVCleanup);
|
||||
} else {
|
||||
SVCleanup(db_, sv_, read_options_.background_purge_on_iterator_cleanup);
|
||||
SVCleanup(db_, sv_, background_purge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,9 +381,9 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
}
|
||||
}
|
||||
|
||||
Slice user_key;
|
||||
Slice target_user_key;
|
||||
if (!seek_to_first) {
|
||||
user_key = ExtractUserKey(internal_key);
|
||||
target_user_key = ExtractUserKey(internal_key);
|
||||
}
|
||||
const VersionStorageInfo* vstorage = sv_->current->storage_info();
|
||||
const std::vector<FileMetaData*>& l0 = vstorage->LevelFiles(0);
|
||||
@@ -394,8 +396,8 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
} else {
|
||||
// If the target key passes over the larget key, we are sure Next()
|
||||
// won't go over this file.
|
||||
if (user_comparator_->Compare(user_key,
|
||||
l0[i]->largest.user_key()) > 0) {
|
||||
if (user_comparator_->Compare(target_user_key,
|
||||
l0[i]->largest.user_key()) > 0) {
|
||||
if (read_options_.iterate_upper_bound != nullptr) {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
|
||||
+45
-12
@@ -2,6 +2,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
@@ -58,7 +60,8 @@ const double kMB = 1048576.0;
|
||||
const double kGB = kMB * 1024;
|
||||
const double kMicrosInSec = 1000000.0;
|
||||
|
||||
void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name,
|
||||
const std::string& group_by) {
|
||||
int written_size =
|
||||
snprintf(buf, len, "\n** Compaction Stats [%s] **\n", cf_name.c_str());
|
||||
auto hdr = [](LevelStatType t) {
|
||||
@@ -66,17 +69,18 @@ void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name) {
|
||||
};
|
||||
int line_size = snprintf(
|
||||
buf + written_size, len - written_size,
|
||||
"Level %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
|
||||
"%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n",
|
||||
// Note that we skip COMPACTED_FILES and merge it with Files column
|
||||
hdr(LevelStatType::NUM_FILES), hdr(LevelStatType::SIZE_BYTES),
|
||||
hdr(LevelStatType::SCORE), hdr(LevelStatType::READ_GB),
|
||||
hdr(LevelStatType::RN_GB), hdr(LevelStatType::RNP1_GB),
|
||||
hdr(LevelStatType::WRITE_GB), hdr(LevelStatType::W_NEW_GB),
|
||||
hdr(LevelStatType::MOVED_GB), hdr(LevelStatType::WRITE_AMP),
|
||||
hdr(LevelStatType::READ_MBPS), hdr(LevelStatType::WRITE_MBPS),
|
||||
hdr(LevelStatType::COMP_SEC), hdr(LevelStatType::COMP_CPU_SEC),
|
||||
hdr(LevelStatType::COMP_COUNT), hdr(LevelStatType::AVG_SEC),
|
||||
hdr(LevelStatType::KEY_IN), hdr(LevelStatType::KEY_DROP));
|
||||
group_by.c_str(), hdr(LevelStatType::NUM_FILES),
|
||||
hdr(LevelStatType::SIZE_BYTES), hdr(LevelStatType::SCORE),
|
||||
hdr(LevelStatType::READ_GB), hdr(LevelStatType::RN_GB),
|
||||
hdr(LevelStatType::RNP1_GB), hdr(LevelStatType::WRITE_GB),
|
||||
hdr(LevelStatType::W_NEW_GB), hdr(LevelStatType::MOVED_GB),
|
||||
hdr(LevelStatType::WRITE_AMP), hdr(LevelStatType::READ_MBPS),
|
||||
hdr(LevelStatType::WRITE_MBPS), hdr(LevelStatType::COMP_SEC),
|
||||
hdr(LevelStatType::COMP_CPU_SEC), hdr(LevelStatType::COMP_COUNT),
|
||||
hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
|
||||
hdr(LevelStatType::KEY_DROP));
|
||||
|
||||
written_size += line_size;
|
||||
snprintf(buf + written_size, len - written_size, "%s\n",
|
||||
@@ -1152,6 +1156,20 @@ void InternalStats::DumpCFMapStats(
|
||||
(*levels_stats)[-1] = sum_stats; // -1 is for the Sum level
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFMapStatsByPriority(
|
||||
std::map<int, std::map<LevelStatType, double>>* priorities_stats) {
|
||||
for (size_t priority = 0; priority < comp_stats_by_pri_.size(); priority++) {
|
||||
if (comp_stats_by_pri_[priority].micros > 0) {
|
||||
std::map<LevelStatType, double> priority_stats;
|
||||
PrepareLevelStats(&priority_stats, 0 /* num_files */,
|
||||
0 /* being_compacted */, 0 /* total_file_size */,
|
||||
0 /* compaction_score */, 0 /* w_amp */,
|
||||
comp_stats_by_pri_[priority]);
|
||||
(*priorities_stats)[static_cast<int>(priority)] = priority_stats;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFMapStatsIOStalls(
|
||||
std::map<std::string, std::string>* cf_stats) {
|
||||
(*cf_stats)["io_stalls.level0_slowdown"] =
|
||||
@@ -1192,7 +1210,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
|
||||
char buf[2000];
|
||||
// Per-ColumnFamily stats
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName());
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Level");
|
||||
value->append(buf);
|
||||
|
||||
// Print stats for each level
|
||||
@@ -1238,6 +1256,21 @@ void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Priority");
|
||||
value->append(buf);
|
||||
std::map<int, std::map<LevelStatType, double>> priorities_stats;
|
||||
DumpCFMapStatsByPriority(&priorities_stats);
|
||||
for (size_t priority = 0; priority < comp_stats_by_pri_.size(); ++priority) {
|
||||
if (priorities_stats.find(static_cast<int>(priority)) !=
|
||||
priorities_stats.end()) {
|
||||
PrintLevelStats(
|
||||
buf, sizeof(buf),
|
||||
Env::PriorityToString(static_cast<Env::Priority>(priority)),
|
||||
priorities_stats[static_cast<int>(priority)]);
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
|
||||
double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec;
|
||||
double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
|
||||
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
||||
|
||||
+9
-2
@@ -125,6 +125,7 @@ class InternalStats {
|
||||
cf_stats_value_{},
|
||||
cf_stats_count_{},
|
||||
comp_stats_(num_levels),
|
||||
comp_stats_by_pri_(Env::Priority::TOTAL),
|
||||
file_read_latency_(num_levels),
|
||||
bg_error_count_(0),
|
||||
number_levels_(num_levels),
|
||||
@@ -318,8 +319,10 @@ class InternalStats {
|
||||
started_at_ = env_->NowMicros();
|
||||
}
|
||||
|
||||
void AddCompactionStats(int level, const CompactionStats& stats) {
|
||||
void AddCompactionStats(int level, Env::Priority thread_pri,
|
||||
const CompactionStats& stats) {
|
||||
comp_stats_[level].Add(stats);
|
||||
comp_stats_by_pri_[thread_pri].Add(stats);
|
||||
}
|
||||
|
||||
void IncBytesMoved(int level, uint64_t amount) {
|
||||
@@ -381,6 +384,8 @@ class InternalStats {
|
||||
void DumpCFMapStats(
|
||||
std::map<int, std::map<LevelStatType, double>>* level_stats,
|
||||
CompactionStats* compaction_stats_sum);
|
||||
void DumpCFMapStatsByPriority(
|
||||
std::map<int, std::map<LevelStatType, double>>* priorities_stats);
|
||||
void DumpCFMapStatsIOStalls(std::map<std::string, std::string>* cf_stats);
|
||||
void DumpCFStats(std::string* value);
|
||||
void DumpCFStatsNoFileHistogram(std::string* value);
|
||||
@@ -395,6 +400,7 @@ class InternalStats {
|
||||
uint64_t cf_stats_count_[INTERNAL_CF_STATS_ENUM_MAX];
|
||||
// Per-ColumnFamily/level compaction stats
|
||||
std::vector<CompactionStats> comp_stats_;
|
||||
std::vector<CompactionStats> comp_stats_by_pri_;
|
||||
std::vector<HistogramImpl> file_read_latency_;
|
||||
|
||||
// Used to compute per-interval statistics
|
||||
@@ -625,7 +631,8 @@ class InternalStats {
|
||||
void Subtract(const CompactionStats& /*c*/) {}
|
||||
};
|
||||
|
||||
void AddCompactionStats(int /*level*/, const CompactionStats& /*stats*/) {}
|
||||
void AddCompactionStats(int /*level*/, Env::Priority /*thread_pri*/,
|
||||
const CompactionStats& /*stats*/) {}
|
||||
|
||||
void IncBytesMoved(int /*level*/, uint64_t /*amount*/) {}
|
||||
|
||||
|
||||
+1
-1
@@ -492,7 +492,7 @@ TEST_F(EventListenerTest, CompactionReasonLevel) {
|
||||
|
||||
Put("key", "value");
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
|
||||
+240
-30
@@ -24,8 +24,7 @@ Reader::Reporter::~Reporter() {
|
||||
|
||||
Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
std::unique_ptr<SequentialFileReader>&& _file,
|
||||
Reporter* reporter, bool checksum, uint64_t log_num,
|
||||
bool retry_after_eof)
|
||||
Reporter* reporter, bool checksum, uint64_t log_num)
|
||||
: info_log_(info_log),
|
||||
file_(std::move(_file)),
|
||||
reporter_(reporter),
|
||||
@@ -38,8 +37,7 @@ Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
last_record_offset_(0),
|
||||
end_of_buffer_offset_(0),
|
||||
log_number_(log_num),
|
||||
recycled_(false),
|
||||
retry_after_eof_(retry_after_eof) {}
|
||||
recycled_(false) {}
|
||||
|
||||
Reader::~Reader() {
|
||||
delete[] backing_store_;
|
||||
@@ -207,14 +205,14 @@ void Reader::UnmarkEOF() {
|
||||
if (read_error_) {
|
||||
return;
|
||||
}
|
||||
|
||||
eof_ = false;
|
||||
|
||||
// If retry_after_eof_ is true, we have to proceed to read anyway.
|
||||
if (!retry_after_eof_ && eof_offset_ == 0) {
|
||||
if (eof_offset_ == 0) {
|
||||
return;
|
||||
}
|
||||
UnmarkEOFInternal();
|
||||
}
|
||||
|
||||
void Reader::UnmarkEOFInternal() {
|
||||
// If the EOF was in the middle of a block (a partial block was read) we have
|
||||
// to read the rest of the block as ReadPhysicalRecord can only read full
|
||||
// blocks and expects the file position indicator to be aligned to the start
|
||||
@@ -292,12 +290,8 @@ bool Reader::ReadMore(size_t* drop_size, int *error) {
|
||||
} else if (buffer_.size() < static_cast<size_t>(kBlockSize)) {
|
||||
eof_ = true;
|
||||
eof_offset_ = buffer_.size();
|
||||
TEST_SYNC_POINT("LogReader::ReadMore:FirstEOF");
|
||||
}
|
||||
return true;
|
||||
} else if (retry_after_eof_ && !read_error_) {
|
||||
UnmarkEOF();
|
||||
return !read_error_;
|
||||
} else {
|
||||
// Note that if buffer_ is non-empty, we have a truncated header at the
|
||||
// end of the file, which can be caused by the writer crashing in the
|
||||
@@ -355,24 +349,16 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
}
|
||||
}
|
||||
if (header_size + length > buffer_.size()) {
|
||||
if (!retry_after_eof_) {
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
if (!eof_) {
|
||||
return kBadRecordLen;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length|
|
||||
// bytes of payload, assume the writer died in the middle of writing the
|
||||
// record. Don't report a corruption unless requested.
|
||||
if (*drop_size) {
|
||||
return kBadHeader;
|
||||
}
|
||||
} else {
|
||||
int r = kEof;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
if (!eof_) {
|
||||
return kBadRecordLen;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length|
|
||||
// bytes of payload, assume the writer died in the middle of writing the
|
||||
// record. Don't report a corruption unless requested.
|
||||
if (*drop_size) {
|
||||
return kBadHeader;
|
||||
}
|
||||
return kEof;
|
||||
}
|
||||
@@ -409,5 +395,229 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
}
|
||||
}
|
||||
|
||||
bool FragmentBufferedReader::ReadRecord(Slice* record, std::string* scratch,
|
||||
WALRecoveryMode /*unused*/) {
|
||||
assert(record != nullptr);
|
||||
assert(scratch != nullptr);
|
||||
record->clear();
|
||||
scratch->clear();
|
||||
|
||||
uint64_t prospective_record_offset = 0;
|
||||
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
|
||||
size_t drop_size = 0;
|
||||
unsigned int fragment_type_or_err = 0; // Initialize to make compiler happy
|
||||
Slice fragment;
|
||||
while (TryReadFragment(&fragment, &drop_size, &fragment_type_or_err)) {
|
||||
switch (fragment_type_or_err) {
|
||||
case kFullType:
|
||||
case kRecyclableFullType:
|
||||
if (in_fragmented_record_ && !fragments_.empty()) {
|
||||
ReportCorruption(fragments_.size(), "partial record without end(1)");
|
||||
}
|
||||
fragments_.clear();
|
||||
*record = fragment;
|
||||
prospective_record_offset = physical_record_offset;
|
||||
last_record_offset_ = prospective_record_offset;
|
||||
in_fragmented_record_ = false;
|
||||
return true;
|
||||
|
||||
case kFirstType:
|
||||
case kRecyclableFirstType:
|
||||
if (in_fragmented_record_ || !fragments_.empty()) {
|
||||
ReportCorruption(fragments_.size(), "partial record without end(2)");
|
||||
}
|
||||
prospective_record_offset = physical_record_offset;
|
||||
fragments_.assign(fragment.data(), fragment.size());
|
||||
in_fragmented_record_ = true;
|
||||
break;
|
||||
|
||||
case kMiddleType:
|
||||
case kRecyclableMiddleType:
|
||||
if (!in_fragmented_record_) {
|
||||
ReportCorruption(fragment.size(),
|
||||
"missing start of fragmented record(1)");
|
||||
} else {
|
||||
fragments_.append(fragment.data(), fragment.size());
|
||||
}
|
||||
break;
|
||||
|
||||
case kLastType:
|
||||
case kRecyclableLastType:
|
||||
if (!in_fragmented_record_) {
|
||||
ReportCorruption(fragment.size(),
|
||||
"missing start of fragmented record(2)");
|
||||
} else {
|
||||
fragments_.append(fragment.data(), fragment.size());
|
||||
scratch->assign(fragments_.data(), fragments_.size());
|
||||
fragments_.clear();
|
||||
*record = Slice(*scratch);
|
||||
last_record_offset_ = prospective_record_offset;
|
||||
in_fragmented_record_ = false;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case kBadHeader:
|
||||
case kBadRecord:
|
||||
case kEof:
|
||||
case kOldRecord:
|
||||
if (in_fragmented_record_) {
|
||||
ReportCorruption(fragments_.size(), "error in middle of record");
|
||||
in_fragmented_record_ = false;
|
||||
fragments_.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case kBadRecordChecksum:
|
||||
if (recycled_) {
|
||||
fragments_.clear();
|
||||
return false;
|
||||
}
|
||||
ReportCorruption(drop_size, "checksum mismatch");
|
||||
if (in_fragmented_record_) {
|
||||
ReportCorruption(fragments_.size(), "error in middle of record");
|
||||
in_fragmented_record_ = false;
|
||||
fragments_.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "unknown record type %u",
|
||||
fragment_type_or_err);
|
||||
ReportCorruption(
|
||||
fragment.size() + (in_fragmented_record_ ? fragments_.size() : 0),
|
||||
buf);
|
||||
in_fragmented_record_ = false;
|
||||
fragments_.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FragmentBufferedReader::UnmarkEOF() {
|
||||
if (read_error_) {
|
||||
return;
|
||||
}
|
||||
eof_ = false;
|
||||
UnmarkEOFInternal();
|
||||
}
|
||||
|
||||
bool FragmentBufferedReader::TryReadMore(size_t* drop_size, int* error) {
|
||||
if (!eof_ && !read_error_) {
|
||||
// Last read was a full read, so this is a trailer to skip
|
||||
buffer_.clear();
|
||||
Status status = file_->Read(kBlockSize, &buffer_, backing_store_);
|
||||
end_of_buffer_offset_ += buffer_.size();
|
||||
if (!status.ok()) {
|
||||
buffer_.clear();
|
||||
ReportDrop(kBlockSize, status);
|
||||
read_error_ = true;
|
||||
*error = kEof;
|
||||
return false;
|
||||
} else if (buffer_.size() < static_cast<size_t>(kBlockSize)) {
|
||||
eof_ = true;
|
||||
eof_offset_ = buffer_.size();
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"FragmentBufferedLogReader::TryReadMore:FirstEOF", nullptr);
|
||||
}
|
||||
return true;
|
||||
} else if (!read_error_) {
|
||||
UnmarkEOF();
|
||||
}
|
||||
if (!read_error_) {
|
||||
return true;
|
||||
}
|
||||
*error = kEof;
|
||||
*drop_size = buffer_.size();
|
||||
if (buffer_.size() > 0) {
|
||||
*error = kBadHeader;
|
||||
}
|
||||
buffer_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// return true if the caller should process the fragment_type_or_err.
|
||||
bool FragmentBufferedReader::TryReadFragment(
|
||||
Slice* fragment, size_t* drop_size, unsigned int* fragment_type_or_err) {
|
||||
assert(fragment != nullptr);
|
||||
assert(drop_size != nullptr);
|
||||
assert(fragment_type_or_err != nullptr);
|
||||
|
||||
while (buffer_.size() < static_cast<size_t>(kHeaderSize)) {
|
||||
size_t old_size = buffer_.size();
|
||||
int error = kEof;
|
||||
if (!TryReadMore(drop_size, &error)) {
|
||||
*fragment_type_or_err = error;
|
||||
return false;
|
||||
} else if (old_size == buffer_.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const char* header = buffer_.data();
|
||||
const uint32_t a = static_cast<uint32_t>(header[4]) & 0xff;
|
||||
const uint32_t b = static_cast<uint32_t>(header[5]) & 0xff;
|
||||
const unsigned int type = header[6];
|
||||
const uint32_t length = a | (b << 8);
|
||||
int header_size = kHeaderSize;
|
||||
if (type >= kRecyclableFullType && type <= kRecyclableLastType) {
|
||||
if (end_of_buffer_offset_ - buffer_.size() == 0) {
|
||||
recycled_ = true;
|
||||
}
|
||||
header_size = kRecyclableHeaderSize;
|
||||
while (buffer_.size() < static_cast<size_t>(kRecyclableHeaderSize)) {
|
||||
size_t old_size = buffer_.size();
|
||||
int error = kEof;
|
||||
if (!TryReadMore(drop_size, &error)) {
|
||||
*fragment_type_or_err = error;
|
||||
return false;
|
||||
} else if (old_size == buffer_.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const uint32_t log_num = DecodeFixed32(header + 7);
|
||||
if (log_num != log_number_) {
|
||||
*fragment_type_or_err = kOldRecord;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (header_size + length > buffer_.size()) {
|
||||
size_t old_size = buffer_.size();
|
||||
int error = kEof;
|
||||
if (!TryReadMore(drop_size, &error)) {
|
||||
*fragment_type_or_err = error;
|
||||
return false;
|
||||
} else if (old_size == buffer_.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == kZeroType && length == 0) {
|
||||
buffer_.clear();
|
||||
*fragment_type_or_err = kBadRecord;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checksum_) {
|
||||
uint32_t expected_crc = crc32c::Unmask(DecodeFixed32(header));
|
||||
uint32_t actual_crc = crc32c::Value(header + 6, length + header_size - 6);
|
||||
if (actual_crc != expected_crc) {
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
*fragment_type_or_err = kBadRecordChecksum;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
buffer_.remove_prefix(header_size + length);
|
||||
|
||||
*fragment = Slice(header + header_size, length);
|
||||
*fragment_type_or_err = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace log
|
||||
} // namespace rocksdb
|
||||
+48
-13
@@ -20,7 +20,6 @@ namespace rocksdb {
|
||||
|
||||
class SequentialFileReader;
|
||||
class Logger;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace log {
|
||||
|
||||
@@ -53,18 +52,18 @@ class Reader {
|
||||
Reader(std::shared_ptr<Logger> info_log,
|
||||
// @lint-ignore TXT2 T25377293 Grandfathered in
|
||||
std::unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
|
||||
bool checksum, uint64_t log_num, bool retry_after_eof);
|
||||
bool checksum, uint64_t log_num);
|
||||
|
||||
~Reader();
|
||||
virtual ~Reader();
|
||||
|
||||
// Read the next record into *record. Returns true if read
|
||||
// successfully, false if we hit end of the input. May use
|
||||
// "*scratch" as temporary storage. The contents filled in *record
|
||||
// will only be valid until the next mutating operation on this
|
||||
// reader or the next mutation to *scratch.
|
||||
bool ReadRecord(Slice* record, std::string* scratch,
|
||||
WALRecoveryMode wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords);
|
||||
virtual bool ReadRecord(Slice* record, std::string* scratch,
|
||||
WALRecoveryMode wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords);
|
||||
|
||||
// Returns the physical offset of the last record returned by ReadRecord.
|
||||
//
|
||||
@@ -76,21 +75,30 @@ class Reader {
|
||||
return eof_;
|
||||
}
|
||||
|
||||
// returns true if the reader has encountered read error.
|
||||
bool hasReadError() const { return read_error_; }
|
||||
|
||||
// when we know more data has been written to the file. we can use this
|
||||
// function to force the reader to look again in the file.
|
||||
// Also aligns the file position indicator to the start of the next block
|
||||
// by reading the rest of the data from the EOF position to the end of the
|
||||
// block that was partially read.
|
||||
void UnmarkEOF();
|
||||
virtual void UnmarkEOF();
|
||||
|
||||
SequentialFileReader* file() { return file_.get(); }
|
||||
|
||||
private:
|
||||
Reporter* GetReporter() const { return reporter_; }
|
||||
|
||||
uint64_t GetLogNumber() const { return log_number_; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
const std::unique_ptr<SequentialFileReader> file_;
|
||||
Reporter* const reporter_;
|
||||
bool const checksum_;
|
||||
char* const backing_store_;
|
||||
|
||||
// Internal state variables used for reading records
|
||||
Slice buffer_;
|
||||
bool eof_; // Last Read() indicated EOF by returning < kBlockSize
|
||||
bool read_error_; // Error occurred while reading from file
|
||||
@@ -110,11 +118,6 @@ class Reader {
|
||||
// Whether this is a recycled log file
|
||||
bool recycled_;
|
||||
|
||||
// Whether retry after encountering EOF
|
||||
// TODO (yanqin) add support for retry policy, e.g. sleep, max retry limit,
|
||||
// etc.
|
||||
const bool retry_after_eof_;
|
||||
|
||||
// Extend record types with the following special values
|
||||
enum {
|
||||
kEof = kMaxRecordType + 1,
|
||||
@@ -139,15 +142,47 @@ class Reader {
|
||||
// Read some more
|
||||
bool ReadMore(size_t* drop_size, int *error);
|
||||
|
||||
void UnmarkEOFInternal();
|
||||
|
||||
// Reports dropped bytes to the reporter.
|
||||
// buffer_ must be updated to remove the dropped bytes prior to invocation.
|
||||
void ReportCorruption(size_t bytes, const char* reason);
|
||||
void ReportDrop(size_t bytes, const Status& reason);
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
Reader(const Reader&);
|
||||
void operator=(const Reader&);
|
||||
};
|
||||
|
||||
class FragmentBufferedReader : public Reader {
|
||||
public:
|
||||
FragmentBufferedReader(std::shared_ptr<Logger> info_log,
|
||||
// @lint-ignore TXT2 T25377293 Grandfathered in
|
||||
std::unique_ptr<SequentialFileReader>&& _file,
|
||||
Reporter* reporter, bool checksum, uint64_t log_num)
|
||||
: Reader(info_log, std::move(_file), reporter, checksum, log_num),
|
||||
fragments_(),
|
||||
in_fragmented_record_(false) {}
|
||||
~FragmentBufferedReader() override {}
|
||||
bool ReadRecord(Slice* record, std::string* scratch,
|
||||
WALRecoveryMode wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords) override;
|
||||
void UnmarkEOF() override;
|
||||
|
||||
private:
|
||||
std::string fragments_;
|
||||
bool in_fragmented_record_;
|
||||
|
||||
bool TryReadFragment(Slice* result, size_t* drop_size,
|
||||
unsigned int* fragment_type_or_err);
|
||||
|
||||
bool TryReadMore(size_t* drop_size, int* error);
|
||||
|
||||
// No copy allowed
|
||||
FragmentBufferedReader(const FragmentBufferedReader&);
|
||||
void operator=(const FragmentBufferedReader&);
|
||||
};
|
||||
|
||||
} // namespace log
|
||||
} // namespace rocksdb
|
||||
+170
-82
@@ -43,7 +43,10 @@ static std::string RandomSkewedString(int i, Random* rnd) {
|
||||
return BigString(NumberString(i), rnd->Skewed(17));
|
||||
}
|
||||
|
||||
class LogTest : public ::testing::TestWithParam<int> {
|
||||
// Param type is tuple<int, bool>
|
||||
// get<0>(tuple): non-zero if recycling log, zero if regular log
|
||||
// get<1>(tuple): true if allow retry after read EOF, false otherwise
|
||||
class LogTest : public ::testing::TestWithParam<std::tuple<int, bool>> {
|
||||
private:
|
||||
class StringSource : public SequentialFile {
|
||||
public:
|
||||
@@ -53,16 +56,20 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
bool force_eof_;
|
||||
size_t force_eof_position_;
|
||||
bool returned_partial_;
|
||||
explicit StringSource(Slice& contents) :
|
||||
contents_(contents),
|
||||
force_error_(false),
|
||||
force_error_position_(0),
|
||||
force_eof_(false),
|
||||
force_eof_position_(0),
|
||||
returned_partial_(false) { }
|
||||
bool fail_after_read_partial_;
|
||||
explicit StringSource(Slice& contents, bool fail_after_read_partial)
|
||||
: contents_(contents),
|
||||
force_error_(false),
|
||||
force_error_position_(0),
|
||||
force_eof_(false),
|
||||
force_eof_position_(0),
|
||||
returned_partial_(false),
|
||||
fail_after_read_partial_(fail_after_read_partial) {}
|
||||
|
||||
Status Read(size_t n, Slice* result, char* scratch) override {
|
||||
EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error";
|
||||
if (fail_after_read_partial_) {
|
||||
EXPECT_TRUE(!returned_partial_) << "must not Read() after eof/error";
|
||||
}
|
||||
|
||||
if (force_error_) {
|
||||
if (force_error_position_ >= n) {
|
||||
@@ -139,7 +146,7 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
}
|
||||
|
||||
void reset_source_contents() {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_->file()->file());
|
||||
assert(src);
|
||||
src->contents_ = dest_contents();
|
||||
}
|
||||
@@ -149,11 +156,10 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
std::unique_ptr<SequentialFileReader> source_holder_;
|
||||
ReportCollector report_;
|
||||
Writer writer_;
|
||||
Reader reader_;
|
||||
std::unique_ptr<Reader> reader_;
|
||||
|
||||
// Record metadata for testing initial offset functionality
|
||||
static size_t initial_offset_record_sizes_[];
|
||||
uint64_t initial_offset_last_record_offsets_[4];
|
||||
protected:
|
||||
bool allow_retry_read_;
|
||||
|
||||
public:
|
||||
LogTest()
|
||||
@@ -161,18 +167,18 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
dest_holder_(test::GetWritableFileWriter(
|
||||
new test::StringSink(&reader_contents_), "" /* don't care */)),
|
||||
source_holder_(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* file name */)),
|
||||
writer_(std::move(dest_holder_), 123, GetParam()),
|
||||
reader_(nullptr, std::move(source_holder_), &report_,
|
||||
true /* checksum */, 123 /* log_number */,
|
||||
false /* retry_after_eof */) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
initial_offset_last_record_offsets_[0] = 0;
|
||||
initial_offset_last_record_offsets_[1] = header_size + 10000;
|
||||
initial_offset_last_record_offsets_[2] = 2 * (header_size + 10000);
|
||||
initial_offset_last_record_offsets_[3] = 2 * (header_size + 10000) +
|
||||
(2 * log::kBlockSize - 1000) +
|
||||
3 * header_size;
|
||||
new StringSource(reader_contents_, !std::get<1>(GetParam())),
|
||||
"" /* file name */)),
|
||||
writer_(std::move(dest_holder_), 123, std::get<0>(GetParam())),
|
||||
allow_retry_read_(std::get<1>(GetParam())) {
|
||||
if (allow_retry_read_) {
|
||||
reader_.reset(new FragmentBufferedReader(
|
||||
nullptr, std::move(source_holder_), &report_, true /* checksum */,
|
||||
123 /* log_number */));
|
||||
} else {
|
||||
reader_.reset(new Reader(nullptr, std::move(source_holder_), &report_,
|
||||
true /* checksum */, 123 /* log_number */));
|
||||
}
|
||||
}
|
||||
|
||||
Slice* get_reader_contents() { return &reader_contents_; }
|
||||
@@ -189,7 +195,9 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords) {
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
if (reader_.ReadRecord(&record, &scratch, wal_recovery_mode)) {
|
||||
bool ret = false;
|
||||
ret = reader_->ReadRecord(&record, &scratch, wal_recovery_mode);
|
||||
if (ret) {
|
||||
return record.ToString();
|
||||
} else {
|
||||
return "EOF";
|
||||
@@ -221,7 +229,7 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
}
|
||||
|
||||
void ForceError(size_t position = 0) {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_->file()->file());
|
||||
src->force_error_ = true;
|
||||
src->force_error_position_ = position;
|
||||
}
|
||||
@@ -235,20 +243,18 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
}
|
||||
|
||||
void ForceEOF(size_t position = 0) {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_->file()->file());
|
||||
src->force_eof_ = true;
|
||||
src->force_eof_position_ = position;
|
||||
}
|
||||
|
||||
void UnmarkEOF() {
|
||||
auto src = dynamic_cast<StringSource*>(reader_.file()->file());
|
||||
auto src = dynamic_cast<StringSource*>(reader_->file()->file());
|
||||
src->returned_partial_ = false;
|
||||
reader_.UnmarkEOF();
|
||||
reader_->UnmarkEOF();
|
||||
}
|
||||
|
||||
bool IsEOF() {
|
||||
return reader_.IsEOF();
|
||||
}
|
||||
bool IsEOF() { return reader_->IsEOF(); }
|
||||
|
||||
// Returns OK iff recorded error message contains "msg"
|
||||
std::string MatchError(const std::string& msg) const {
|
||||
@@ -258,23 +264,8 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
|
||||
void WriteInitialOffsetLog() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
std::string record(initial_offset_record_sizes_[i],
|
||||
static_cast<char>('a' + i));
|
||||
Write(record);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
size_t LogTest::initial_offset_record_sizes_[] =
|
||||
{10000, // Two sizable records in first block
|
||||
10000,
|
||||
2 * log::kBlockSize - 1000, // Span three blocks
|
||||
1};
|
||||
|
||||
TEST_P(LogTest, Empty) { ASSERT_EQ("EOF", Read()); }
|
||||
|
||||
TEST_P(LogTest, ReadWrite) {
|
||||
@@ -312,7 +303,8 @@ TEST_P(LogTest, Fragmentation) {
|
||||
|
||||
TEST_P(LogTest, MarginalTrailer) {
|
||||
// Make a trailer that is exactly the same length as an empty record.
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
int header_size =
|
||||
std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize;
|
||||
const int n = kBlockSize - 2 * header_size;
|
||||
Write(BigString("foo", n));
|
||||
ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes());
|
||||
@@ -326,7 +318,8 @@ TEST_P(LogTest, MarginalTrailer) {
|
||||
|
||||
TEST_P(LogTest, MarginalTrailer2) {
|
||||
// Make a trailer that is exactly the same length as an empty record.
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
int header_size =
|
||||
std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize;
|
||||
const int n = kBlockSize - 2 * header_size;
|
||||
Write(BigString("foo", n));
|
||||
ASSERT_EQ((unsigned int)(kBlockSize - header_size), WrittenBytes());
|
||||
@@ -339,7 +332,8 @@ TEST_P(LogTest, MarginalTrailer2) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, ShortTrailer) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
int header_size =
|
||||
std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize;
|
||||
const int n = kBlockSize - 2 * header_size + 4;
|
||||
Write(BigString("foo", n));
|
||||
ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes());
|
||||
@@ -352,7 +346,8 @@ TEST_P(LogTest, ShortTrailer) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, AlignedEof) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
int header_size =
|
||||
std::get<0>(GetParam()) ? kRecyclableHeaderSize : kHeaderSize;
|
||||
const int n = kBlockSize - 2 * header_size + 4;
|
||||
Write(BigString("foo", n));
|
||||
ASSERT_EQ((unsigned int)(kBlockSize - header_size + 4), WrittenBytes());
|
||||
@@ -403,6 +398,11 @@ TEST_P(LogTest, TruncatedTrailingRecordIsIgnored) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) {
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then truncated trailing record should not
|
||||
// raise an error.
|
||||
return;
|
||||
}
|
||||
Write("foo");
|
||||
ShrinkSize(4); // Drop all payload as well as a header byte
|
||||
ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
|
||||
@@ -412,13 +412,20 @@ TEST_P(LogTest, TruncatedTrailingRecordIsNotIgnored) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, BadLength) {
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then we should not raise an error when the
|
||||
// record length specified in header is longer than data currently
|
||||
// available. It's possible that the body of the record is not written yet.
|
||||
return;
|
||||
}
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize;
|
||||
const int kPayloadSize = kBlockSize - header_size;
|
||||
Write(BigString("bar", kPayloadSize));
|
||||
Write("foo");
|
||||
// Least significant size byte is stored in header[4].
|
||||
IncrementByte(4, 1);
|
||||
if (!GetParam()) {
|
||||
if (!recyclable_log) {
|
||||
ASSERT_EQ("foo", Read());
|
||||
ASSERT_EQ(kBlockSize, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("bad record length"));
|
||||
@@ -428,6 +435,12 @@ TEST_P(LogTest, BadLength) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, BadLengthAtEndIsIgnored) {
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then we should not raise an error when the
|
||||
// record length specified in header is longer than data currently
|
||||
// available. It's possible that the body of the record is not written yet.
|
||||
return;
|
||||
}
|
||||
Write("foo");
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
@@ -436,6 +449,12 @@ TEST_P(LogTest, BadLengthAtEndIsIgnored) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, BadLengthAtEndIsNotIgnored) {
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then we should not raise an error when the
|
||||
// record length specified in header is longer than data currently
|
||||
// available. It's possible that the body of the record is not written yet.
|
||||
return;
|
||||
}
|
||||
Write("foo");
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read(WALRecoveryMode::kAbsoluteConsistency));
|
||||
@@ -447,7 +466,8 @@ TEST_P(LogTest, ChecksumMismatch) {
|
||||
Write("foooooo");
|
||||
IncrementByte(0, 14);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
if (!GetParam()) {
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
if (!recyclable_log) {
|
||||
ASSERT_EQ(14U, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("checksum mismatch"));
|
||||
} else {
|
||||
@@ -458,8 +478,10 @@ TEST_P(LogTest, ChecksumMismatch) {
|
||||
|
||||
TEST_P(LogTest, UnexpectedMiddleType) {
|
||||
Write("foo");
|
||||
SetByte(6, static_cast<char>(GetParam() ? kRecyclableMiddleType : kMiddleType));
|
||||
FixChecksum(0, 3, !!GetParam());
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
SetByte(6, static_cast<char>(recyclable_log ? kRecyclableMiddleType
|
||||
: kMiddleType));
|
||||
FixChecksum(0, 3, !!recyclable_log);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(3U, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("missing start"));
|
||||
@@ -467,8 +489,10 @@ TEST_P(LogTest, UnexpectedMiddleType) {
|
||||
|
||||
TEST_P(LogTest, UnexpectedLastType) {
|
||||
Write("foo");
|
||||
SetByte(6, static_cast<char>(GetParam() ? kRecyclableLastType : kLastType));
|
||||
FixChecksum(0, 3, !!GetParam());
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
SetByte(6,
|
||||
static_cast<char>(recyclable_log ? kRecyclableLastType : kLastType));
|
||||
FixChecksum(0, 3, !!recyclable_log);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(3U, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("missing start"));
|
||||
@@ -477,8 +501,10 @@ TEST_P(LogTest, UnexpectedLastType) {
|
||||
TEST_P(LogTest, UnexpectedFullType) {
|
||||
Write("foo");
|
||||
Write("bar");
|
||||
SetByte(6, static_cast<char>(GetParam() ? kRecyclableFirstType : kFirstType));
|
||||
FixChecksum(0, 3, !!GetParam());
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
SetByte(
|
||||
6, static_cast<char>(recyclable_log ? kRecyclableFirstType : kFirstType));
|
||||
FixChecksum(0, 3, !!recyclable_log);
|
||||
ASSERT_EQ("bar", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(3U, DroppedBytes());
|
||||
@@ -488,8 +514,10 @@ TEST_P(LogTest, UnexpectedFullType) {
|
||||
TEST_P(LogTest, UnexpectedFirstType) {
|
||||
Write("foo");
|
||||
Write(BigString("bar", 100000));
|
||||
SetByte(6, static_cast<char>(GetParam() ? kRecyclableFirstType : kFirstType));
|
||||
FixChecksum(0, 3, !!GetParam());
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
SetByte(
|
||||
6, static_cast<char>(recyclable_log ? kRecyclableFirstType : kFirstType));
|
||||
FixChecksum(0, 3, !!recyclable_log);
|
||||
ASSERT_EQ(BigString("bar", 100000), Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(3U, DroppedBytes());
|
||||
@@ -506,6 +534,11 @@ TEST_P(LogTest, MissingLastIsIgnored) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, MissingLastIsNotIgnored) {
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then truncated trailing record should not
|
||||
// raise an error.
|
||||
return;
|
||||
}
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Remove the LAST block, including header.
|
||||
ShrinkSize(14);
|
||||
@@ -524,6 +557,11 @@ TEST_P(LogTest, PartialLastIsIgnored) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, PartialLastIsNotIgnored) {
|
||||
if (allow_retry_read_) {
|
||||
// If read retry is allowed, then truncated trailing record should not
|
||||
// raise an error.
|
||||
return;
|
||||
}
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Cause a bad record length in the LAST block.
|
||||
ShrinkSize(1);
|
||||
@@ -550,7 +588,8 @@ TEST_P(LogTest, ErrorJoinsRecords) {
|
||||
SetByte(offset, 'x');
|
||||
}
|
||||
|
||||
if (!GetParam()) {
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
if (!recyclable_log) {
|
||||
ASSERT_EQ("correct", Read());
|
||||
ASSERT_EQ("EOF", Read());
|
||||
size_t dropped = DroppedBytes();
|
||||
@@ -564,7 +603,8 @@ TEST_P(LogTest, ErrorJoinsRecords) {
|
||||
TEST_P(LogTest, ClearEofSingleBlock) {
|
||||
Write("foo");
|
||||
Write("bar");
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize;
|
||||
ForceEOF(3 + header_size + 2);
|
||||
ASSERT_EQ("foo", Read());
|
||||
UnmarkEOF();
|
||||
@@ -579,7 +619,8 @@ TEST_P(LogTest, ClearEofSingleBlock) {
|
||||
|
||||
TEST_P(LogTest, ClearEofMultiBlock) {
|
||||
size_t num_full_blocks = 5;
|
||||
int header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
int header_size = recyclable_log ? kRecyclableHeaderSize : kHeaderSize;
|
||||
size_t n = (kBlockSize - header_size) * num_full_blocks + 25;
|
||||
Write(BigString("foo", n));
|
||||
Write(BigString("bar", n));
|
||||
@@ -628,7 +669,8 @@ TEST_P(LogTest, ClearEofError2) {
|
||||
}
|
||||
|
||||
TEST_P(LogTest, Recycle) {
|
||||
if (!GetParam()) {
|
||||
bool recyclable_log = (std::get<0>(GetParam()) != 0);
|
||||
if (!recyclable_log) {
|
||||
return; // test is only valid for recycled logs
|
||||
}
|
||||
Write("foo");
|
||||
@@ -651,7 +693,11 @@ TEST_P(LogTest, Recycle) {
|
||||
ASSERT_EQ("EOF", Read());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(bool, LogTest, ::testing::Values(0, 2));
|
||||
INSTANTIATE_TEST_CASE_P(bool, LogTest,
|
||||
::testing::Values(std::make_tuple(0, false),
|
||||
std::make_tuple(0, true),
|
||||
std::make_tuple(1, false),
|
||||
std::make_tuple(1, true)));
|
||||
|
||||
class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
private:
|
||||
@@ -677,7 +723,7 @@ class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
std::unique_ptr<WritableFileWriter> writer_;
|
||||
std::unique_ptr<SequentialFileReader> reader_;
|
||||
ReportCollector report_;
|
||||
std::unique_ptr<Reader> log_reader_;
|
||||
std::unique_ptr<FragmentBufferedReader> log_reader_;
|
||||
|
||||
public:
|
||||
RetriableLogTest()
|
||||
@@ -716,9 +762,9 @@ class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
if (s.ok()) {
|
||||
reader_.reset(new SequentialFileReader(std::move(seq_file), log_file_));
|
||||
assert(reader_ != nullptr);
|
||||
log_reader_.reset(new Reader(nullptr, std::move(reader_), &report_,
|
||||
true /* checksum */, 123 /* log_number */,
|
||||
true /* retry_after_eof */));
|
||||
log_reader_.reset(new FragmentBufferedReader(
|
||||
nullptr, std::move(reader_), &report_, true /* checksum */,
|
||||
123 /* log_number */));
|
||||
assert(log_reader_ != nullptr);
|
||||
}
|
||||
return s;
|
||||
@@ -738,14 +784,17 @@ class RetriableLogTest : public ::testing::TestWithParam<int> {
|
||||
writer_->Sync(true);
|
||||
}
|
||||
|
||||
std::string Read() {
|
||||
auto wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
bool TryRead(std::string* result) {
|
||||
assert(result != nullptr);
|
||||
result->clear();
|
||||
std::string scratch;
|
||||
Slice record;
|
||||
if (log_reader_->ReadRecord(&record, &scratch, wal_recovery_mode)) {
|
||||
return record.ToString();
|
||||
bool r = log_reader_->ReadRecord(&record, &scratch);
|
||||
if (r) {
|
||||
result->assign(record.data(), record.size());
|
||||
return true;
|
||||
} else {
|
||||
return "Read error";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -754,12 +803,17 @@ TEST_P(RetriableLogTest, TailLog_PartialHeader) {
|
||||
ASSERT_OK(SetupTestEnv());
|
||||
std::vector<int> remaining_bytes_in_last_record;
|
||||
size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
bool eof = false;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RetriableLogTest::TailLog:AfterPart1",
|
||||
"RetriableLogTest::TailLog:BeforeReadRecord"},
|
||||
{"LogReader::ReadMore:FirstEOF",
|
||||
{"FragmentBufferedLogReader::TryReadMore:FirstEOF",
|
||||
"RetriableLogTest::TailLog:BeforePart2"}});
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FragmentBufferedLogReader::TryReadMore:FirstEOF",
|
||||
[&](void* /*arg*/) { eof = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
size_t delta = header_size - 1;
|
||||
@@ -779,23 +833,30 @@ TEST_P(RetriableLogTest, TailLog_PartialHeader) {
|
||||
std::string record;
|
||||
port::Thread log_reader_thread([&]() {
|
||||
TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord");
|
||||
record = Read();
|
||||
while (!TryRead(&record)) {
|
||||
}
|
||||
});
|
||||
log_reader_thread.join();
|
||||
log_writer_thread.join();
|
||||
ASSERT_EQ("foo", record);
|
||||
ASSERT_TRUE(eof);
|
||||
}
|
||||
|
||||
TEST_P(RetriableLogTest, TailLog_FullHeader) {
|
||||
ASSERT_OK(SetupTestEnv());
|
||||
std::vector<int> remaining_bytes_in_last_record;
|
||||
size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
bool eof = false;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"RetriableLogTest::TailLog:AfterPart1",
|
||||
"RetriableLogTest::TailLog:BeforeReadRecord"},
|
||||
{"LogReader::ReadMore:FirstEOF",
|
||||
{"FragmentBufferedLogReader::TryReadMore:FirstEOF",
|
||||
"RetriableLogTest::TailLog:BeforePart2"}});
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FragmentBufferedLogReader::TryReadMore:FirstEOF",
|
||||
[&](void* /*arg*/) { eof = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
size_t delta = header_size + 1;
|
||||
@@ -810,18 +871,45 @@ TEST_P(RetriableLogTest, TailLog_FullHeader) {
|
||||
TEST_SYNC_POINT("RetriableLogTest::TailLog:AfterPart1");
|
||||
TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforePart2");
|
||||
Write(Slice(part2));
|
||||
ASSERT_TRUE(eof);
|
||||
});
|
||||
|
||||
std::string record;
|
||||
port::Thread log_reader_thread([&]() {
|
||||
TEST_SYNC_POINT("RetriableLogTest::TailLog:BeforeReadRecord");
|
||||
record = Read();
|
||||
while (!TryRead(&record)) {
|
||||
}
|
||||
});
|
||||
log_reader_thread.join();
|
||||
log_writer_thread.join();
|
||||
ASSERT_EQ("foo", record);
|
||||
}
|
||||
|
||||
TEST_P(RetriableLogTest, NonBlockingReadFullRecord) {
|
||||
// Clear all sync point callbacks even if this test does not use sync point.
|
||||
// It is necessary, otherwise the execute of this test may hit a sync point
|
||||
// with which a callback is registered. The registered callback may access
|
||||
// some dead variable, causing segfault.
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_OK(SetupTestEnv());
|
||||
size_t header_size = GetParam() ? kRecyclableHeaderSize : kHeaderSize;
|
||||
size_t delta = header_size - 1;
|
||||
size_t old_sz = contents().size();
|
||||
Encode("foo-bar");
|
||||
size_t new_sz = contents().size();
|
||||
std::string part1 = contents().substr(old_sz, delta);
|
||||
std::string part2 =
|
||||
contents().substr(old_sz + delta, new_sz - old_sz - delta);
|
||||
Write(Slice(part1));
|
||||
std::string record;
|
||||
ASSERT_FALSE(TryRead(&record));
|
||||
ASSERT_TRUE(record.empty());
|
||||
Write(Slice(part2));
|
||||
ASSERT_TRUE(TryRead(&record));
|
||||
ASSERT_EQ("foo-bar", record);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(bool, RetriableLogTest, ::testing::Values(0, 2));
|
||||
|
||||
} // namespace log
|
||||
|
||||
+15
-2
@@ -18,7 +18,7 @@
|
||||
namespace rocksdb {
|
||||
namespace log {
|
||||
|
||||
Writer::Writer(unique_ptr<WritableFileWriter>&& dest, uint64_t log_number,
|
||||
Writer::Writer(std::unique_ptr<WritableFileWriter>&& dest, uint64_t log_number,
|
||||
bool recycle_log_files, bool manual_flush)
|
||||
: dest_(std::move(dest)),
|
||||
block_offset_(0),
|
||||
@@ -31,10 +31,23 @@ Writer::Writer(unique_ptr<WritableFileWriter>&& dest, uint64_t log_number,
|
||||
}
|
||||
}
|
||||
|
||||
Writer::~Writer() { WriteBuffer(); }
|
||||
Writer::~Writer() {
|
||||
if (dest_) {
|
||||
WriteBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
Status Writer::WriteBuffer() { return dest_->Flush(); }
|
||||
|
||||
Status Writer::Close() {
|
||||
Status s;
|
||||
if (dest_) {
|
||||
s = dest_->Close();
|
||||
dest_.reset();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Writer::AddRecord(const Slice& slice) {
|
||||
const char* ptr = slice.data();
|
||||
size_t left = slice.size();
|
||||
|
||||
Loaded 100 of 429 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user