mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0915c99f01 | ||
|
|
4f48a2af7b | ||
|
|
aaabdc85d9 | ||
|
|
5929273ea6 | ||
|
|
b0864639e8 | ||
|
|
59d47e8c3e | ||
|
|
b88bb93514 | ||
|
|
944346d0fa | ||
|
|
02fd7ca92a | ||
|
|
0eab26e482 | ||
|
|
25dce8fd7c | ||
|
|
10c141a3b7 | ||
|
|
2c62c227ae | ||
|
|
0bc8750e82 | ||
|
|
f6b3de76e5 | ||
|
|
974dfc3de6 | ||
|
|
cad5db1c3e | ||
|
|
bd698e4f55 | ||
|
|
c4bc30e12d | ||
|
|
f34782a67d | ||
|
|
0672a6db64 | ||
|
|
967a2d953f | ||
|
|
cb1142e00d | ||
|
|
e6e8b9e871 | ||
|
|
d305f13e21 | ||
|
|
8e309b35bb | ||
|
|
8aa99fc71e | ||
|
|
4b86fe1123 | ||
|
|
931876e86e | ||
|
|
7e5b04d04f | ||
|
|
6b64aed4c0 | ||
|
|
d87cffaea4 | ||
|
|
73f65b457e | ||
|
|
86623a7153 | ||
|
|
f8b5ef85ec | ||
|
|
b7f1b3e51c | ||
|
|
d2b4d42d4b | ||
|
|
1dd7873e08 | ||
|
|
76c117b24b | ||
|
|
d4b7fbf0d5 | ||
|
|
894c6d21af | ||
|
|
6733be033e | ||
|
|
cfa585611d | ||
|
|
eff5e076f5 | ||
|
|
687119aeaf | ||
|
|
6a9989381f | ||
|
|
39410bcb3d | ||
|
|
cfd9732f65 | ||
|
|
e5b476f551 | ||
|
|
941bd15aed | ||
|
|
1244abef66 | ||
|
|
f4a378be3e | ||
|
|
6477075f2c |
No files matched your search
+1
-1
@@ -81,7 +81,7 @@ install:
|
||||
sudo apt-get install -y mingw-w64 ;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
|
||||
mkdir cmake-dist && curl -sfSL https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH;
|
||||
mkdir cmake-dist && curl --silent --fail --show-error --location https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == java_test ]]; then
|
||||
java -version && echo "JAVA_HOME=${JAVA_HOME}";
|
||||
|
||||
+7
-1
@@ -1166,9 +1166,15 @@ if(WITH_BENCHMARK_TOOLS)
|
||||
${ROCKSDB_LIB})
|
||||
endif()
|
||||
|
||||
option(WITH_CORE_TOOLS "build with ldb and sst_dump" ON)
|
||||
option(WITH_TOOLS "build with tools" ON)
|
||||
if(WITH_TOOLS)
|
||||
if(WITH_CORE_TOOLS OR WITH_TOOLS)
|
||||
add_subdirectory(tools)
|
||||
add_custom_target(core_tools
|
||||
DEPENDS ${core_tool_deps})
|
||||
endif()
|
||||
|
||||
if(WITH_TOOLS)
|
||||
add_subdirectory(db_stress_tool)
|
||||
add_custom_target(tools
|
||||
DEPENDS ${tool_deps})
|
||||
|
||||
+35
-6
@@ -1,5 +1,19 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.7.3 (03/18/2020)
|
||||
### Bug Fixes
|
||||
* Fix a data race that might cause crash when calling DB::GetCreationTimeOfOldestFile() by a small chance. The bug was introduced in 6.6 Release.
|
||||
|
||||
## 6.7.2 (02/24/2020)
|
||||
### Bug Fixes
|
||||
* Fixed a bug of IO Uring partial result handling introduced in 6.7.0.
|
||||
|
||||
|
||||
## 6.7.1 (02/13/2020)
|
||||
### Bug Fixes
|
||||
* Fixed issue #6316 that can cause a corruption of the MANIFEST file in the middle when writing to it fails due to no disk space.
|
||||
* Batched MultiGet() ignores IO errors while reading data blocks, causing it to potentially continue looking for a key and returning stale results.
|
||||
|
||||
## 6.7.0 (01/21/2020)
|
||||
### Public API Change
|
||||
* Added a rocksdb::FileSystem class in include/rocksdb/file_system.h to encapsulate file creation/read/write operations, and an option DBOptions::file_system to allow a user to pass in an instance of rocksdb::FileSystem. If its a non-null value, this will take precendence over DBOptions::env for file operations. A new API rocksdb::FileSystem::Default() returns a platform default object. The DBOptions::env option and Env::Default() API will continue to be used for threading and other OS related functions, and where DBOptions::file_system is not specified, for file operations. For storage developers who are accustomed to rocksdb::Env, the interface in rocksdb::FileSystem is new and will probably undergo some changes as more storage systems are ported to it from rocksdb::Env. As of now, no env other than Posix has been ported to the new interface.
|
||||
* A new rocksdb::NewSstFileManager() API that allows the caller to pass in separate Env and FileSystem objects.
|
||||
@@ -8,18 +22,33 @@
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug that can cause unnecessary bg thread to be scheduled(#6104).
|
||||
* Fix a bug in which a snapshot read could be affected by a DeleteRange after the snapshot (#6062).
|
||||
* Fix crash caused by concurrent CF iterations and drops(#6147).
|
||||
* Fixed two performance issues related to memtable history trimming. First, a new SuperVersion is now created only if some memtables were actually trimmed. Second, trimming is only scheduled if there is at least one flushed memtable that is kept in memory for the purposes of transaction conflict checking.
|
||||
* Fix a bug in WriteBatchWithIndex::MultiGetFromBatchAndDB, which is called by Transaction::MultiGet, that causes due to stale pointer access when the number of keys is > 32
|
||||
* BlobDB no longer updates the SST to blob file mapping upon failed compactions.
|
||||
* Fixed a bug where BlobDB was comparing the `ColumnFamilyHandle` pointers themselves instead of only the column family IDs when checking whether an API call uses the default column family or not.
|
||||
* Fix a race condition for cfd->log_number_ between manifest switch and memtable switch (PR 6249) when number of column families is greater than 1.
|
||||
* Fix a bug on fractional cascading index when multiple files at the same level contain the same smallest user key, and those user keys are for merge operands. In this case, Get() the exact key may miss some merge operands.
|
||||
* Delcare kHashSearch index type feature-incompatible with index_block_restart_interval larger than 1.
|
||||
* Fixed an issue where the thread pools were not resized upon setting `max_background_jobs` dynamically through the `SetDBOptions` interface.
|
||||
* Fix a bug that can cause write threads to hang when a slowdown/stall happens and there is a mix of writers with WriteOptions::no_slowdown set/unset.
|
||||
* Fixed an issue where an incorrect "number of input records" value was used to compute the "records dropped" statistics for compactions.
|
||||
|
||||
### New Features
|
||||
* It is now possible to enable periodic compactions for the base DB when using BlobDB.
|
||||
* BlobDB now garbage collects non-TTL blobs when `enable_garbage_collection` is set to `true` in `BlobDBOptions`. Garbage collection is performed during compaction: any valid blobs located in the oldest N files (where N is the number of non-TTL blob files multiplied by the value of `BlobDBOptions::garbage_collection_cutoff`) encountered during compaction get relocated to new blob files, and old blob files are dropped once they are no longer needed. Note: we recommend enabling periodic compactions for the base DB when using this feature to deal with the case when some old blob files are kept alive by SSTs that otherwise do not get picked for compaction.
|
||||
* `db_bench` now supports the `garbage_collection_cutoff` option for BlobDB.
|
||||
* MultiGet() can use IO Uring to parallelize read from the same SST file. This featuer is by default disabled. It can be enabled with environment variable ROCKSDB_USE_IO_URING.
|
||||
|
||||
## 6.6.2 (01/13/2020)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where non-L0 compaction input files were not considered to compute the `creation_time` of new compaction outputs.
|
||||
|
||||
## 6.6.1 (01/02/2020)
|
||||
### Bug Fixes
|
||||
* Fix a bug in WriteBatchWithIndex::MultiGetFromBatchAndDB, which is called by Transaction::MultiGet, that causes due to stale pointer access when the number of keys is > 32
|
||||
* Fixed two performance issues related to memtable history trimming. First, a new SuperVersion is now created only if some memtables were actually trimmed. Second, trimming is only scheduled if there is at least one flushed memtable that is kept in memory for the purposes of transaction conflict checking.
|
||||
* BlobDB no longer updates the SST to blob file mapping upon failed compactions.
|
||||
* Fix a bug in which a snapshot read through an iterator could be affected by a DeleteRange after the snapshot (#6062).
|
||||
* Fixed a bug where BlobDB was comparing the `ColumnFamilyHandle` pointers themselves instead of only the column family IDs when checking whether an API call uses the default column family or not.
|
||||
* Delete superversions in BackgroundCallPurge.
|
||||
* Fix use-after-free and double-deleting files in BackgroundCallPurge().
|
||||
|
||||
## 6.6.0 (11/25/2019)
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1128,16 +1128,21 @@ unity_test: db/db_test.o db/db_test_util.o $(TESTHARNESS) $(TOOLLIBOBJECTS) unit
|
||||
rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc
|
||||
build_tools/amalgamate.py -I. -i./include unity.cc -x include/rocksdb/c.h -H rocksdb.h -o rocksdb.cc
|
||||
|
||||
clean: clean-ext-libraries-all clean-rocks
|
||||
clean: clean-ext-libraries-all clean-rocks clean-rocksjava
|
||||
|
||||
clean-not-downloaded: clean-ext-libraries-bin clean-rocks
|
||||
clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-rocksjava
|
||||
|
||||
clean-rocks:
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(LIBRARY) $(SHARED)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
cd java; $(MAKE) clean
|
||||
|
||||
clean-rocksjava:
|
||||
cd java && $(MAKE) clean
|
||||
|
||||
clean-not-downloaded-rocksjava:
|
||||
cd java && $(MAKE) clean-not-downloaded
|
||||
|
||||
clean-ext-libraries-all:
|
||||
rm -rf bzip2* snappy* zlib* lz4* zstd*
|
||||
@@ -1787,8 +1792,8 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= https://downloads.sourceforge.net/project/bzip2
|
||||
SNAPPY_VER ?= 1.1.7
|
||||
SNAPPY_SHA256 ?= 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.2
|
||||
LZ4_SHA256 ?= 658ba6191fa44c92280d4aa2c271b0f4fbc0e34d249578dd05e50e76d0e5efcc
|
||||
@@ -1834,7 +1839,7 @@ endif
|
||||
libz.a:
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz))
|
||||
curl --output zlib-$(ZLIB_VER).tar.gz -L ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
curl --fail --output zlib-$(ZLIB_VER).tar.gz --location ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
endif
|
||||
ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
|
||||
@@ -1848,7 +1853,7 @@ endif
|
||||
libbz2.a:
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz))
|
||||
curl --output bzip2-$(BZIP2_VER).tar.gz -L ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
|
||||
curl --fail --output bzip2-$(BZIP2_VER).tar.gz --location ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
|
||||
endif
|
||||
BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
|
||||
@@ -1862,7 +1867,7 @@ endif
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz))
|
||||
curl --output snappy-$(SNAPPY_VER).tar.gz -L ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
|
||||
curl --fail --output snappy-$(SNAPPY_VER).tar.gz --location ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
|
||||
endif
|
||||
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
|
||||
@@ -1877,7 +1882,7 @@ endif
|
||||
liblz4.a:
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz))
|
||||
curl --output lz4-$(LZ4_VER).tar.gz -L ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
curl --fail --output lz4-$(LZ4_VER).tar.gz --location ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
endif
|
||||
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
|
||||
@@ -1891,7 +1896,7 @@ endif
|
||||
libzstd.a:
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz))
|
||||
curl --output zstd-$(ZSTD_VER).tar.gz -L ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
curl --fail --output zstd-$(ZSTD_VER).tar.gz --location ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
endif
|
||||
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
|
||||
@@ -1961,35 +1966,35 @@ rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 roc
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x64-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x64-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_arm64v8-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x64-musl-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x64-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64lemusl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_ppc64le-musl-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_arm64v8-musl-be --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_IOURING_PRESENT",
|
||||
"-DHAVE_SSE42",
|
||||
"-DLIBURING",
|
||||
"-DNUMA",
|
||||
],
|
||||
),
|
||||
|
||||
@@ -102,3 +102,7 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
|
||||
## Crux
|
||||
[Crux](https://github.com/juxt/crux) is a document database that uses RocksDB for local [EAV](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model) index storage to enable point-in-time bitemporal Datalog queries. The "unbundled" architecture uses Kafka to provide horizontal scalability.
|
||||
|
||||
## Nebula Graph
|
||||
|
||||
[Nebula Graph](https://github.com/vesoft-inc/nebula) is a distributed, scalable, lightning-fast, open source graph database capable of hosting super large scale graphs with dozens of billions of vertices (nodes) and trillions of edges, with milliseconds of latency.
|
||||
+3
-3
@@ -29,7 +29,7 @@ install:
|
||||
- md %THIRDPARTY_HOME%
|
||||
- echo "Building Snappy dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o snappy-1.1.7.zip https://github.com/google/snappy/archive/1.1.7.zip
|
||||
- curl --fail --silent --show-error --output snappy-1.1.7.zip --location https://github.com/google/snappy/archive/1.1.7.zip
|
||||
- unzip snappy-1.1.7.zip
|
||||
- cd snappy-1.1.7
|
||||
- mkdir build
|
||||
@@ -39,7 +39,7 @@ install:
|
||||
- msbuild Snappy.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building LZ4 dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o lz4-1.8.3.zip https://github.com/lz4/lz4/archive/v1.8.3.zip
|
||||
- curl --fail --silent --show-error --output lz4-1.8.3.zip --location https://github.com/lz4/lz4/archive/v1.8.3.zip
|
||||
- unzip lz4-1.8.3.zip
|
||||
- cd lz4-1.8.3\visual\VS2010
|
||||
- ps: $CMD="$Env:DEV_ENV"; & $CMD lz4.sln /upgrade
|
||||
@@ -47,7 +47,7 @@ install:
|
||||
- msbuild lz4.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building ZStd dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl -fsSL -o zstd-1.4.0.zip https://github.com/facebook/zstd/archive/v1.4.0.zip
|
||||
- curl --fail --silent --show-error --output zstd-1.4.0.zip --location https://github.com/facebook/zstd/archive/v1.4.0.zip
|
||||
- unzip zstd-1.4.0.zip
|
||||
- cd zstd-1.4.0\build\VS2010
|
||||
- ps: $CMD="$Env:DEV_ENV"; & $CMD zstd.sln /upgrade
|
||||
|
||||
@@ -55,6 +55,7 @@ ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_IOURING_PRESENT",
|
||||
"-DHAVE_SSE42",
|
||||
"-DLIBURING",
|
||||
"-DNUMA",
|
||||
],
|
||||
),
|
||||
|
||||
@@ -150,18 +150,20 @@ case "$TARGET_OS" in
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic"
|
||||
fi
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt"
|
||||
# check for liburing
|
||||
$CXX $CFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
io_uring_queue_init(1, &ring, 0);
|
||||
return 0;
|
||||
}
|
||||
if test $ROCKSDB_USE_IO_URING; then
|
||||
# check for liburing
|
||||
$CXX $CFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
io_uring_queue_init(1, &ring, 0);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -luring"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_IOURING_PRESENT"
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -luring"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_IOURING_PRESENT"
|
||||
fi
|
||||
fi
|
||||
if test -z "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
|
||||
USE_FOLLY_DISTRIBUTED_MUTEX=1
|
||||
|
||||
@@ -378,7 +378,7 @@ function send_to_ods {
|
||||
echo >&2 "ERROR: Key $key doesn't have a value."
|
||||
return
|
||||
fi
|
||||
curl -s "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build$git_br&key=$key&value=$value" \
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build$git_br&key=$key&value=$value" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -880,7 +880,7 @@ run_regression()
|
||||
|
||||
# parameters: $1 -- key, $2 -- value
|
||||
function send_size_to_ods {
|
||||
curl -s "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=rocksdb.build_size.$1&value=$2" \
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=rocksdb.build_size.$1&value=$2" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -671,8 +671,8 @@ rocksdb_t* rocksdb_open_column_families(
|
||||
const rocksdb_options_t* db_options,
|
||||
const char* name,
|
||||
int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles,
|
||||
char** errptr) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -703,8 +703,8 @@ rocksdb_t* rocksdb_open_for_read_only_column_families(
|
||||
const rocksdb_options_t* db_options,
|
||||
const char* name,
|
||||
int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles,
|
||||
unsigned char error_if_log_file_exist,
|
||||
char** errptr) {
|
||||
@@ -735,8 +735,8 @@ rocksdb_t* rocksdb_open_for_read_only_column_families(
|
||||
rocksdb_t* rocksdb_open_as_secondary_column_families(
|
||||
const rocksdb_options_t* db_options, const char* name,
|
||||
const char* secondary_path, int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* 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) {
|
||||
@@ -2736,6 +2736,12 @@ void rocksdb_options_set_ratelimiter(rocksdb_options_t *opt, rocksdb_ratelimiter
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_atomic_flush(
|
||||
rocksdb_options_t *opt,
|
||||
unsigned char atomic_flush) {
|
||||
opt->rep.atomic_flush = atomic_flush;
|
||||
}
|
||||
|
||||
rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us,
|
||||
@@ -3837,8 +3843,8 @@ rocksdb_transactiondb_t* rocksdb_transactiondb_open(
|
||||
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,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* 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++) {
|
||||
@@ -4215,8 +4221,8 @@ rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(
|
||||
rocksdb_optimistictransactiondb_t*
|
||||
rocksdb_optimistictransactiondb_open_column_families(
|
||||
const rocksdb_options_t* db_options, const char* name,
|
||||
int num_column_families, const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* 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++) {
|
||||
|
||||
+5
-5
@@ -160,11 +160,6 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
return Status::InvalidArgument(
|
||||
"Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
if (cf_options.max_successive_merges != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"max_successive_merges > 0 is incompatible "
|
||||
"with concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1252,6 +1247,11 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
if (s.ok() && db_options.allow_concurrent_memtable_write) {
|
||||
s = CheckConcurrentWritesSupported(cf_options);
|
||||
}
|
||||
if (s.ok() && db_options.unordered_write &&
|
||||
cf_options.max_successive_merges != 0) {
|
||||
s = Status::InvalidArgument(
|
||||
"max_successive_merges > 0 is incompatible with unordered_write");
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = CheckCFPathsSupported(db_options, cf_options);
|
||||
}
|
||||
|
||||
@@ -545,11 +545,13 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime() const {
|
||||
uint64_t min_oldest_ancester_time = port::kMaxUint64;
|
||||
for (const auto& file : inputs_[0].files) {
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time != 0) {
|
||||
min_oldest_ancester_time =
|
||||
std::min(min_oldest_ancester_time, oldest_ancester_time);
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time != 0) {
|
||||
min_oldest_ancester_time =
|
||||
std::min(min_oldest_ancester_time, oldest_ancester_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
return min_oldest_ancester_time;
|
||||
|
||||
@@ -144,7 +144,6 @@ struct CompactionJob::SubcompactionState {
|
||||
|
||||
// State during the subcompaction
|
||||
uint64_t total_bytes;
|
||||
uint64_t num_input_records;
|
||||
uint64_t num_output_records;
|
||||
CompactionJobStats compaction_job_stats;
|
||||
uint64_t approx_size;
|
||||
@@ -165,7 +164,6 @@ struct CompactionJob::SubcompactionState {
|
||||
builder(nullptr),
|
||||
current_output_file_size(0),
|
||||
total_bytes(0),
|
||||
num_input_records(0),
|
||||
num_output_records(0),
|
||||
approx_size(size),
|
||||
grandparent_index(0),
|
||||
@@ -186,7 +184,6 @@ struct CompactionJob::SubcompactionState {
|
||||
builder = std::move(o.builder);
|
||||
current_output_file_size = std::move(o.current_output_file_size);
|
||||
total_bytes = std::move(o.total_bytes);
|
||||
num_input_records = std::move(o.num_input_records);
|
||||
num_output_records = std::move(o.num_output_records);
|
||||
compaction_job_stats = std::move(o.compaction_job_stats);
|
||||
approx_size = std::move(o.approx_size);
|
||||
@@ -245,13 +242,11 @@ struct CompactionJob::CompactionState {
|
||||
Status status;
|
||||
|
||||
uint64_t total_bytes;
|
||||
uint64_t num_input_records;
|
||||
uint64_t num_output_records;
|
||||
|
||||
explicit CompactionState(Compaction* c)
|
||||
: compaction(c),
|
||||
total_bytes(0),
|
||||
num_input_records(0),
|
||||
num_output_records(0) {}
|
||||
|
||||
size_t NumOutputFiles() {
|
||||
@@ -289,7 +284,6 @@ struct CompactionJob::CompactionState {
|
||||
void CompactionJob::AggregateStatistics() {
|
||||
for (SubcompactionState& sc : compact_->sub_compact_states) {
|
||||
compact_->total_bytes += sc.total_bytes;
|
||||
compact_->num_input_records += sc.num_input_records;
|
||||
compact_->num_output_records += sc.num_output_records;
|
||||
}
|
||||
if (compaction_job_stats_) {
|
||||
@@ -770,12 +764,12 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "compaction_time_micros" << compaction_stats_.micros
|
||||
<< "compaction_time_cpu_micros" << compaction_stats_.cpu_micros
|
||||
<< "output_level" << compact_->compaction->output_level()
|
||||
<< "num_output_files" << compact_->NumOutputFiles()
|
||||
<< "total_output_size" << compact_->total_bytes << "num_input_records"
|
||||
<< compact_->num_input_records << "num_output_records"
|
||||
<< "compaction_time_micros" << stats.micros
|
||||
<< "compaction_time_cpu_micros" << stats.cpu_micros << "output_level"
|
||||
<< compact_->compaction->output_level() << "num_output_files"
|
||||
<< compact_->NumOutputFiles() << "total_output_size"
|
||||
<< compact_->total_bytes << "num_input_records"
|
||||
<< stats.num_input_records << "num_output_records"
|
||||
<< compact_->num_output_records << "num_subcompactions"
|
||||
<< compact_->sub_compact_states.size() << "output_compression"
|
||||
<< CompressionTypeToString(compact_->compaction->output_compression());
|
||||
@@ -991,7 +985,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
}
|
||||
|
||||
sub_compact->num_input_records = c_iter_stats.num_input_records;
|
||||
sub_compact->compaction_job_stats.num_input_deletion_records =
|
||||
c_iter_stats.num_input_deletion_records;
|
||||
sub_compact->compaction_job_stats.num_corrupt_keys =
|
||||
@@ -1589,6 +1582,8 @@ void CompactionJob::UpdateCompactionStats() {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t num_output_records = 0;
|
||||
|
||||
for (const auto& sub_compact : compact_->sub_compact_states) {
|
||||
size_t num_output_files = sub_compact.outputs.size();
|
||||
if (sub_compact.builder != nullptr) {
|
||||
@@ -1598,13 +1593,16 @@ void CompactionJob::UpdateCompactionStats() {
|
||||
}
|
||||
compaction_stats_.num_output_files += static_cast<int>(num_output_files);
|
||||
|
||||
num_output_records += sub_compact.num_output_records;
|
||||
|
||||
for (const auto& out : sub_compact.outputs) {
|
||||
compaction_stats_.bytes_written += out.meta.fd.file_size;
|
||||
}
|
||||
if (sub_compact.num_input_records > sub_compact.num_output_records) {
|
||||
compaction_stats_.num_dropped_records +=
|
||||
sub_compact.num_input_records - sub_compact.num_output_records;
|
||||
}
|
||||
}
|
||||
|
||||
if (compaction_stats_.num_input_records > num_output_records) {
|
||||
compaction_stats_.num_dropped_records =
|
||||
compaction_stats_.num_input_records - num_output_records;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1632,7 +1630,7 @@ void CompactionJob::UpdateCompactionJobStats(
|
||||
// input information
|
||||
compaction_job_stats_->total_input_bytes =
|
||||
stats.bytes_read_non_output_levels + stats.bytes_read_output_level;
|
||||
compaction_job_stats_->num_input_records = compact_->num_input_records;
|
||||
compaction_job_stats_->num_input_records = stats.num_input_records;
|
||||
compaction_job_stats_->num_input_files =
|
||||
stats.num_input_files_in_non_output_levels +
|
||||
stats.num_input_files_in_output_level;
|
||||
|
||||
+337
-14
@@ -1405,6 +1405,91 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
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(Merge("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(Merge("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(Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> values;
|
||||
|
||||
for (int i = 32; i < 80; ++i) {
|
||||
keys.push_back("key_" + std::to_string(i));
|
||||
}
|
||||
|
||||
values = MultiGet(keys, nullptr);
|
||||
ASSERT_EQ(values.size(), keys.size());
|
||||
for (unsigned int j = 0; j < 48; ++j) {
|
||||
int key = j + 32;
|
||||
std::string value;
|
||||
value.append("val_l2_" + std::to_string(key));
|
||||
if (key % 3 == 0) {
|
||||
value.append(",");
|
||||
value.append("val_l1_" + std::to_string(key));
|
||||
}
|
||||
if (key % 5 == 0) {
|
||||
value.append(",");
|
||||
value.append("val_l0_" + std::to_string(key));
|
||||
}
|
||||
if (key % 9 == 0) {
|
||||
value.append(",");
|
||||
value.append("val_mem_" + std::to_string(key));
|
||||
}
|
||||
ASSERT_EQ(values[j], value);
|
||||
}
|
||||
}
|
||||
|
||||
// Test class for batched MultiGet with prefix extractor
|
||||
// Param bool - If true, use partitioned filters
|
||||
// If false, use full filter block
|
||||
@@ -2011,6 +2096,90 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
// We cannot resize a PinnableSlice vector, so just set initial size to
|
||||
// largest we think we will need
|
||||
std::vector<PinnableSlice> values(10);
|
||||
std::vector<Status> statuses;
|
||||
int read_count = 0;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RetrieveMultipleBlocks:VerifyChecksum", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
read_count++;
|
||||
if (read_count == 2) {
|
||||
*s = Status::Corruption();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Warm up the cache first
|
||||
key_data.emplace_back(Key(0));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
key_data.emplace_back(Key(50));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
statuses.resize(keys.size());
|
||||
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(0, values[0].ToString()));
|
||||
//ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
ASSERT_EQ(statuses[0], Status::OK());
|
||||
ASSERT_EQ(statuses[1], Status::Corruption());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
// We cannot resize a PinnableSlice vector, so just set initial size to
|
||||
// largest we think we will need
|
||||
std::vector<PinnableSlice> values(10);
|
||||
std::vector<Status> statuses;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::MultiGet:FindTable", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
});
|
||||
// DB open will create table readers unless we reduce the table cache
|
||||
// capacity.
|
||||
// SanitizeOptions will set max_open_files to minimum of 20. Table cache
|
||||
// is allocated with max_open_files - 10 as capacity. So override
|
||||
// max_open_files to 11 so table cache capacity will become 1. This will
|
||||
// prevent file open during DB open and force the file to be opened
|
||||
// during MultiGet
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void *arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(CurrentOptions());
|
||||
|
||||
// Warm up the cache first
|
||||
key_data.emplace_back(Key(0));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
key_data.emplace_back(Key(50));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
statuses.resize(keys.size());
|
||||
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_EQ(statuses[0], Status::IOError());
|
||||
ASSERT_EQ(statuses[1], Status::IOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Params are as follows -
|
||||
@@ -2021,23 +2190,15 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(), ::testing::Bool()));
|
||||
|
||||
class DBBasicTestWithTimestampWithParam
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
class DBBasicTestWithTimestampBase : public DBTestBase {
|
||||
public:
|
||||
DBBasicTestWithTimestampWithParam()
|
||||
: DBTestBase("/db_basic_test_with_timestamp") {}
|
||||
explicit DBBasicTestWithTimestampBase(const std::string& dbname)
|
||||
: DBTestBase(dbname) {}
|
||||
|
||||
protected:
|
||||
class TestComparator : public Comparator {
|
||||
private:
|
||||
const Comparator* cmp_without_ts_;
|
||||
|
||||
class TestComparatorBase : public Comparator {
|
||||
public:
|
||||
explicit TestComparator(size_t ts_sz)
|
||||
: Comparator(ts_sz), cmp_without_ts_(nullptr) {
|
||||
cmp_without_ts_ = BytewiseComparator();
|
||||
}
|
||||
explicit TestComparatorBase(size_t ts_sz) : Comparator(ts_sz) {}
|
||||
|
||||
const char* Name() const override { return "TestComparator"; }
|
||||
|
||||
@@ -2055,13 +2216,15 @@ class DBBasicTestWithTimestampWithParam
|
||||
Slice(b.data() + b.size() - timestamp_size(), timestamp_size()));
|
||||
}
|
||||
|
||||
virtual int CompareImpl(const Slice& a, const Slice& b) const = 0;
|
||||
|
||||
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
|
||||
assert(a.size() >= timestamp_size());
|
||||
assert(b.size() >= timestamp_size());
|
||||
Slice k1 = StripTimestampFromUserKey(a, timestamp_size());
|
||||
Slice k2 = StripTimestampFromUserKey(b, timestamp_size());
|
||||
|
||||
return cmp_without_ts_->Compare(k1, k2);
|
||||
return CompareImpl(k1, k2);
|
||||
}
|
||||
|
||||
int CompareTimestamp(const Slice& ts1, const Slice& ts2) const override {
|
||||
@@ -2107,6 +2270,166 @@ class DBBasicTestWithTimestampWithParam
|
||||
}
|
||||
};
|
||||
|
||||
class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBBasicTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("/db_basic_test_with_timestamp") {}
|
||||
|
||||
protected:
|
||||
class TestComparator : public TestComparatorBase {
|
||||
public:
|
||||
const int kKeyPrefixLength =
|
||||
3; // 3: length of "key" in generated keys ("key" + std::to_string(j))
|
||||
explicit TestComparator(size_t ts_sz) : TestComparatorBase(ts_sz) {}
|
||||
|
||||
int CompareImpl(const Slice& a, const Slice& b) const override {
|
||||
int n1 = atoi(
|
||||
std::string(a.data() + kKeyPrefixLength, a.size() - kKeyPrefixLength)
|
||||
.c_str());
|
||||
int n2 = atoi(
|
||||
std::string(b.data() + kKeyPrefixLength, b.size() - kKeyPrefixLength)
|
||||
.c_str());
|
||||
return (n1 < n2) ? -1 : (n1 > n2) ? 1 : 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// A class which remembers the name of each flushed file.
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFlushedFiles() {
|
||||
std::vector<std::string> result;
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
result = flushed_files_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFlushedFiles() {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
InstrumentedMutex mutex_;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, PutAndGetWithCompaction) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 2;
|
||||
const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
const size_t kSplitPosBase = kNumKeysPerTimestamp / 2;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
std::string tmp;
|
||||
size_t ts_sz = EncodeTimestamp(0, 0, &tmp).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(
|
||||
10 /*bits_per_key*/, false /*use_block_based_builder*/));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_strs(kNumTimestamps);
|
||||
std::vector<std::string> read_ts_strs(kNumTimestamps);
|
||||
std::vector<Slice> write_ts_list;
|
||||
std::vector<Slice> read_ts_list;
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.emplace_back(EncodeTimestamp(i * 2, 0, &write_ts_strs[i]));
|
||||
read_ts_list.emplace_back(EncodeTimestamp(1 + i * 2, 0, &read_ts_strs[i]));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &write_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(Put(cf, "key" + std::to_string(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
wopts));
|
||||
if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 1) {
|
||||
// flush all keys with the same timestamp to two sst files, split at
|
||||
// incremental positions such that lowerlevel[1].smallest.userkey ==
|
||||
// higherlevel[0].largest.userkey
|
||||
ASSERT_OK(Flush(cf));
|
||||
|
||||
// compact files (2 at each level) to a lower level such that all keys
|
||||
// with the same timestamp is at one level, with newer versions at
|
||||
// higher levels.
|
||||
CompactionOptions compact_opt;
|
||||
compact_opt.compression = kNoCompression;
|
||||
db_->CompactFiles(compact_opt, handles_[cf],
|
||||
collector->GetFlushedFiles(),
|
||||
static_cast<int>(kNumTimestamps - i));
|
||||
collector->ClearFlushedFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
ropts.timestamp = &read_ts_list[i];
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ropts, cfh, "key" + std::to_string(j), &value));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestWithTimestampWithParam
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBBasicTestWithTimestampWithParam()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"/db_basic_test_with_timestamp_with_param") {}
|
||||
|
||||
protected:
|
||||
class TestComparator : public TestComparatorBase {
|
||||
private:
|
||||
const Comparator* cmp_without_ts_;
|
||||
|
||||
public:
|
||||
explicit TestComparator(size_t ts_sz)
|
||||
: TestComparatorBase(ts_sz), cmp_without_ts_(nullptr) {
|
||||
cmp_without_ts_ = BytewiseComparator();
|
||||
}
|
||||
|
||||
int CompareImpl(const Slice& a, const Slice& b) const override {
|
||||
return cmp_without_ts_->Compare(a, b);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampWithParam, PutAndGet) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 6;
|
||||
|
||||
+32
-13
@@ -400,6 +400,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// The test will need to be updated if the internal behavior changes.
|
||||
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
@@ -410,31 +411,33 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
values.push_back(RandomString(&rnd, kCDTValueSize));
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Close();
|
||||
|
||||
int update_acc_stats_called = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionStorageInfo::UpdateAccumulatedStats",
|
||||
[&](void* /* arg */) { ++update_acc_stats_called; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Reopen the DB with stats-update disabled
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
options.max_open_files = 20;
|
||||
env_->random_file_open_counter_.store(0);
|
||||
Reopen(options);
|
||||
|
||||
// As stats-update is disabled, we expect a very low number of
|
||||
// random file open.
|
||||
// Note that this number must be changed accordingly if we change
|
||||
// the number of files needed to be opened in the DB::Open process.
|
||||
const int kMaxFileOpenCount = 10;
|
||||
ASSERT_LT(env_->random_file_open_counter_.load(), kMaxFileOpenCount);
|
||||
ASSERT_EQ(update_acc_stats_called, 0);
|
||||
|
||||
// Repeat the reopen process, but this time we enable
|
||||
// stats-update.
|
||||
options.skip_stats_update_on_db_open = false;
|
||||
env_->random_file_open_counter_.store(0);
|
||||
Reopen(options);
|
||||
|
||||
// Since we do a normal stats update on db-open, there
|
||||
// will be more random open files.
|
||||
ASSERT_GT(env_->random_file_open_counter_.load(), kMaxFileOpenCount);
|
||||
ASSERT_GT(update_acc_stats_called, 0);
|
||||
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
@@ -3607,6 +3610,15 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
// Get the first file's creation time. This will be the oldest file in the
|
||||
// DB. Compactions inolving this file's descendents should keep getting
|
||||
// this time.
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
uint64_t oldest_time = level_to_files[0][0].oldest_ancester_time;
|
||||
// Add 1 hour and do another flush.
|
||||
env_->addon_time_.fetch_add(1 * 60 * 60);
|
||||
for (int i = 101; i <= 200; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
@@ -3614,11 +3626,13 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
MoveFilesToLevel(6);
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
|
||||
|
||||
env_->addon_time_.fetch_add(1 * 60 * 60);
|
||||
// Add two L4 files with key ranges: [1 .. 50], [51 .. 150].
|
||||
for (int i = 1; i <= 50; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
Flush();
|
||||
env_->addon_time_.fetch_add(1 * 60 * 60);
|
||||
for (int i = 51; i <= 150; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
@@ -3626,6 +3640,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
MoveFilesToLevel(4);
|
||||
ASSERT_EQ("0,0,0,0,2,0,2", FilesPerLevel());
|
||||
|
||||
env_->addon_time_.fetch_add(1 * 60 * 60);
|
||||
// Add one L1 file with key range: [26, 75].
|
||||
for (int i = 26; i <= 75; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, kValueSize)));
|
||||
@@ -3667,6 +3682,10 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
ASSERT_EQ("1,0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(5, ttl_compactions);
|
||||
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_EQ(oldest_time, level_to_files[6][0].oldest_ancester_time);
|
||||
|
||||
env_->addon_time_.fetch_add(25 * 60 * 60);
|
||||
ASSERT_OK(Put(Key(2), "1"));
|
||||
if (if_restart) {
|
||||
|
||||
+43
-11
@@ -1005,12 +1005,36 @@ Status DBImpl::SetDBOptions(
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (new_options.max_background_compactions >
|
||||
mutable_db_options_.max_background_compactions) {
|
||||
env_->IncBackgroundThreadsIfNeeded(
|
||||
new_options.max_background_compactions, Env::Priority::LOW);
|
||||
const BGJobLimits current_bg_job_limits =
|
||||
GetBGJobLimits(immutable_db_options_.max_background_flushes,
|
||||
mutable_db_options_.max_background_compactions,
|
||||
mutable_db_options_.max_background_jobs,
|
||||
/* parallelize_compactions */ true);
|
||||
const BGJobLimits new_bg_job_limits = GetBGJobLimits(
|
||||
immutable_db_options_.max_background_flushes,
|
||||
new_options.max_background_compactions,
|
||||
new_options.max_background_jobs, /* parallelize_compactions */ true);
|
||||
|
||||
const bool max_flushes_increased =
|
||||
new_bg_job_limits.max_flushes > current_bg_job_limits.max_flushes;
|
||||
const bool max_compactions_increased =
|
||||
new_bg_job_limits.max_compactions >
|
||||
current_bg_job_limits.max_compactions;
|
||||
|
||||
if (max_flushes_increased || max_compactions_increased) {
|
||||
if (max_flushes_increased) {
|
||||
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_flushes,
|
||||
Env::Priority::HIGH);
|
||||
}
|
||||
|
||||
if (max_compactions_increased) {
|
||||
env_->IncBackgroundThreadsIfNeeded(new_bg_job_limits.max_compactions,
|
||||
Env::Priority::LOW);
|
||||
}
|
||||
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (new_options.stats_dump_period_sec !=
|
||||
mutable_db_options_.stats_dump_period_sec) {
|
||||
if (thread_dump_stats_) {
|
||||
@@ -4471,13 +4495,21 @@ Status DBImpl::GetCreationTimeOfOldestFile(uint64_t* creation_time) {
|
||||
if (mutable_db_options_.max_open_files == -1) {
|
||||
uint64_t oldest_time = port::kMaxUint64;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
uint64_t ctime;
|
||||
cfd->current()->GetCreationTimeOfOldestFile(&ctime);
|
||||
if (ctime < oldest_time) {
|
||||
oldest_time = ctime;
|
||||
}
|
||||
if (oldest_time == 0) {
|
||||
break;
|
||||
if (!cfd->IsDropped()) {
|
||||
uint64_t ctime;
|
||||
{
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
Version* version = sv->current;
|
||||
version->GetCreationTimeOfOldestFile(&ctime);
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
}
|
||||
|
||||
if (ctime < oldest_time) {
|
||||
oldest_time = ctime;
|
||||
}
|
||||
if (oldest_time == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*creation_time = oldest_time;
|
||||
|
||||
@@ -150,8 +150,6 @@ TEST_F(DBMergeOperatorTest, MergeErrorOnWrite) {
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
options.max_successive_merges = 3;
|
||||
// allow_concurrent_memtable_write is incompatible with max_successive_merges
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
|
||||
+12
-6
@@ -423,16 +423,22 @@ TEST_F(DBOptionsTest, SetBackgroundJobs) {
|
||||
std::to_string(options.max_background_jobs)}}));
|
||||
}
|
||||
|
||||
ASSERT_EQ(options.max_background_jobs / 4,
|
||||
dbfull()->TEST_BGFlushesAllowed());
|
||||
const int expected_max_flushes = options.max_background_jobs / 4;
|
||||
|
||||
ASSERT_EQ(expected_max_flushes, dbfull()->TEST_BGFlushesAllowed());
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
auto stop_token = dbfull()->TEST_write_controler().GetStopToken();
|
||||
|
||||
ASSERT_EQ(options.max_background_jobs / 4,
|
||||
dbfull()->TEST_BGFlushesAllowed());
|
||||
ASSERT_EQ(3 * options.max_background_jobs / 4,
|
||||
dbfull()->TEST_BGCompactionsAllowed());
|
||||
const int expected_max_compactions = 3 * expected_max_flushes;
|
||||
|
||||
ASSERT_EQ(expected_max_flushes, dbfull()->TEST_BGFlushesAllowed());
|
||||
ASSERT_EQ(expected_max_compactions, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
ASSERT_EQ(expected_max_flushes,
|
||||
env_->GetBackgroundThreads(Env::Priority::HIGH));
|
||||
ASSERT_EQ(expected_max_compactions,
|
||||
env_->GetBackgroundThreads(Env::Priority::LOW));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-2
@@ -153,8 +153,6 @@ TEST_F(DBTest2, MaxSuccessiveMergesChangeWithDBRecovery) {
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.max_successive_merges = 3;
|
||||
// allow_concurrent_memtable_write is incompatible with max_successive_merges
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -4268,6 +4266,40 @@ TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, SameSmallestInSameLevel) {
|
||||
// This test validates fractional casacading logic when several files at one
|
||||
// one level only contains the same user key.
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("key", "1"));
|
||||
ASSERT_OK(Put("key", "2"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "3"));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "4"));
|
||||
Flush();
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, db_->DefaultColumnFamily(), nullptr,
|
||||
nullptr));
|
||||
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "5"));
|
||||
Flush();
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "6"));
|
||||
Flush();
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "7"));
|
||||
Flush();
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "key", "8"));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,4,1", FilesPerLevel());
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
ASSERT_EQ("2,3,4,5,6,7,8", Get("key"));
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_thread.h"
|
||||
@@ -39,6 +40,97 @@ TEST_P(DBWriteTest, SyncAndDisableWAL) {
|
||||
ASSERT_TRUE(dbfull()->Write(write_options, &batch).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, WriteThreadHangOnWriteStall) {
|
||||
Options options = GetOptions();
|
||||
options.level0_stop_writes_trigger = options.level0_slowdown_writes_trigger = 4;
|
||||
std::vector<port::Thread> threads;
|
||||
std::atomic<int> thread_num(0);
|
||||
port::Mutex mutex;
|
||||
port::CondVar cv(&mutex);
|
||||
|
||||
Reopen(options);
|
||||
|
||||
std::function<void()> write_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = false;
|
||||
dbfull()->Put(wo, key, "bar");
|
||||
};
|
||||
std::function<void()> write_no_slowdown_func = [&]() {
|
||||
int a = thread_num.fetch_add(1);
|
||||
std::string key = "foo" + std::to_string(a);
|
||||
WriteOptions wo;
|
||||
wo.no_slowdown = true;
|
||||
dbfull()->Put(wo, key, "bar");
|
||||
};
|
||||
std::function<void(void *)> unblock_main_thread_func = [&](void *) {
|
||||
mutex.Lock();
|
||||
cv.SignalAll();
|
||||
mutex.Unlock();
|
||||
};
|
||||
|
||||
// Create 3 L0 files and schedule 4th without waiting
|
||||
Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
|
||||
Flush();
|
||||
Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
|
||||
Flush();
|
||||
Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
|
||||
Flush();
|
||||
Put("foo" + std::to_string(thread_num.fetch_add(1)), "bar");
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WriteThread::JoinBatchGroup:Start", unblock_main_thread_func);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBWriteTest::WriteThreadHangOnWriteStall:1",
|
||||
"DBImpl::BackgroundCallFlush:start"},
|
||||
{"DBWriteTest::WriteThreadHangOnWriteStall:2",
|
||||
"DBImpl::WriteImpl:BeforeLeaderEnters"},
|
||||
// Make compaction start wait for the write stall to be detected and
|
||||
// implemented by a write group leader
|
||||
{"DBWriteTest::WriteThreadHangOnWriteStall:3",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Schedule creation of 4th L0 file without waiting. This will seal the
|
||||
// memtable and then wait for a sync point before writing the file. We need
|
||||
// to do it this way because SwitchMemtable() needs to enter the
|
||||
// write_thread
|
||||
FlushOptions fopt;
|
||||
fopt.wait = false;
|
||||
dbfull()->Flush(fopt);
|
||||
|
||||
// Create a mix of slowdown/no_slowdown write threads
|
||||
mutex.Lock();
|
||||
// First leader
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
cv.Wait();
|
||||
// Second leader. Will stall writes
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
cv.Wait();
|
||||
threads.emplace_back(write_no_slowdown_func);
|
||||
cv.Wait();
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
cv.Wait();
|
||||
threads.emplace_back(write_no_slowdown_func);
|
||||
cv.Wait();
|
||||
threads.emplace_back(write_slowdown_func);
|
||||
cv.Wait();
|
||||
mutex.Unlock();
|
||||
|
||||
TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:1");
|
||||
dbfull()->TEST_WaitForFlushMemTable(nullptr);
|
||||
// This would have triggered a write stall. Unblock the write group leader
|
||||
TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:2");
|
||||
// The leader is going to create missing newer links. When the leader finishes,
|
||||
// the next leader is going to delay writes and fail writers with no_slowdown
|
||||
|
||||
TEST_SYNC_POINT("DBWriteTest::WriteThreadHangOnWriteStall:3");
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, IOErrorOnWALWritePropagateToWriteThreadFollower) {
|
||||
constexpr int kNumThreads = 5;
|
||||
std::unique_ptr<FaultInjectionTestEnv> mock_env(
|
||||
@@ -185,6 +277,41 @@ TEST_P(DBWriteTest, LockWalInEffect) {
|
||||
ASSERT_OK(dbfull()->UnlockWAL());
|
||||
}
|
||||
|
||||
TEST_P(DBWriteTest, ConcurrentlyDisabledWAL) {
|
||||
Options options = GetOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->set_stats_level(StatsLevel::kAll);
|
||||
Reopen(options);
|
||||
std::string wal_key_prefix = "WAL_KEY_";
|
||||
std::string no_wal_key_prefix = "K_";
|
||||
// 100 KB value each for NO-WAL operation
|
||||
std::string no_wal_value(1024 * 100, 'X');
|
||||
// 1B value each for WAL operation
|
||||
std::string wal_value = "0";
|
||||
std::thread threads[10];
|
||||
for (int t = 0; t < 10; t++) {
|
||||
threads[t] = std::thread([t, wal_key_prefix, wal_value, no_wal_key_prefix, no_wal_value, this] {
|
||||
for(int i = 0; i < 10; i++) {
|
||||
rocksdb::WriteOptions write_option_disable;
|
||||
write_option_disable.disableWAL = true;
|
||||
rocksdb::WriteOptions write_option_default;
|
||||
std::string no_wal_key = no_wal_key_prefix + std::to_string(t) + "_" + std::to_string(i);
|
||||
this->Put(no_wal_key, no_wal_value, write_option_disable);
|
||||
std::string wal_key = wal_key_prefix + std::to_string(i) + "_" + std::to_string(i);
|
||||
this->Put(wal_key, wal_value, write_option_default);
|
||||
dbfull()->SyncWAL();
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
for (auto& t: threads) {
|
||||
t.join();
|
||||
}
|
||||
uint64_t bytes_num = options.statistics->getTickerCount(rocksdb::Tickers::WAL_FILE_BYTES);
|
||||
// written WAL size should less than 100KB (even included HEADER & FOOTER overhead)
|
||||
ASSERT_LE(bytes_num, 1024 * 100);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBWriteTestInstance, DBWriteTest,
|
||||
testing::Values(DBTestBase::kDefault,
|
||||
DBTestBase::kConcurrentWALWrites,
|
||||
|
||||
+6
-7
@@ -166,12 +166,6 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Check if recovery is currently in progress. If it is, we will save this
|
||||
// error so we can check it at the end to see if recovery succeeded or not
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_err;
|
||||
}
|
||||
|
||||
bool paranoid = db_options_.paranoid_checks;
|
||||
Status::Severity sev = Status::Severity::kFatalError;
|
||||
Status new_bg_err;
|
||||
@@ -204,10 +198,15 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
|
||||
new_bg_err = Status(bg_err, sev);
|
||||
|
||||
// Check if recovery is currently in progress. If it is, we will save this
|
||||
// error so we can check it at the end to see if recovery succeeded or not
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = new_bg_err;
|
||||
}
|
||||
|
||||
bool auto_recovery = auto_recovery_;
|
||||
if (new_bg_err.severity() >= Status::Severity::kFatalError && auto_recovery) {
|
||||
auto_recovery = false;
|
||||
;
|
||||
}
|
||||
|
||||
// Allow some error specific overrides
|
||||
|
||||
@@ -22,6 +22,21 @@ namespace rocksdb {
|
||||
class DBErrorHandlingTest : public DBTestBase {
|
||||
public:
|
||||
DBErrorHandlingTest() : DBTestBase("/db_error_handling_test") {}
|
||||
|
||||
std::string GetManifestNameFromLiveFiles() {
|
||||
std::vector<std::string> live_files;
|
||||
uint64_t manifest_size;
|
||||
|
||||
dbfull()->GetLiveFiles(live_files, &manifest_size, false);
|
||||
for (auto& file : live_files) {
|
||||
uint64_t num = 0;
|
||||
FileType type;
|
||||
if (ParseFileName(file, &num, &type) && type == kDescriptorFile) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
class DBErrorHandlingEnv : public EnvWrapper {
|
||||
@@ -161,6 +176,169 @@ TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, ManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, DoubleManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
fault_env->SetFilesystemActive(true);
|
||||
|
||||
// This Resume() will attempt to create a new manifest file and fail again
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kHardError);
|
||||
fault_env->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// A successful Resume() will create a new manifest file
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
std::atomic<bool> fail_manifest(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
Put(Key(0), "val");
|
||||
Put(Key(2), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
// Wait for flush of 2nd L0 file before starting compaction
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"},
|
||||
// Wait for compaction to detect manifest write error
|
||||
{"BackgroundCallCompaction:1",
|
||||
"CompactionManifestWriteError:0"},
|
||||
// Make compaction thread wait for error to be cleared
|
||||
{"CompactionManifestWriteError:1",
|
||||
"DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"},
|
||||
// Wait for DB instance to clear bg_error before calling
|
||||
// TEST_WaitForCompact
|
||||
{"SstFileManagerImpl::ClearError",
|
||||
"CompactionManifestWriteError:2"}});
|
||||
// trigger manifest write failure in compaction thread
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void *) {
|
||||
fail_manifest.store(true);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void *) {
|
||||
if (fail_manifest.load()) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
// This Flush will trigger a compaction, which will fail when appending to
|
||||
// the manifest
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:0");
|
||||
// Clear all errors so when the compaction is retried, it will succeed
|
||||
fault_env->SetFilesystemActive(true);
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:2");
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
ASSERT_EQ("val", Get(Key(2)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
|
||||
+12
-10
@@ -108,26 +108,30 @@ void FileIndexer::UpdateIndex(Arena* arena, const size_t num_levels,
|
||||
|
||||
CalculateLB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->smallest.user_key(), b->largest.user_key());
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
return ucmp_->CompareWithoutTimestamp(a->smallest.user_key(),
|
||||
b->largest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) { index->smallest_lb = f_idx; });
|
||||
CalculateLB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->largest.user_key(), b->largest.user_key());
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
return ucmp_->CompareWithoutTimestamp(a->largest.user_key(),
|
||||
b->largest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) { index->largest_lb = f_idx; });
|
||||
CalculateRB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->smallest.user_key(), b->smallest.user_key());
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
return ucmp_->CompareWithoutTimestamp(a->smallest.user_key(),
|
||||
b->smallest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) { index->smallest_rb = f_idx; });
|
||||
CalculateRB(
|
||||
upper_files, lower_files, &index_level,
|
||||
[this](const FileMetaData * a, const FileMetaData * b)->int {
|
||||
return ucmp_->Compare(a->largest.user_key(), b->smallest.user_key());
|
||||
[this](const FileMetaData* a, const FileMetaData* b) -> int {
|
||||
return ucmp_->CompareWithoutTimestamp(a->largest.user_key(),
|
||||
b->smallest.user_key());
|
||||
},
|
||||
[](IndexUnit* index, int32_t f_idx) { index->largest_rb = f_idx; });
|
||||
}
|
||||
@@ -153,7 +157,6 @@ void FileIndexer::CalculateLB(
|
||||
if (cmp == 0) {
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
++upper_idx;
|
||||
++lower_idx;
|
||||
} else if (cmp > 0) {
|
||||
// Lower level's file (largest) is smaller, a key won't hit in that
|
||||
// file. Move to next lower file
|
||||
@@ -191,7 +194,6 @@ void FileIndexer::CalculateRB(
|
||||
if (cmp == 0) {
|
||||
set_index(&index[upper_idx], lower_idx);
|
||||
--upper_idx;
|
||||
--lower_idx;
|
||||
} else if (cmp < 0) {
|
||||
// Lower level's file (smallest) is larger, a key won't hit in that
|
||||
// file. Move to next lower file.
|
||||
|
||||
@@ -663,7 +663,6 @@ bool InternalStats::HandleNumImmutableMemTableFlushed(uint64_t* value,
|
||||
|
||||
bool InternalStats::HandleMemTableFlushPending(uint64_t* value, DBImpl* /*db*/,
|
||||
Version* /*version*/) {
|
||||
// Return number of mem tables that are ready to flush (made immutable)
|
||||
*value = (cfd_->imm()->IsFlushPending() ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -78,9 +78,6 @@ std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = std::make_shared<CountMergeOperator>();
|
||||
options.max_successive_merges = max_successive_merges;
|
||||
if (max_successive_merges > 0) {
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
}
|
||||
Status s;
|
||||
DestroyDB(dbname, Options());
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "include/rocksdb/types.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/table_builder.h"
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "include/rocksdb/types.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "table/table_builder.h"
|
||||
|
||||
@@ -485,6 +485,7 @@ Status TableCache::MultiGet(const ReadOptions& options,
|
||||
file_options_, internal_comparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record_read_stats */, file_read_hist, skip_filters, level);
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::MultiGet:FindTable", &s);
|
||||
if (s.ok()) {
|
||||
t = GetTableReaderFromHandle(handle);
|
||||
assert(t);
|
||||
|
||||
+21
-1
@@ -1918,6 +1918,11 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
&iter->max_covering_tombstone_seq, this->env_, nullptr,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_mget_id);
|
||||
// MergeInProgress status, if set, has been transferred to the get_context
|
||||
// state, so we set status to ok here. From now on, the iter status will
|
||||
// be used for IO errors, and get_context state will be used for any
|
||||
// key level errors
|
||||
*(iter->s) = Status::OK();
|
||||
}
|
||||
int get_ctx_index = 0;
|
||||
for (auto iter = range->begin(); iter != range->end();
|
||||
@@ -1962,6 +1967,15 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
for (auto iter = file_range.begin(); iter != file_range.end(); ++iter) {
|
||||
GetContext& get_context = *iter->get_context;
|
||||
Status* status = iter->s;
|
||||
// The Status in the KeyContext takes precedence over GetContext state
|
||||
// Status may be an error if there were any IO errors in the table
|
||||
// reader. We never expect Status to be NotFound(), as that is
|
||||
// determined by get_context
|
||||
assert(!status->IsNotFound());
|
||||
if (!status->ok()) {
|
||||
file_range.MarkKeyDone(iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_context.sample()) {
|
||||
sample_file_read_inc(f->file_metadata);
|
||||
@@ -2112,6 +2126,9 @@ bool Version::MaybeInitializeFileMetaData(FileMetaData* file_meta) {
|
||||
}
|
||||
|
||||
void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) {
|
||||
TEST_SYNC_POINT_CALLBACK("VersionStorageInfo::UpdateAccumulatedStats",
|
||||
nullptr);
|
||||
|
||||
assert(file_meta->init_stats_from_file);
|
||||
accumulated_file_size_ += file_meta->fd.GetFileSize();
|
||||
accumulated_raw_key_size_ += file_meta->raw_key_size;
|
||||
@@ -3950,12 +3967,15 @@ Status VersionSet::ProcessManifestWrites(
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
// If manifest append failed for whatever reason, the file could be
|
||||
// corrupted. So we need to force the next version update to start a
|
||||
// new manifest file.
|
||||
descriptor_log_.reset();
|
||||
if (new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Deleting manifest %" PRIu64 " current manifest %" PRIu64
|
||||
"\n",
|
||||
manifest_file_number_, pending_manifest_file_number_);
|
||||
descriptor_log_.reset();
|
||||
env_->DeleteFile(
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_));
|
||||
}
|
||||
|
||||
+9
-2
@@ -344,6 +344,9 @@ void WriteThread::BeginWriteStall() {
|
||||
prev->link_older = w->link_older;
|
||||
w->status = Status::Incomplete("Write stall");
|
||||
SetState(w, STATE_COMPLETED);
|
||||
if (prev->link_older) {
|
||||
prev->link_older->link_newer = prev;
|
||||
}
|
||||
w = prev->link_older;
|
||||
} else {
|
||||
prev = w;
|
||||
@@ -355,7 +358,11 @@ void WriteThread::BeginWriteStall() {
|
||||
void WriteThread::EndWriteStall() {
|
||||
MutexLock lock(&stall_mu_);
|
||||
|
||||
// Unlink write_stall_dummy_ from the write queue. This will unblock
|
||||
// pending write threads to enqueue themselves
|
||||
assert(newest_writer_.load(std::memory_order_relaxed) == &write_stall_dummy_);
|
||||
assert(write_stall_dummy_.link_older != nullptr);
|
||||
write_stall_dummy_.link_older->link_newer = write_stall_dummy_.link_newer;
|
||||
newest_writer_.exchange(write_stall_dummy_.link_older);
|
||||
|
||||
// Wake up writers
|
||||
@@ -444,8 +451,8 @@ size_t WriteThread::EnterAsBatchGroupLeader(Writer* leader,
|
||||
break;
|
||||
}
|
||||
|
||||
if (!w->disable_wal && leader->disable_wal) {
|
||||
// Do not include a write that needs WAL into a batch that has
|
||||
if (w->disable_wal != leader->disable_wal) {
|
||||
// Do not mix writes that enable WAL with the ones whose
|
||||
// WAL disabled.
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,9 @@ class CfConsistencyStressTest : public StressTest {
|
||||
std::string upper_bound;
|
||||
Slice ub_slice;
|
||||
ReadOptions ro_copy = readoptions;
|
||||
if (thread->rand.OneIn(2) && GetNextPrefix(prefix, &upper_bound)) {
|
||||
// Get the next prefix first and then see if we want to set upper bound.
|
||||
// We'll use the next prefix in an assertion later on
|
||||
if (GetNextPrefix(prefix, &upper_bound) && thread->rand.OneIn(2)) {
|
||||
ub_slice = Slice(upper_bound);
|
||||
ro_copy.iterate_upper_bound = &ub_slice;
|
||||
}
|
||||
@@ -255,13 +257,13 @@ class CfConsistencyStressTest : public StressTest {
|
||||
column_families_[rand_column_families[thread->rand.Next() %
|
||||
rand_column_families.size()]];
|
||||
Iterator* iter = db_->NewIterator(ro_copy, cfh);
|
||||
long count = 0;
|
||||
unsigned long count = 0;
|
||||
for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix);
|
||||
iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
assert(prefix_to_use == 0 ||
|
||||
count <= (static_cast<long>(1) << ((8 - prefix_to_use) * 8)));
|
||||
count <= GetPrefixKeyCount(prefix.ToString(), upper_bound));
|
||||
Status s = iter->status();
|
||||
if (s.ok()) {
|
||||
thread->stats.AddPrefixes(1, count);
|
||||
|
||||
@@ -62,7 +62,7 @@ void InitializeHotKeyGenerator(double alpha) {
|
||||
int64_t GetOneHotKeyID(double rand_seed, int64_t max_key) {
|
||||
int64_t low = 1, mid, high = zipf_sum_size, zipf = 0;
|
||||
while (low <= high) {
|
||||
mid = std::floor((low + high) / 2);
|
||||
mid = static_cast<int64_t>(std::floor((low + high) / 2));
|
||||
if (sum_probs[mid] >= rand_seed && sum_probs[mid - 1] < rand_seed) {
|
||||
zipf = mid;
|
||||
break;
|
||||
|
||||
@@ -82,6 +82,9 @@ DECLARE_uint64(seed);
|
||||
DECLARE_bool(read_only);
|
||||
DECLARE_int64(max_key);
|
||||
DECLARE_double(hot_key_alpha);
|
||||
DECLARE_int32(max_key_len);
|
||||
DECLARE_string(key_len_percent_dist);
|
||||
DECLARE_int32(key_window_scale_factor);
|
||||
DECLARE_int32(column_families);
|
||||
DECLARE_string(options_file);
|
||||
DECLARE_int64(active_width);
|
||||
@@ -352,7 +355,7 @@ inline bool GetNextPrefix(const rocksdb::Slice& src, std::string* v) {
|
||||
#endif
|
||||
|
||||
// convert long to a big-endian slice key
|
||||
extern inline std::string Key(int64_t val) {
|
||||
extern inline std::string GetStringFromInt(int64_t val) {
|
||||
std::string little_endian_key;
|
||||
std::string big_endian_key;
|
||||
PutFixed64(&little_endian_key, val);
|
||||
@@ -364,16 +367,107 @@ extern inline std::string Key(int64_t val) {
|
||||
return big_endian_key;
|
||||
}
|
||||
|
||||
// A struct for maintaining the parameters for generating variable length keys
|
||||
struct KeyGenContext {
|
||||
// Number of adjacent keys in one cycle of key lengths
|
||||
uint64_t window;
|
||||
// Number of keys of each possible length in a given window
|
||||
std::vector<uint64_t> weights;
|
||||
};
|
||||
extern KeyGenContext key_gen_ctx;
|
||||
|
||||
// Generate a variable length key string from the given int64 val. The
|
||||
// order of the keys is preserved. The key could be anywhere from 8 to
|
||||
// max_key_len * 8 bytes.
|
||||
// The algorithm picks the length based on the
|
||||
// offset of the val within a configured window and the distribution of the
|
||||
// number of keys of various lengths in that window. For example, if x, y, x are
|
||||
// the weights assigned to each possible key length, the keys generated would be
|
||||
// - {0}...{x-1}
|
||||
// {(x-1),0}..{(x-1),(y-1)},{(x-1),(y-1),0}..{(x-1),(y-1),(z-1)} and so on.
|
||||
// Additionally, a trailer of 0-7 bytes could be appended.
|
||||
extern inline std::string Key(int64_t val) {
|
||||
uint64_t window = key_gen_ctx.window;
|
||||
size_t levels = key_gen_ctx.weights.size();
|
||||
std::string key;
|
||||
|
||||
for (size_t level = 0; level < levels; ++level) {
|
||||
uint64_t weight = key_gen_ctx.weights[level];
|
||||
uint64_t offset = static_cast<uint64_t>(val) % window;
|
||||
uint64_t mult = static_cast<uint64_t>(val) / window;
|
||||
uint64_t pfx = mult * weight + (offset >= weight ? weight - 1 : offset);
|
||||
key.append(GetStringFromInt(pfx));
|
||||
if (offset < weight) {
|
||||
// Use the bottom 3 bits of offset as the number of trailing 'x's in the
|
||||
// key. If the next key is going to be of the next level, then skip the
|
||||
// trailer as it would break ordering. If the key length is already at max,
|
||||
// skip the trailer.
|
||||
if (offset < weight - 1 && level < levels - 1) {
|
||||
size_t trailer_len = offset & 0x7;
|
||||
key.append(trailer_len, 'x');
|
||||
}
|
||||
break;
|
||||
}
|
||||
val = offset - weight;
|
||||
window -= weight;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
// Given a string key, map it to an index into the expected values buffer
|
||||
extern inline bool GetIntVal(std::string big_endian_key, uint64_t* key_p) {
|
||||
unsigned int size_key = sizeof(*key_p);
|
||||
assert(big_endian_key.size() == size_key);
|
||||
size_t size_key = big_endian_key.size();
|
||||
std::vector<uint64_t> prefixes;
|
||||
|
||||
assert(size_key <= key_gen_ctx.weights.size() * sizeof(uint64_t));
|
||||
|
||||
// Pad with zeros to make it a multiple of 8. This function may be called
|
||||
// with a prefix, in which case we return the first index that falls
|
||||
// inside or outside that prefix, dependeing on whether the prefix is
|
||||
// the start of upper bound of a scan
|
||||
unsigned int pad = sizeof(uint64_t) - (size_key % sizeof(uint64_t));
|
||||
if (pad < sizeof(uint64_t)) {
|
||||
big_endian_key.append(pad, '\0');
|
||||
size_key += pad;
|
||||
}
|
||||
|
||||
std::string little_endian_key;
|
||||
little_endian_key.resize(size_key);
|
||||
for (size_t i = 0; i < size_key; ++i) {
|
||||
little_endian_key[i] = big_endian_key[size_key - 1 - i];
|
||||
for (size_t start = 0; start < size_key; start += sizeof(uint64_t)) {
|
||||
size_t end = start + sizeof(uint64_t);
|
||||
for (size_t i = 0; i < sizeof(uint64_t); ++i) {
|
||||
little_endian_key[start + i] = big_endian_key[end - 1 - i];
|
||||
}
|
||||
Slice little_endian_slice =
|
||||
Slice(&little_endian_key[start], sizeof(uint64_t));
|
||||
uint64_t pfx;
|
||||
if (!GetFixed64(&little_endian_slice, &pfx)) {
|
||||
return false;
|
||||
}
|
||||
prefixes.emplace_back(pfx);
|
||||
}
|
||||
Slice little_endian_slice = Slice(little_endian_key);
|
||||
return GetFixed64(&little_endian_slice, key_p);
|
||||
|
||||
uint64_t key = 0;
|
||||
for (size_t i = 0; i < prefixes.size(); ++i) {
|
||||
uint64_t pfx = prefixes[i];
|
||||
key += (pfx / key_gen_ctx.weights[i]) * key_gen_ctx.window +
|
||||
pfx % key_gen_ctx.weights[i];
|
||||
}
|
||||
*key_p = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern inline uint64_t GetPrefixKeyCount(const std::string& prefix,
|
||||
const std::string& ub) {
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
|
||||
if (!GetIntVal(prefix, &start) || !GetIntVal(ub, &end)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return end - start;
|
||||
}
|
||||
|
||||
extern inline std::string StringToHex(const std::string& str) {
|
||||
|
||||
@@ -28,6 +28,17 @@ DEFINE_bool(read_only, false, "True if open DB in read-only mode during tests");
|
||||
DEFINE_int64(max_key, 1 * KB * KB,
|
||||
"Max number of key/values to place in database");
|
||||
|
||||
DEFINE_int32(max_key_len, 3, "Maximum length of a key in 8-byte units");
|
||||
|
||||
DEFINE_string(key_len_percent_dist, "",
|
||||
"Percentages of keys of various lengths. For example, 1,30,69 "
|
||||
"means 1% of keys are 8 bytes, 30% are 16 bytes, and 69% are "
|
||||
"24 bytes. If not specified, it will be evenly distributed");
|
||||
|
||||
DEFINE_int32(key_window_scale_factor, 10,
|
||||
"This value will be multiplied by 100 to come up with a window "
|
||||
"size for varying the key length");
|
||||
|
||||
DEFINE_int32(column_families, 10, "Number of column families");
|
||||
|
||||
DEFINE_double(
|
||||
|
||||
@@ -771,7 +771,7 @@ std::vector<std::string> StressTest::GetWhiteBoxKeys(ThreadState* thread,
|
||||
k[i] = static_cast<char>(cur - 1);
|
||||
break;
|
||||
} else if (i > 0) {
|
||||
k[i] = 0xFF;
|
||||
k[i] = static_cast<char>(0xFF);
|
||||
}
|
||||
}
|
||||
} else if (thread->rand.OneIn(2)) {
|
||||
@@ -1040,7 +1040,26 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
return;
|
||||
}
|
||||
|
||||
const SliceTransform* pe =
|
||||
ro.total_order_seek ? nullptr : options_.prefix_extractor.get();
|
||||
const Comparator* cmp = options_.comparator;
|
||||
|
||||
if (iter->Valid() && !cmp_iter->Valid()) {
|
||||
if (pe != nullptr) {
|
||||
if (!pe->InDomain(seek_key)) {
|
||||
// Prefix seek a non-in-domain key is undefined. Skip checking for
|
||||
// this scenario.
|
||||
*diverged = true;
|
||||
return;
|
||||
} else if (!pe->InDomain(iter->key())) {
|
||||
// out of range is iterator key is not in domain anymore.
|
||||
*diverged = true;
|
||||
return;
|
||||
} else if (pe->Transform(iter->key()) != pe->Transform(seek_key)) {
|
||||
*diverged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Control interator is invalid but iterator has key %s "
|
||||
"%s\n",
|
||||
@@ -1051,9 +1070,6 @@ void StressTest::VerifyIterator(ThreadState* thread,
|
||||
// Iterator is not valid. It can be legimate if it has already been
|
||||
// out of upper or lower bound, or filtered out by prefix iterator.
|
||||
const Slice& total_order_key = cmp_iter->key();
|
||||
const SliceTransform* pe =
|
||||
ro.total_order_seek ? nullptr : options_.prefix_extractor.get();
|
||||
const Comparator* cmp = options_.comparator;
|
||||
|
||||
if (pe != nullptr) {
|
||||
if (!pe->InDomain(seek_key)) {
|
||||
@@ -1947,6 +1963,11 @@ void StressTest::Open() {
|
||||
}
|
||||
s = TransactionDB::Open(options_, txn_db_options, FLAGS_db,
|
||||
cf_descriptors, &column_families_, &txn_db_);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error in opening the TransactionDB [%s]\n",
|
||||
s.ToString().c_str());
|
||||
fflush(stderr);
|
||||
}
|
||||
assert(s.ok());
|
||||
db_ = txn_db_;
|
||||
// after a crash, rollback to commit recovered transactions
|
||||
|
||||
@@ -30,6 +30,8 @@ static std::shared_ptr<rocksdb::Env> env_guard;
|
||||
static std::shared_ptr<rocksdb::DbStressEnvWrapper> env_wrapper_guard;
|
||||
} // namespace
|
||||
|
||||
KeyGenContext key_gen_ctx;
|
||||
|
||||
int db_stress_tool(int argc, char** argv) {
|
||||
SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) +
|
||||
" [OPTIONS]...");
|
||||
@@ -197,6 +199,38 @@ int db_stress_tool(int argc, char** argv) {
|
||||
rocksdb_kill_odds = FLAGS_kill_random_test;
|
||||
rocksdb_kill_prefix_blacklist = SplitString(FLAGS_kill_prefix_blacklist);
|
||||
|
||||
unsigned int levels = FLAGS_max_key_len;
|
||||
std::vector<std::string> weights;
|
||||
uint64_t scale_factor = FLAGS_key_window_scale_factor;
|
||||
key_gen_ctx.window = scale_factor * 100;
|
||||
if (!FLAGS_key_len_percent_dist.empty()) {
|
||||
weights = SplitString(FLAGS_key_len_percent_dist);
|
||||
if (weights.size() != levels) {
|
||||
fprintf(stderr,
|
||||
"Number of weights in key_len_dist should be equal to"
|
||||
" max_key_len");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
uint64_t total_weight = 0;
|
||||
for (std::string& weight : weights) {
|
||||
uint64_t val = std::stoull(weight);
|
||||
key_gen_ctx.weights.emplace_back(val * scale_factor);
|
||||
total_weight += val;
|
||||
}
|
||||
if (total_weight != 100) {
|
||||
fprintf(stderr, "Sum of all weights in key_len_dist should be 100");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
uint64_t keys_per_level = key_gen_ctx.window / levels;
|
||||
for (unsigned int level = 0; level < levels - 1; ++level) {
|
||||
key_gen_ctx.weights.emplace_back(keys_per_level);
|
||||
}
|
||||
key_gen_ctx.weights.emplace_back(key_gen_ctx.window -
|
||||
keys_per_level * (levels - 1));
|
||||
}
|
||||
|
||||
std::unique_ptr<rocksdb::StressTest> stress;
|
||||
if (FLAGS_test_cf_consistency) {
|
||||
stress.reset(CreateCfConsistencyStressTest());
|
||||
|
||||
@@ -35,29 +35,35 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
}
|
||||
if (!thread->rand.OneIn(2)) {
|
||||
// Use iterator to verify this range
|
||||
Slice prefix;
|
||||
std::string seek_key = Key(start);
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(options, column_families_[cf]));
|
||||
iter->Seek(Key(start));
|
||||
iter->Seek(seek_key);
|
||||
prefix = Slice(seek_key.data(), prefix_to_use);
|
||||
for (auto i = start; i < end; i++) {
|
||||
if (thread->shared->HasVerificationFailedYet()) {
|
||||
break;
|
||||
}
|
||||
// Reseek when the prefix changes
|
||||
if (prefix_to_use > 0 &&
|
||||
i % (static_cast<uint64_t>(1) << 8 * (8 - prefix_to_use)) == 0) {
|
||||
iter->Seek(Key(i));
|
||||
}
|
||||
std::string from_db;
|
||||
std::string keystr = Key(i);
|
||||
Slice k = keystr;
|
||||
Slice pfx = Slice(keystr.data(), prefix_to_use);
|
||||
// Reseek when the prefix changes
|
||||
if (prefix_to_use > 0 && prefix.compare(pfx) != 0) {
|
||||
iter->Seek(k);
|
||||
seek_key = keystr;
|
||||
prefix = Slice(seek_key.data(), prefix_to_use);
|
||||
}
|
||||
Status s = iter->status();
|
||||
if (iter->Valid()) {
|
||||
Slice iter_key = iter->key();
|
||||
if (iter->key().compare(k) > 0) {
|
||||
s = Status::NotFound(Slice());
|
||||
} else if (iter->key().compare(k) == 0) {
|
||||
from_db = iter->value().ToString();
|
||||
iter->Next();
|
||||
} else if (iter->key().compare(k) < 0) {
|
||||
} else if (iter_key.compare(k) < 0) {
|
||||
VerificationAbort(shared, "An out of range key was found",
|
||||
static_cast<int>(cf), i);
|
||||
}
|
||||
@@ -265,19 +271,23 @@ class NonBatchedOpsStressTest : public StressTest {
|
||||
std::string upper_bound;
|
||||
Slice ub_slice;
|
||||
ReadOptions ro_copy = read_opts;
|
||||
if (thread->rand.OneIn(2) && GetNextPrefix(prefix, &upper_bound)) {
|
||||
// Get the next prefix first and then see if we want to set upper bound.
|
||||
// We'll use the next prefix in an assertion later on
|
||||
if (GetNextPrefix(prefix, &upper_bound) && thread->rand.OneIn(2)) {
|
||||
// For half of the time, set the upper bound to the next prefix
|
||||
ub_slice = Slice(upper_bound);
|
||||
ro_copy.iterate_upper_bound = &ub_slice;
|
||||
}
|
||||
|
||||
Iterator* iter = db_->NewIterator(ro_copy, cfh);
|
||||
long count = 0;
|
||||
unsigned long count = 0;
|
||||
for (iter->Seek(prefix); iter->Valid() && iter->key().starts_with(prefix);
|
||||
iter->Next()) {
|
||||
++count;
|
||||
}
|
||||
assert(count <= (static_cast<long>(1) << ((8 - FLAGS_prefix_size) * 8)));
|
||||
|
||||
assert(count <= GetPrefixKeyCount(prefix.ToString(), upper_bound));
|
||||
|
||||
Status s = iter->status();
|
||||
if (iter->status().ok()) {
|
||||
thread->stats.AddPrefixes(1, count);
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Vendored
+5
-4
@@ -627,8 +627,9 @@ class CompositeEnvWrapper : public Env {
|
||||
|
||||
class LegacySequentialFileWrapper : public FSSequentialFile {
|
||||
public:
|
||||
explicit LegacySequentialFileWrapper(std::unique_ptr<SequentialFile>&& target)
|
||||
: target_(std::move(target)) {}
|
||||
explicit LegacySequentialFileWrapper(
|
||||
std::unique_ptr<SequentialFile>&& _target)
|
||||
: target_(std::move(_target)) {}
|
||||
|
||||
IOStatus Read(size_t n, const IOOptions& /*options*/, Slice* result,
|
||||
char* scratch, IODebugContext* /*dbg*/) override {
|
||||
@@ -716,8 +717,8 @@ class LegacyRandomAccessFileWrapper : public FSRandomAccessFile {
|
||||
|
||||
class LegacyWritableFileWrapper : public FSWritableFile {
|
||||
public:
|
||||
explicit LegacyWritableFileWrapper(std::unique_ptr<WritableFile>&& target)
|
||||
: target_(std::move(target)) {}
|
||||
explicit LegacyWritableFileWrapper(std::unique_ptr<WritableFile>&& _target)
|
||||
: target_(std::move(_target)) {}
|
||||
|
||||
IOStatus Append(const Slice& data, const IOOptions& /*options*/,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
|
||||
Vendored
+22
-4
@@ -1130,8 +1130,25 @@ TEST_P(EnvPosixTestWithParam, MultiRead) {
|
||||
ASSERT_OK(wfile->Close());
|
||||
}
|
||||
|
||||
// Random Read
|
||||
{
|
||||
// More attempts to simulate more partial result sequences.
|
||||
for (uint32_t attempt = 0; attempt < 20; attempt++) {
|
||||
// Random Read
|
||||
Random rnd(301 + attempt);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixRandomAccessFile::MultiRead:io_uring_result", [&](void* arg) {
|
||||
if (attempt > 0) {
|
||||
// No failure in the first attempt.
|
||||
size_t& bytes_read = *static_cast<size_t*>(arg);
|
||||
if (rnd.OneIn(4)) {
|
||||
bytes_read = 0;
|
||||
} else if (rnd.OneIn(3)) {
|
||||
bytes_read = static_cast<size_t>(
|
||||
rnd.Uniform(static_cast<int>(bytes_read)));
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
std::vector<ReadRequest> reqs(3);
|
||||
std::vector<std::unique_ptr<char, Deleter>> data;
|
||||
@@ -1156,6 +1173,7 @@ TEST_P(EnvPosixTestWithParam, MultiRead) {
|
||||
ASSERT_OK(reqs[i].status);
|
||||
ASSERT_EQ(memcmp(reqs[i].scratch, buf.get(), kSectorSize), 0);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1186,7 +1204,7 @@ TEST_P(EnvPosixTestWithParam, InvalidateCache) {
|
||||
ASSERT_OK(wfile->Append(slice));
|
||||
ASSERT_OK(wfile->InvalidateCache(0, 0));
|
||||
ASSERT_OK(wfile->Close());
|
||||
}
|
||||
}
|
||||
|
||||
// Random Read
|
||||
{
|
||||
@@ -1430,7 +1448,7 @@ TEST_P(EnvPosixTestWithParam, ConsistentChildrenAttributes) {
|
||||
Slice buf(buf_ptr.get(), data.size());
|
||||
file->Append(buf);
|
||||
data.append(std::string(4096, 'T'));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Env::FileAttributes> file_attrs;
|
||||
ASSERT_OK(env_->GetChildrenFileAttributes(test::TmpDir(env_), &file_attrs));
|
||||
|
||||
Vendored
+57
-23
@@ -482,9 +482,6 @@ IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
#if defined(ROCKSDB_IOURING_PRESENT)
|
||||
size_t reqs_off;
|
||||
ssize_t ret __attribute__((__unused__));
|
||||
|
||||
struct io_uring* iu = nullptr;
|
||||
if (thread_local_io_urings_) {
|
||||
iu = static_cast<struct io_uring*>(thread_local_io_urings_->Get());
|
||||
@@ -505,35 +502,49 @@ IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs,
|
||||
struct WrappedReadRequest {
|
||||
FSReadRequest* req;
|
||||
struct iovec iov;
|
||||
explicit WrappedReadRequest(FSReadRequest* r) : req(r) {}
|
||||
size_t finished_len;
|
||||
explicit WrappedReadRequest(FSReadRequest* r) : req(r), finished_len(0) {}
|
||||
};
|
||||
|
||||
autovector<WrappedReadRequest, 32> req_wraps;
|
||||
autovector<WrappedReadRequest*, 4> incomplete_rq_list;
|
||||
|
||||
for (size_t i = 0; i < num_reqs; i++) {
|
||||
req_wraps.emplace_back(&reqs[i]);
|
||||
}
|
||||
|
||||
reqs_off = 0;
|
||||
while (num_reqs) {
|
||||
size_t this_reqs = num_reqs;
|
||||
size_t reqs_off = 0;
|
||||
while (num_reqs > reqs_off || !incomplete_rq_list.empty()) {
|
||||
size_t this_reqs = (num_reqs - reqs_off) + incomplete_rq_list.size();
|
||||
|
||||
// If requests exceed depth, split it into batches
|
||||
if (this_reqs > kIoUringDepth) this_reqs = kIoUringDepth;
|
||||
|
||||
assert(incomplete_rq_list.size() <= this_reqs);
|
||||
for (size_t i = 0; i < this_reqs; i++) {
|
||||
size_t index = i + reqs_off;
|
||||
WrappedReadRequest* rep_to_submit;
|
||||
if (i < incomplete_rq_list.size()) {
|
||||
rep_to_submit = incomplete_rq_list[i];
|
||||
} else {
|
||||
rep_to_submit = &req_wraps[reqs_off++];
|
||||
}
|
||||
assert(rep_to_submit->req->len > rep_to_submit->finished_len);
|
||||
rep_to_submit->iov.iov_base =
|
||||
rep_to_submit->req->scratch + rep_to_submit->finished_len;
|
||||
rep_to_submit->iov.iov_len =
|
||||
rep_to_submit->req->len - rep_to_submit->finished_len;
|
||||
|
||||
struct io_uring_sqe* sqe;
|
||||
|
||||
sqe = io_uring_get_sqe(iu);
|
||||
req_wraps[index].iov.iov_base = reqs[index].scratch;
|
||||
req_wraps[index].iov.iov_len = reqs[index].len;
|
||||
io_uring_prep_readv(sqe, fd_, &req_wraps[index].iov, 1,
|
||||
reqs[index].offset);
|
||||
io_uring_sqe_set_data(sqe, &req_wraps[index]);
|
||||
io_uring_prep_readv(
|
||||
sqe, fd_, &rep_to_submit->iov, 1,
|
||||
rep_to_submit->req->offset + rep_to_submit->finished_len);
|
||||
io_uring_sqe_set_data(sqe, rep_to_submit);
|
||||
}
|
||||
incomplete_rq_list.clear();
|
||||
|
||||
ret = io_uring_submit_and_wait(iu, static_cast<unsigned int>(this_reqs));
|
||||
ssize_t ret =
|
||||
io_uring_submit_and_wait(iu, static_cast<unsigned int>(this_reqs));
|
||||
if (static_cast<size_t>(ret) != this_reqs) {
|
||||
fprintf(stderr, "ret = %ld this_reqs: %ld\n", (long)ret, (long)this_reqs);
|
||||
}
|
||||
@@ -547,21 +558,44 @@ IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs,
|
||||
// of our initial wait not reaping all completions
|
||||
ret = io_uring_wait_cqe(iu, &cqe);
|
||||
assert(!ret);
|
||||
|
||||
req_wrap = static_cast<WrappedReadRequest*>(io_uring_cqe_get_data(cqe));
|
||||
FSReadRequest* req = req_wrap->req;
|
||||
if (static_cast<size_t>(cqe->res) == req_wrap->iov.iov_len) {
|
||||
req->result = Slice(req->scratch, cqe->res);
|
||||
req->status = IOStatus::OK();
|
||||
} else if (cqe->res >= 0) {
|
||||
req->result = Slice(req->scratch, req_wrap->iov.iov_len - cqe->res);
|
||||
} else {
|
||||
if (cqe->res < 0) {
|
||||
req->result = Slice(req->scratch, 0);
|
||||
req->status = IOError("Req failed", filename_, cqe->res);
|
||||
} else {
|
||||
size_t bytes_read = static_cast<size_t>(cqe->res);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"PosixRandomAccessFile::MultiRead:io_uring_result", &bytes_read);
|
||||
if (bytes_read == req_wrap->iov.iov_len) {
|
||||
req->result = Slice(req->scratch, req->len);
|
||||
req->status = IOStatus::OK();
|
||||
} else if (bytes_read == 0) {
|
||||
// cqe->res == 0 can means EOF, or can mean partial results. See
|
||||
// comment
|
||||
// https://github.com/facebook/rocksdb/pull/6441#issuecomment-589843435
|
||||
// Fall back to pread in this case.
|
||||
Slice tmp_slice;
|
||||
req->status =
|
||||
Read(req->offset + req_wrap->finished_len,
|
||||
req->len - req_wrap->finished_len, options, &tmp_slice,
|
||||
req->scratch + req_wrap->finished_len, dbg);
|
||||
req->result =
|
||||
Slice(req->scratch, req_wrap->finished_len + tmp_slice.size());
|
||||
} else if (bytes_read < req_wrap->iov.iov_len) {
|
||||
assert(bytes_read > 0);
|
||||
assert(bytes_read + req_wrap->finished_len < req->len);
|
||||
req_wrap->finished_len += bytes_read;
|
||||
incomplete_rq_list.push_back(req_wrap);
|
||||
} else {
|
||||
req->result = Slice(req->scratch, 0);
|
||||
req->status = IOError("Req returned more bytes than requested",
|
||||
filename_, cqe->res);
|
||||
}
|
||||
}
|
||||
io_uring_cqe_seen(iu, cqe);
|
||||
}
|
||||
num_reqs -= this_reqs;
|
||||
reqs_off += this_reqs;
|
||||
}
|
||||
return IOStatus::OK();
|
||||
#else
|
||||
|
||||
@@ -46,21 +46,34 @@ int main() {
|
||||
assert(s.IsNotFound());
|
||||
|
||||
// Write a key in this transaction
|
||||
txn->Put("abc", "def");
|
||||
s = txn->Put("abc", "xyz");
|
||||
assert(s.ok());
|
||||
|
||||
// Read a key OUTSIDE this transaction. Does not affect txn.
|
||||
s = db->Get(read_options, "abc", &value);
|
||||
assert(s.IsNotFound());
|
||||
|
||||
// Write a key OUTSIDE of this transaction.
|
||||
// Does not affect txn since this is an unrelated key. If we wrote key 'abc'
|
||||
// here, the transaction would fail to commit.
|
||||
s = db->Put(write_options, "xyz", "zzz");
|
||||
assert(s.ok());
|
||||
s = db->Put(write_options, "abc", "def");
|
||||
assert(s.ok());
|
||||
|
||||
// Commit transaction
|
||||
s = txn->Commit();
|
||||
assert(s.ok());
|
||||
assert(s.IsBusy());
|
||||
delete txn;
|
||||
|
||||
s = db->Get(read_options, "xyz", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "zzz");
|
||||
|
||||
s = db->Get(read_options, "abc", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "def");
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// "Repeatable Read" (Snapshot Isolation) Example
|
||||
@@ -75,11 +88,13 @@ int main() {
|
||||
const Snapshot* snapshot = txn->GetSnapshot();
|
||||
|
||||
// Write a key OUTSIDE of transaction
|
||||
db->Put(write_options, "abc", "xyz");
|
||||
s = db->Put(write_options, "abc", "xyz");
|
||||
assert(s.ok());
|
||||
|
||||
// Read a key using the snapshot
|
||||
read_options.snapshot = snapshot;
|
||||
s = txn->GetForUpdate(read_options, "abc", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "def");
|
||||
|
||||
// Attempt to commit transaction
|
||||
@@ -94,6 +109,10 @@ int main() {
|
||||
read_options.snapshot = nullptr;
|
||||
snapshot = nullptr;
|
||||
|
||||
s = db->Get(read_options, "abc", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "xyz");
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// "Read Committed" (Monotonic Atomic Views) Example
|
||||
@@ -112,10 +131,18 @@ int main() {
|
||||
// Do some reads and writes to key "x"
|
||||
read_options.snapshot = db->GetSnapshot();
|
||||
s = txn->Get(read_options, "x", &value);
|
||||
txn->Put("x", "x");
|
||||
assert(s.IsNotFound());
|
||||
s = txn->Put("x", "x");
|
||||
assert(s.ok());
|
||||
|
||||
// The transaction hasn't committed, so the write is not visible
|
||||
// outside of txn.
|
||||
s = db->Get(read_options, "x", &value);
|
||||
assert(s.IsNotFound());
|
||||
|
||||
// Do a write outside of the transaction to key "y"
|
||||
s = db->Put(write_options, "y", "y");
|
||||
s = db->Put(write_options, "y", "z");
|
||||
assert(s.ok());
|
||||
|
||||
// Set a new snapshot in the transaction
|
||||
txn->SetSnapshot();
|
||||
@@ -123,6 +150,8 @@ int main() {
|
||||
|
||||
// Do some reads and writes to key "y"
|
||||
s = txn->GetForUpdate(read_options, "y", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "z");
|
||||
txn->Put("y", "y");
|
||||
|
||||
// Commit. Since the snapshot was advanced, the write done outside of the
|
||||
@@ -133,6 +162,15 @@ int main() {
|
||||
// Clear snapshot from read options since it is no longer valid
|
||||
read_options.snapshot = nullptr;
|
||||
|
||||
// txn is committed, read the latest values.
|
||||
s = db->Get(read_options, "x", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "x");
|
||||
|
||||
s = db->Get(read_options, "y", &value);
|
||||
assert(s.ok());
|
||||
assert(value == "y");
|
||||
|
||||
// Cleanup
|
||||
delete txn_db;
|
||||
DestroyDB(kDBPath, options);
|
||||
|
||||
@@ -308,6 +308,7 @@ void SstFileManagerImpl::ClearError() {
|
||||
// since the ErrorHandler::recovery_in_prog_ flag would be true
|
||||
cur_instance_ = error_handler;
|
||||
mu_.Unlock();
|
||||
TEST_SYNC_POINT("SstFileManagerImpl::ClearError");
|
||||
s = error_handler->RecoverFromBGError();
|
||||
mu_.Lock();
|
||||
// The DB instance might have been deleted while we were
|
||||
@@ -418,7 +419,8 @@ bool SstFileManagerImpl::CancelErrorRecovery(ErrorHandler* handler) {
|
||||
Status SstFileManagerImpl::ScheduleFileDeletion(
|
||||
const std::string& file_path, const std::string& path_to_sync,
|
||||
const bool force_bg) {
|
||||
TEST_SYNC_POINT("SstFileManagerImpl::ScheduleFileDeletion");
|
||||
TEST_SYNC_POINT_CALLBACK("SstFileManagerImpl::ScheduleFileDeletion",
|
||||
const_cast<std::string*>(&file_path));
|
||||
return delete_scheduler_.DeleteFile(file_path, path_to_sync,
|
||||
force_bg);
|
||||
}
|
||||
|
||||
+12
-10
@@ -210,23 +210,23 @@ extern ROCKSDB_LIBRARY_API void rocksdb_checkpoint_object_destroy(
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_column_families(
|
||||
const rocksdb_options_t* options, const char* name, int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_t*
|
||||
rocksdb_open_for_read_only_column_families(
|
||||
const rocksdb_options_t* options, const char* name, int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles,
|
||||
unsigned char error_if_log_file_exist, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_open_as_secondary_column_families(
|
||||
const rocksdb_options_t* options, const char* name,
|
||||
const char* secondary_path, int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** colummn_family_handles, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API char** rocksdb_list_column_families(
|
||||
@@ -1037,6 +1037,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_fifo_compaction_options(
|
||||
rocksdb_options_t* opt, rocksdb_fifo_compaction_options_t* fifo);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_ratelimiter(
|
||||
rocksdb_options_t* opt, rocksdb_ratelimiter_t* limiter);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_atomic_flush(
|
||||
rocksdb_options_t* opt, unsigned char);
|
||||
|
||||
/* RateLimiter */
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
@@ -1512,8 +1514,8 @@ extern ROCKSDB_LIBRARY_API rocksdb_transactiondb_t* rocksdb_transactiondb_open(
|
||||
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,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t*
|
||||
@@ -1661,8 +1663,8 @@ rocksdb_optimistictransactiondb_open(const rocksdb_options_t* options,
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_optimistictransactiondb_t*
|
||||
rocksdb_optimistictransactiondb_open_column_families(
|
||||
const rocksdb_options_t* options, const char* name, int num_column_families,
|
||||
const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
const char* const* column_family_names,
|
||||
const rocksdb_options_t* const* column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_t*
|
||||
|
||||
@@ -255,7 +255,7 @@ class Cache {
|
||||
// Always delete the DB object before calling this method!
|
||||
virtual void DisownData(){
|
||||
// default implementation is noop
|
||||
};
|
||||
}
|
||||
|
||||
// Apply callback to all entries in the cache
|
||||
// If thread_safe is true, it will also lock the accesses. Otherwise, it will
|
||||
|
||||
@@ -672,7 +672,7 @@ class RandomAccessFile {
|
||||
virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
|
||||
return 0; // Default implementation to prevent issues with backwards
|
||||
// compatibility.
|
||||
};
|
||||
}
|
||||
|
||||
enum AccessPattern { NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED };
|
||||
|
||||
@@ -1414,7 +1414,7 @@ class RandomAccessFileWrapper : public RandomAccessFile {
|
||||
}
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override {
|
||||
return target_->GetUniqueId(id, max_size);
|
||||
};
|
||||
}
|
||||
void Hint(AccessPattern pattern) override { target_->Hint(pattern); }
|
||||
bool use_direct_io() const override { return target_->use_direct_io(); }
|
||||
size_t GetRequiredBufferAlignment() const override {
|
||||
|
||||
@@ -101,6 +101,9 @@ struct FilterBuildingContext {
|
||||
// The table level at time of constructing the SST file, or -1 if unknown.
|
||||
// (The table file could later be used at a different level.)
|
||||
int level_at_creation = -1;
|
||||
|
||||
// An optional logger for reporting errors, warnings, etc.
|
||||
Logger* info_log = nullptr;
|
||||
};
|
||||
|
||||
// We add a new format of filter block called full filter block
|
||||
|
||||
@@ -195,8 +195,15 @@ class PinnableSlice : public Slice, public Cleanable {
|
||||
}
|
||||
}
|
||||
|
||||
void remove_prefix(size_t /*n*/) {
|
||||
assert(0); // Not implemented
|
||||
void remove_prefix(size_t n) {
|
||||
assert(n <= size());
|
||||
if (pinned_) {
|
||||
data_ += n;
|
||||
size_ -= n;
|
||||
} else {
|
||||
buf_->erase(0, n);
|
||||
PinSelf();
|
||||
}
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
|
||||
@@ -523,7 +523,7 @@ class Statistics {
|
||||
virtual bool getTickerMap(std::map<std::string, uint64_t>*) const {
|
||||
// Do nothing by default
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// Override this function to disable particular histogram collection
|
||||
virtual bool HistEnabledForType(uint32_t type) const {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 6
|
||||
#define ROCKSDB_MINOR 6
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 7
|
||||
#define ROCKSDB_PATCH 3
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
// double underscore. Now we have to live with our choice. We'll deprecate these
|
||||
|
||||
+12
-10
@@ -313,17 +313,19 @@ if(NOT EXISTS ${JAVA_TEST_LIBDIR})
|
||||
file(MAKE_DIRECTORY mkdir ${JAVA_TEST_LIBDIR})
|
||||
endif()
|
||||
|
||||
if (DEFINED CUSTOM_REPO_URL)
|
||||
set(SEARCH_REPO_URL ${CUSTOM_REPO_URL}/)
|
||||
set(CENTRAL_REPO_URL ${CUSTOM_REPO_URL}/)
|
||||
if (DEFINED CUSTOM_DEPS_URL)
|
||||
set(DEPS_URL ${CUSTOM_DEPS_URL}/)
|
||||
else ()
|
||||
set(SEARCH_REPO_URL "http://search.maven.org/remotecontent?filepath=")
|
||||
set(CENTRAL_REPO_URL "http://central.maven.org/maven2/")
|
||||
# This is a URL for artifacts from a "fake" release on pdillinger's fork,
|
||||
# so as not to put binaries in git (ew). We should move to hosting these
|
||||
# under the facebook account on github, or something else more reliable
|
||||
# than maven.org, which has been failing frequently from Travis.
|
||||
set(DEPS_URL "https://github.com/pdillinger/rocksdb/releases/download/v6.6.x-java-deps")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS ${JAVA_JUNIT_JAR})
|
||||
message("Downloading ${JAVA_JUNIT_JAR}")
|
||||
file(DOWNLOAD ${SEARCH_REPO_URL}junit/junit/4.12/junit-4.12.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
file(DOWNLOAD ${DEPS_URL}/junit-4.12.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
list(GET downloadStatus 0 error_code)
|
||||
if(NOT error_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed downloading ${JAVA_JUNIT_JAR}")
|
||||
@@ -332,7 +334,7 @@ if(NOT EXISTS ${JAVA_JUNIT_JAR})
|
||||
endif()
|
||||
if(NOT EXISTS ${JAVA_HAMCR_JAR})
|
||||
message("Downloading ${JAVA_HAMCR_JAR}")
|
||||
file(DOWNLOAD ${SEARCH_REPO_URL}org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
file(DOWNLOAD ${DEPS_URL}/hamcrest-core-1.3.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
list(GET downloadStatus 0 error_code)
|
||||
if(NOT error_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed downloading ${JAVA_HAMCR_JAR}")
|
||||
@@ -341,7 +343,7 @@ if(NOT EXISTS ${JAVA_HAMCR_JAR})
|
||||
endif()
|
||||
if(NOT EXISTS ${JAVA_MOCKITO_JAR})
|
||||
message("Downloading ${JAVA_MOCKITO_JAR}")
|
||||
file(DOWNLOAD ${SEARCH_REPO_URL}org/mockito/mockito-all/1.10.19/mockito-all-1.10.19.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
file(DOWNLOAD ${DEPS_URL}/mockito-all-1.10.19.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
list(GET downloadStatus 0 error_code)
|
||||
if(NOT error_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed downloading ${JAVA_MOCKITO_JAR}")
|
||||
@@ -350,7 +352,7 @@ if(NOT EXISTS ${JAVA_MOCKITO_JAR})
|
||||
endif()
|
||||
if(NOT EXISTS ${JAVA_CGLIB_JAR})
|
||||
message("Downloading ${JAVA_CGLIB_JAR}")
|
||||
file(DOWNLOAD ${SEARCH_REPO_URL}cglib/cglib/2.2.2/cglib-2.2.2.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
file(DOWNLOAD ${DEPS_URL}/cglib-2.2.2.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
list(GET downloadStatus 0 error_code)
|
||||
if(NOT error_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed downloading ${JAVA_CGLIB_JAR}")
|
||||
@@ -359,7 +361,7 @@ if(NOT EXISTS ${JAVA_CGLIB_JAR})
|
||||
endif()
|
||||
if(NOT EXISTS ${JAVA_ASSERTJ_JAR})
|
||||
message("Downloading ${JAVA_ASSERTJ_JAR}")
|
||||
file(DOWNLOAD ${CENTRAL_REPO_URL}org/assertj/assertj-core/1.7.1/assertj-core-1.7.1.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
file(DOWNLOAD ${DEPS_URL}/assertj-core-1.7.1.jar ${JAVA_TMP_JAR} STATUS downloadStatus)
|
||||
list(GET downloadStatus 0 error_code)
|
||||
if(NOT error_code EQUAL 0)
|
||||
message(FATAL_ERROR "Failed downloading ${JAVA_ASSERTJ_JAR}")
|
||||
|
||||
+17
-10
@@ -213,16 +213,23 @@ ifneq ($(DEBUG_LEVEL),0)
|
||||
JAVAC_ARGS = -Xlint:deprecation -Xlint:unchecked
|
||||
endif
|
||||
|
||||
SEARCH_REPO_URL?=http://search.maven.org/remotecontent?filepath=
|
||||
CENTRAL_REPO_URL?=http://central.maven.org/maven2/
|
||||
# This is a URL for artifacts from a "fake" release on pdillinger's fork,
|
||||
# so as not to put binaries in git (ew). We should move to hosting these
|
||||
# under the facebook account on github, or something else more reliable
|
||||
# than maven.org, which has been failing frequently from Travis.
|
||||
DEPS_URL?=https://github.com/pdillinger/rocksdb/releases/download/v6.6.x-java-deps
|
||||
|
||||
clean:
|
||||
$(AM_V_at)rm -rf include/*
|
||||
$(AM_V_at)rm -rf test-libs/
|
||||
clean: clean-not-downloaded clean-downloaded
|
||||
|
||||
clean-not-downloaded:
|
||||
$(AM_V_at)rm -rf $(NATIVE_INCLUDE)
|
||||
$(AM_V_at)rm -rf $(OUTPUT)
|
||||
$(AM_V_at)rm -rf $(BENCHMARK_OUTPUT)
|
||||
$(AM_V_at)rm -rf $(SAMPLES_OUTPUT)
|
||||
|
||||
clean-downloaded:
|
||||
$(AM_V_at)rm -rf $(JAVA_TEST_LIBDIR)
|
||||
|
||||
|
||||
javadocs: java
|
||||
$(AM_V_GEN)mkdir -p $(JAVADOC)
|
||||
@@ -279,11 +286,11 @@ optimistic_transaction_sample: java
|
||||
|
||||
resolve_test_deps:
|
||||
test -d "$(JAVA_TEST_LIBDIR)" || mkdir -p "$(JAVA_TEST_LIBDIR)"
|
||||
test -s "$(JAVA_JUNIT_JAR)" || cp $(MVN_LOCAL)/junit/junit/4.12/junit-4.12.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o $(JAVA_JUNIT_JAR) $(SEARCH_REPO_URL)junit/junit/4.12/junit-4.12.jar
|
||||
test -s "$(JAVA_HAMCR_JAR)" || cp $(MVN_LOCAL)/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o $(JAVA_HAMCR_JAR) $(SEARCH_REPO_URL)org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar
|
||||
test -s "$(JAVA_MOCKITO_JAR)" || cp $(MVN_LOCAL)/org/mockito/mockito-all/1.10.19/mockito-all-1.10.19.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o "$(JAVA_MOCKITO_JAR)" $(SEARCH_REPO_URL)org/mockito/mockito-all/1.10.19/mockito-all-1.10.19.jar
|
||||
test -s "$(JAVA_CGLIB_JAR)" || cp $(MVN_LOCAL)/cglib/cglib/2.2.2/cglib-2.2.2.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o "$(JAVA_CGLIB_JAR)" $(SEARCH_REPO_URL)cglib/cglib/2.2.2/cglib-2.2.2.jar
|
||||
test -s "$(JAVA_ASSERTJ_JAR)" || cp $(MVN_LOCAL)/org/assertj/assertj-core/1.7.1/assertj-core-1.7.1.jar $(JAVA_TEST_LIBDIR) || curl -k -L -o "$(JAVA_ASSERTJ_JAR)" $(CENTRAL_REPO_URL)org/assertj/assertj-core/1.7.1/assertj-core-1.7.1.jar
|
||||
test -s "$(JAVA_JUNIT_JAR)" || cp $(MVN_LOCAL)/junit/junit/4.12/junit-4.12.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output $(JAVA_JUNIT_JAR) --location $(DEPS_URL)/junit-4.12.jar
|
||||
test -s "$(JAVA_HAMCR_JAR)" || cp $(MVN_LOCAL)/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output $(JAVA_HAMCR_JAR) --location $(DEPS_URL)/hamcrest-core-1.3.jar
|
||||
test -s "$(JAVA_MOCKITO_JAR)" || cp $(MVN_LOCAL)/org/mockito/mockito-all/1.10.19/mockito-all-1.10.19.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_MOCKITO_JAR)" --location $(DEPS_URL)/mockito-all-1.10.19.jar
|
||||
test -s "$(JAVA_CGLIB_JAR)" || cp $(MVN_LOCAL)/cglib/cglib/2.2.2/cglib-2.2.2.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_CGLIB_JAR)" --location $(DEPS_URL)/cglib-2.2.2.jar
|
||||
test -s "$(JAVA_ASSERTJ_JAR)" || cp $(MVN_LOCAL)/org/assertj/assertj-core/1.7.1/assertj-core-1.7.1.jar $(JAVA_TEST_LIBDIR) || curl --fail --insecure --output "$(JAVA_ASSERTJ_JAR)" --location $(DEPS_URL)/assertj-core-1.7.1.jar
|
||||
|
||||
java_test: java resolve_test_deps
|
||||
$(AM_V_GEN)mkdir -p $(TEST_CLASSES)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
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).
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# JMH Benchmarks for RocksJava
|
||||
|
||||
These are micro-benchmarks for RocksJava functionality, using [JMH (Java Microbenchmark Harness)](https://openjdk.java.net/projects/code-tools/jmh/).
|
||||
|
||||
## Compiling
|
||||
|
||||
**Note**: This uses a specific build of RocksDB that is set in the `<version>` element of the `dependencies` section of the `pom.xml` file. If you are testing local changes you should build and install a SNAPSHOT version of rocksdbjni, and update the `pom.xml` of rocksdbjni-jmh file to test with this.
|
||||
|
||||
```bash
|
||||
$ mvn package
|
||||
```
|
||||
|
||||
## Running
|
||||
```bash
|
||||
$ java -jar target/rocksdbjni-jmh-1.0-SNAPSHOT-benchmarks.jar
|
||||
```
|
||||
|
||||
NOTE: you can append `-help` to the command above to see all of the JMH runtime options.
|
||||
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.rocksdb</groupId>
|
||||
<artifactId>rocksdbjni-jmh</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<url>http://rocksdb.org/</url>
|
||||
|
||||
<name>rocksdbjni-jmh</name>
|
||||
<description>JMH Benchmarks for RocksDB Java API</description>
|
||||
|
||||
<organization>
|
||||
<name>Facebook, Inc.</name>
|
||||
<url>https://www.facebook.com</url>
|
||||
</organization>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
<license>
|
||||
<name>GNU General Public License, version 2</name>
|
||||
<url>http://www.gnu.org/licenses/gpl-2.0.html</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/facebook/rocksdb.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:facebook/rocksdb.git</developerConnection>
|
||||
<url>http://github.com/facebook/rocksdb/</url>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<project.build.source>1.7</project.build.source>
|
||||
<project.build.target>1.7</project.build.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<jmh.version>1.22</jmh.version>
|
||||
<uberjar.name>benchmarks</uberjar.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.rocksdb</groupId>
|
||||
<artifactId>rocksdbjni</artifactId>
|
||||
<version>6.4.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-core</artifactId>
|
||||
<version>${jmh.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-generator-annprocess</artifactId>
|
||||
<version>${jmh.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>${project.build.source}</source>
|
||||
<target>${project.build.target}</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>3.0</version>
|
||||
<inherited>true</inherited>
|
||||
<configuration>
|
||||
<header>LICENSE-HEADER.txt</header>
|
||||
<failIfMissing>true</failIfMissing>
|
||||
<aggregate>true</aggregate>
|
||||
<strictCheck>true</strictCheck>
|
||||
<excludes>
|
||||
<exclude>pom.xml</exclude>
|
||||
</excludes>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<finalName>${project.artifactId}-${project.version}-${uberjar.name}</finalName>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.openjdk.jmh.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<!--
|
||||
Shading signed JARs will fail without this.
|
||||
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
|
||||
-->
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.jmh;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.BytewiseComparator;
|
||||
import org.rocksdb.util.DirectBytewiseComparator;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
import org.rocksdb.util.ReverseBytewiseComparator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class ComparatorBenchmarks {
|
||||
|
||||
@Param({
|
||||
"native_bytewise",
|
||||
"native_reverse_bytewise",
|
||||
"java_bytewise_adaptive_mutex",
|
||||
"java_bytewise_non-adaptive_mutex",
|
||||
"java_reverse_bytewise_adaptive_mutex",
|
||||
"java_reverse_bytewise_non-adaptive_mutex",
|
||||
"java_direct_bytewise_adaptive_mutex",
|
||||
"java_direct_bytewise_non-adaptive_mutex"
|
||||
|
||||
})
|
||||
public String comparatorName;
|
||||
|
||||
Path dbDir;
|
||||
ComparatorOptions comparatorOptions;
|
||||
AbstractComparator comparator;
|
||||
Options options;
|
||||
RocksDB db;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() throws IOException, RocksDBException {
|
||||
RocksDB.loadLibrary();
|
||||
|
||||
dbDir = Files.createTempDirectory("rocksjava-comparator-benchmarks");
|
||||
|
||||
options = new Options()
|
||||
.setCreateIfMissing(true);
|
||||
if (comparatorName == null || "native_bytewise".equals(comparatorName)) {
|
||||
options.setComparator(BuiltinComparator.BYTEWISE_COMPARATOR);
|
||||
} else if ("native_reverse_bytewise".equals(comparatorName)) {
|
||||
options.setComparator(BuiltinComparator.REVERSE_BYTEWISE_COMPARATOR);
|
||||
} else if ("java_bytewise_adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(true);
|
||||
comparator = new BytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else if ("java_bytewise_non-adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(false);
|
||||
comparator = new BytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else if ("java_reverse_bytewise_adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(true);
|
||||
comparator = new ReverseBytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else if ("java_reverse_bytewise_non-adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(false);
|
||||
comparator = new ReverseBytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else if ("java_direct_bytewise_adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(true);
|
||||
comparator = new DirectBytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else if ("java_direct_bytewise_non-adaptive_mutex".equals(comparatorName)) {
|
||||
comparatorOptions = new ComparatorOptions()
|
||||
.setUseAdaptiveMutex(false);
|
||||
comparator = new DirectBytewiseComparator(comparatorOptions);
|
||||
options.setComparator(comparator);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown comparator name: " + comparatorName);
|
||||
}
|
||||
|
||||
db = RocksDB.open(options, dbDir.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
public void cleanup() throws IOException {
|
||||
db.close();
|
||||
if (comparator != null) {
|
||||
comparator.close();
|
||||
}
|
||||
if (comparatorOptions != null) {
|
||||
comparatorOptions.close();
|
||||
}
|
||||
options.close();
|
||||
FileUtils.delete(dbDir);
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class Counter {
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
public int next() {
|
||||
return count.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Benchmark
|
||||
public void put(final Counter counter) throws RocksDBException {
|
||||
final int i = counter.next();
|
||||
db.put(ba("key" + i), ba("value" + i));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.jmh;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class GetBenchmarks {
|
||||
|
||||
@Param({
|
||||
"no_column_family",
|
||||
"1_column_family",
|
||||
"20_column_families",
|
||||
"100_column_families"
|
||||
})
|
||||
String columnFamilyTestType;
|
||||
|
||||
@Param("100000")
|
||||
int keyCount;
|
||||
|
||||
Path dbDir;
|
||||
DBOptions options;
|
||||
int cfs = 0; // number of column families
|
||||
private AtomicInteger cfHandlesIdx;
|
||||
ColumnFamilyHandle[] cfHandles;
|
||||
RocksDB db;
|
||||
private final AtomicInteger keyIndex = new AtomicInteger();
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() throws IOException, RocksDBException {
|
||||
RocksDB.loadLibrary();
|
||||
|
||||
dbDir = Files.createTempDirectory("rocksjava-get-benchmarks");
|
||||
|
||||
options = new DBOptions()
|
||||
.setCreateIfMissing(true)
|
||||
.setCreateMissingColumnFamilies(true);
|
||||
|
||||
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
|
||||
|
||||
if ("1_column_family".equals(columnFamilyTestType)) {
|
||||
cfs = 1;
|
||||
} else if ("20_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 20;
|
||||
} else if ("100_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 100;
|
||||
}
|
||||
|
||||
if (cfs > 0) {
|
||||
cfHandlesIdx = new AtomicInteger(1);
|
||||
for (int i = 1; i <= cfs; i++) {
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
|
||||
}
|
||||
}
|
||||
|
||||
final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
|
||||
db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
|
||||
cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
|
||||
|
||||
// store initial data for retrieving via get
|
||||
for (int i = 0; i < cfs; i++) {
|
||||
for (int j = 0; j < keyCount; j++) {
|
||||
db.put(cfHandles[i], ba("key" + j), ba("value" + j));
|
||||
}
|
||||
}
|
||||
|
||||
try (final FlushOptions flushOptions = new FlushOptions()
|
||||
.setWaitForFlush(true)) {
|
||||
db.flush(flushOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
public void cleanup() throws IOException {
|
||||
for (final ColumnFamilyHandle cfHandle : cfHandles) {
|
||||
cfHandle.close();
|
||||
}
|
||||
db.close();
|
||||
options.close();
|
||||
FileUtils.delete(dbDir);
|
||||
}
|
||||
|
||||
private ColumnFamilyHandle getColumnFamily() {
|
||||
if (cfs == 0) {
|
||||
return cfHandles[0];
|
||||
} else if (cfs == 1) {
|
||||
return cfHandles[1];
|
||||
} else {
|
||||
int idx = cfHandlesIdx.getAndIncrement();
|
||||
if (idx > cfs) {
|
||||
cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
|
||||
idx = 0;
|
||||
}
|
||||
return cfHandles[idx];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the next position in the index.
|
||||
*/
|
||||
private int next() {
|
||||
int idx;
|
||||
int nextIdx;
|
||||
while (true) {
|
||||
idx = keyIndex.get();
|
||||
nextIdx = idx + 1;
|
||||
if (nextIdx >= keyCount) {
|
||||
nextIdx = 0;
|
||||
}
|
||||
|
||||
if (keyIndex.compareAndSet(idx, nextIdx)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public byte[] get() throws RocksDBException {
|
||||
final int keyIdx = next();
|
||||
return db.get(getColumnFamily(), ba("key" + keyIdx));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.jmh;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
import static org.rocksdb.util.KVUtils.keys;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class MultiGetBenchmarks {
|
||||
|
||||
@Param({
|
||||
"no_column_family",
|
||||
"1_column_family",
|
||||
"20_column_families",
|
||||
"100_column_families"
|
||||
})
|
||||
String columnFamilyTestType;
|
||||
|
||||
@Param("100000")
|
||||
int keyCount;
|
||||
|
||||
@Param({
|
||||
"10",
|
||||
"100",
|
||||
"1000",
|
||||
"10000",
|
||||
})
|
||||
int multiGetSize;
|
||||
|
||||
Path dbDir;
|
||||
DBOptions options;
|
||||
int cfs = 0; // number of column families
|
||||
private AtomicInteger cfHandlesIdx;
|
||||
ColumnFamilyHandle[] cfHandles;
|
||||
RocksDB db;
|
||||
private final AtomicInteger keyIndex = new AtomicInteger();
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() throws IOException, RocksDBException {
|
||||
RocksDB.loadLibrary();
|
||||
|
||||
dbDir = Files.createTempDirectory("rocksjava-multiget-benchmarks");
|
||||
|
||||
options = new DBOptions()
|
||||
.setCreateIfMissing(true)
|
||||
.setCreateMissingColumnFamilies(true);
|
||||
|
||||
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
|
||||
|
||||
if ("1_column_family".equals(columnFamilyTestType)) {
|
||||
cfs = 1;
|
||||
} else if ("20_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 20;
|
||||
} else if ("100_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 100;
|
||||
}
|
||||
|
||||
if (cfs > 0) {
|
||||
cfHandlesIdx = new AtomicInteger(1);
|
||||
for (int i = 1; i <= cfs; i++) {
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
|
||||
}
|
||||
}
|
||||
|
||||
final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
|
||||
db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
|
||||
cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
|
||||
|
||||
// store initial data for retrieving via get
|
||||
for (int i = 0; i < cfs; i++) {
|
||||
for (int j = 0; j < keyCount; j++) {
|
||||
db.put(cfHandles[i], ba("key" + j), ba("value" + j));
|
||||
}
|
||||
}
|
||||
|
||||
try (final FlushOptions flushOptions = new FlushOptions()
|
||||
.setWaitForFlush(true)) {
|
||||
db.flush(flushOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
public void cleanup() throws IOException {
|
||||
for (final ColumnFamilyHandle cfHandle : cfHandles) {
|
||||
cfHandle.close();
|
||||
}
|
||||
db.close();
|
||||
options.close();
|
||||
FileUtils.delete(dbDir);
|
||||
}
|
||||
|
||||
private ColumnFamilyHandle getColumnFamily() {
|
||||
if (cfs == 0) {
|
||||
return cfHandles[0];
|
||||
} else if (cfs == 1) {
|
||||
return cfHandles[1];
|
||||
} else {
|
||||
int idx = cfHandlesIdx.getAndIncrement();
|
||||
if (idx > cfs) {
|
||||
cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
|
||||
idx = 0;
|
||||
}
|
||||
return cfHandles[idx];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves the next {@inc} positions in the index.
|
||||
*
|
||||
* @param inc the number by which to increment the index
|
||||
* @param limit the limit for the index
|
||||
* @return the index before {@code inc} is added
|
||||
*/
|
||||
private int next(final int inc, final int limit) {
|
||||
int idx;
|
||||
int nextIdx;
|
||||
while (true) {
|
||||
idx = keyIndex.get();
|
||||
nextIdx = idx + inc;
|
||||
if (nextIdx >= limit) {
|
||||
nextIdx = inc;
|
||||
}
|
||||
|
||||
if (keyIndex.compareAndSet(idx, nextIdx)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextIdx >= limit) {
|
||||
return -1;
|
||||
} else {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public List<byte[]> multiGet10() throws RocksDBException {
|
||||
final int fromKeyIdx = next(multiGetSize, keyCount);
|
||||
final List<byte[]> keys = keys(fromKeyIdx, fromKeyIdx + multiGetSize);
|
||||
return db.multiGetAsList(keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.jmh;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.rocksdb.*;
|
||||
import org.rocksdb.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.rocksdb.util.KVUtils.ba;
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public class PutBenchmarks {
|
||||
|
||||
@Param({
|
||||
"no_column_family",
|
||||
"1_column_family",
|
||||
"20_column_families",
|
||||
"100_column_families"
|
||||
})
|
||||
String columnFamilyTestType;
|
||||
|
||||
Path dbDir;
|
||||
DBOptions options;
|
||||
int cfs = 0; // number of column families
|
||||
private AtomicInteger cfHandlesIdx;
|
||||
ColumnFamilyHandle[] cfHandles;
|
||||
RocksDB db;
|
||||
|
||||
@Setup(Level.Trial)
|
||||
public void setup() throws IOException, RocksDBException {
|
||||
RocksDB.loadLibrary();
|
||||
|
||||
dbDir = Files.createTempDirectory("rocksjava-put-benchmarks");
|
||||
|
||||
options = new DBOptions()
|
||||
.setCreateIfMissing(true)
|
||||
.setCreateMissingColumnFamilies(true);
|
||||
|
||||
final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList<>();
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
|
||||
|
||||
if ("1_column_family".equals(columnFamilyTestType)) {
|
||||
cfs = 1;
|
||||
} else if ("20_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 20;
|
||||
} else if ("100_column_families".equals(columnFamilyTestType)) {
|
||||
cfs = 100;
|
||||
}
|
||||
|
||||
if (cfs > 0) {
|
||||
cfHandlesIdx = new AtomicInteger(1);
|
||||
for (int i = 1; i <= cfs; i++) {
|
||||
cfDescriptors.add(new ColumnFamilyDescriptor(ba("cf" + i)));
|
||||
}
|
||||
}
|
||||
|
||||
final List<ColumnFamilyHandle> cfHandlesList = new ArrayList<>(cfDescriptors.size());
|
||||
db = RocksDB.open(options, dbDir.toAbsolutePath().toString(), cfDescriptors, cfHandlesList);
|
||||
cfHandles = cfHandlesList.toArray(new ColumnFamilyHandle[0]);
|
||||
}
|
||||
|
||||
@TearDown(Level.Trial)
|
||||
public void cleanup() throws IOException {
|
||||
for (final ColumnFamilyHandle cfHandle : cfHandles) {
|
||||
cfHandle.close();
|
||||
}
|
||||
db.close();
|
||||
options.close();
|
||||
FileUtils.delete(dbDir);
|
||||
}
|
||||
|
||||
private ColumnFamilyHandle getColumnFamily() {
|
||||
if (cfs == 0) {
|
||||
return cfHandles[0];
|
||||
} else if (cfs == 1) {
|
||||
return cfHandles[1];
|
||||
} else {
|
||||
int idx = cfHandlesIdx.getAndIncrement();
|
||||
if (idx > cfs) {
|
||||
cfHandlesIdx.set(1); // doesn't ensure a perfect distribution, but it's ok
|
||||
idx = 0;
|
||||
}
|
||||
return cfHandles[idx];
|
||||
}
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class Counter {
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
public int next() {
|
||||
return count.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void put(final ComparatorBenchmarks.Counter counter) throws RocksDBException {
|
||||
final int i = counter.next();
|
||||
db.put(getColumnFamily(), ba("key" + i), ba("value" + i));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public final class FileUtils {
|
||||
private static final SimpleFileVisitor<Path> DELETE_DIR_VISITOR = new DeleteDirVisitor();
|
||||
|
||||
/**
|
||||
* Deletes a path from the filesystem
|
||||
*
|
||||
* If the path is a directory its contents
|
||||
* will be recursively deleted before it itself
|
||||
* is deleted.
|
||||
*
|
||||
* Note that removal of a directory is not an atomic-operation
|
||||
* and so if an error occurs during removal, some of the directories
|
||||
* descendants may have already been removed
|
||||
*
|
||||
* @param path the path to delete.
|
||||
*
|
||||
* @throws IOException if an error occurs whilst removing a file or directory
|
||||
*/
|
||||
public static void delete(final Path path) throws IOException {
|
||||
if (!Files.isDirectory(path)) {
|
||||
Files.deleteIfExists(path);
|
||||
} else {
|
||||
Files.walkFileTree(path, DELETE_DIR_VISITOR);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteDirVisitor extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
|
||||
Files.deleteIfExists(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
|
||||
if (exc != null) {
|
||||
throw exc;
|
||||
}
|
||||
|
||||
Files.deleteIfExists(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
package org.rocksdb.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public final class KVUtils {
|
||||
|
||||
/**
|
||||
* Get a byte array from a string.
|
||||
*
|
||||
* Assumes UTF-8 encoding
|
||||
*
|
||||
* @param string the string
|
||||
*
|
||||
* @return the bytes.
|
||||
*/
|
||||
public static byte[] ba(final String string) {
|
||||
return string.getBytes(UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string from a byte array.
|
||||
*
|
||||
* Assumes UTF-8 encoding
|
||||
*
|
||||
* @param bytes the bytes
|
||||
*
|
||||
* @return the string.
|
||||
*/
|
||||
public static String str(final byte[] bytes) {
|
||||
return new String(bytes, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of keys where the keys are named key1..key1+N
|
||||
* in the range of {@code from} to {@code to} i.e. keyFrom..keyTo.
|
||||
*
|
||||
* @param from the first key
|
||||
* @param to the last key
|
||||
*
|
||||
* @return the array of keys
|
||||
*/
|
||||
public static List<byte[]> keys(final int from, final int to) {
|
||||
final List<byte[]> keys = new ArrayList<>(to - from);
|
||||
for (int i = from; i < to; i++) {
|
||||
keys.add(ba("key" + i));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
@@ -425,6 +425,7 @@ struct BlockBasedTableBuilder::Rep {
|
||||
context.column_family_name = column_family_name;
|
||||
context.compaction_style = ioptions.compaction_style;
|
||||
context.level_at_creation = level_at_creation;
|
||||
context.info_log = ioptions.info_log;
|
||||
filter_builder.reset(CreateFilterBlockBuilder(
|
||||
ioptions, moptions, context, use_delta_encoding_for_index_values,
|
||||
p_index_builder_));
|
||||
|
||||
@@ -157,6 +157,8 @@ size_t TailPrefetchStats::GetSuggestedPrefetchSize() {
|
||||
return std::min(kMaxPrefetchSize, max_qualified_size);
|
||||
}
|
||||
|
||||
// TODO(myabandeh): We should return an error instead of silently changing the
|
||||
// options
|
||||
BlockBasedTableFactory::BlockBasedTableFactory(
|
||||
const BlockBasedTableOptions& _table_options)
|
||||
: table_options_(_table_options) {
|
||||
@@ -184,6 +186,11 @@ BlockBasedTableFactory::BlockBasedTableFactory(
|
||||
if (table_options_.index_block_restart_interval < 1) {
|
||||
table_options_.index_block_restart_interval = 1;
|
||||
}
|
||||
if (table_options_.index_type == BlockBasedTableOptions::kHashSearch &&
|
||||
table_options_.index_block_restart_interval != 1) {
|
||||
// Currently kHashSearch is incompatible with index_block_restart_interval > 1
|
||||
table_options_.index_block_restart_interval = 1;
|
||||
}
|
||||
if (table_options_.partition_filters &&
|
||||
table_options_.index_type !=
|
||||
BlockBasedTableOptions::kTwoLevelIndexSearch) {
|
||||
|
||||
@@ -2455,6 +2455,7 @@ void BlockBasedTable::RetrieveMultipleBlocks(
|
||||
s = rocksdb::VerifyChecksum(footer.checksum(),
|
||||
req.result.data() + req_offset,
|
||||
handle.size() + 1, expected);
|
||||
TEST_SYNC_POINT_CALLBACK("RetrieveMultipleBlocks:VerifyChecksum", &s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,11 @@ class FastLocalBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
return num_cache_lines * 64 + /*metadata*/ 5;
|
||||
}
|
||||
|
||||
double EstimatedFpRate(size_t keys, size_t bytes) override {
|
||||
return FastLocalBloomImpl::EstimatedFpRate(keys, bytes - /*metadata*/ 5,
|
||||
num_probes_, /*hash bits*/ 64);
|
||||
}
|
||||
|
||||
private:
|
||||
void AddAllEntries(char* data, uint32_t len) {
|
||||
// Simple version without prefetching:
|
||||
@@ -194,7 +199,7 @@ using LegacyBloomImpl = LegacyLocalityBloomImpl</*ExtraRotates*/ false>;
|
||||
|
||||
class LegacyBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
public:
|
||||
explicit LegacyBloomBitsBuilder(const int bits_per_key);
|
||||
explicit LegacyBloomBitsBuilder(const int bits_per_key, Logger* info_log);
|
||||
|
||||
// No Copy allowed
|
||||
LegacyBloomBitsBuilder(const LegacyBloomBitsBuilder&) = delete;
|
||||
@@ -214,10 +219,16 @@ class LegacyBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
return CalculateSpace(num_entry, &dont_care1, &dont_care2);
|
||||
}
|
||||
|
||||
double EstimatedFpRate(size_t keys, size_t bytes) override {
|
||||
return LegacyBloomImpl::EstimatedFpRate(keys, bytes - /*metadata*/ 5,
|
||||
num_probes_);
|
||||
}
|
||||
|
||||
private:
|
||||
int bits_per_key_;
|
||||
int num_probes_;
|
||||
std::vector<uint32_t> hash_entries_;
|
||||
Logger* info_log_;
|
||||
|
||||
// Get totalbits that optimized for cpu cache line
|
||||
uint32_t GetTotalBitsForLocality(uint32_t total_bits);
|
||||
@@ -234,9 +245,11 @@ class LegacyBloomBitsBuilder : public BuiltinFilterBitsBuilder {
|
||||
void AddHash(uint32_t h, char* data, uint32_t num_lines, uint32_t total_bits);
|
||||
};
|
||||
|
||||
LegacyBloomBitsBuilder::LegacyBloomBitsBuilder(const int bits_per_key)
|
||||
LegacyBloomBitsBuilder::LegacyBloomBitsBuilder(const int bits_per_key,
|
||||
Logger* info_log)
|
||||
: bits_per_key_(bits_per_key),
|
||||
num_probes_(LegacyNoLocalityBloomImpl::ChooseNumProbes(bits_per_key_)) {
|
||||
num_probes_(LegacyNoLocalityBloomImpl::ChooseNumProbes(bits_per_key_)),
|
||||
info_log_(info_log) {
|
||||
assert(bits_per_key_);
|
||||
}
|
||||
|
||||
@@ -251,14 +264,39 @@ void LegacyBloomBitsBuilder::AddKey(const Slice& key) {
|
||||
|
||||
Slice LegacyBloomBitsBuilder::Finish(std::unique_ptr<const char[]>* buf) {
|
||||
uint32_t total_bits, num_lines;
|
||||
char* data = ReserveSpace(static_cast<int>(hash_entries_.size()), &total_bits,
|
||||
&num_lines);
|
||||
size_t num_entries = hash_entries_.size();
|
||||
char* data =
|
||||
ReserveSpace(static_cast<int>(num_entries), &total_bits, &num_lines);
|
||||
assert(data);
|
||||
|
||||
if (total_bits != 0 && num_lines != 0) {
|
||||
for (auto h : hash_entries_) {
|
||||
AddHash(h, data, num_lines, total_bits);
|
||||
}
|
||||
|
||||
// Check for excessive entries for 32-bit hash function
|
||||
if (num_entries >= /* minimum of 3 million */ 3000000U) {
|
||||
// More specifically, we can detect that the 32-bit hash function
|
||||
// is causing significant increase in FP rate by comparing current
|
||||
// estimated FP rate to what we would get with a normal number of
|
||||
// keys at same memory ratio.
|
||||
double est_fp_rate = LegacyBloomImpl::EstimatedFpRate(
|
||||
num_entries, total_bits / 8, num_probes_);
|
||||
double vs_fp_rate = LegacyBloomImpl::EstimatedFpRate(
|
||||
1U << 16, (1U << 16) * bits_per_key_ / 8, num_probes_);
|
||||
|
||||
if (est_fp_rate >= 1.50 * vs_fp_rate) {
|
||||
// For more details, see
|
||||
// https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter
|
||||
ROCKS_LOG_WARN(
|
||||
info_log_,
|
||||
"Using legacy SST/BBT Bloom filter with excessive key count "
|
||||
"(%.1fM @ %dbpk), causing estimated %.1fx higher filter FP rate. "
|
||||
"Consider using new Bloom with format_version>=5, smaller SST "
|
||||
"file size, or partitioned filters.",
|
||||
num_entries / 1000000.0, bits_per_key_, est_fp_rate / vs_fp_rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
// See BloomFilterPolicy::GetFilterBitsReader for metadata
|
||||
data[total_bits / 8] = static_cast<char>(num_probes_);
|
||||
@@ -419,7 +457,7 @@ const std::vector<BloomFilterPolicy::Mode> BloomFilterPolicy::kAllUserModes = {
|
||||
};
|
||||
|
||||
BloomFilterPolicy::BloomFilterPolicy(double bits_per_key, Mode mode)
|
||||
: mode_(mode) {
|
||||
: mode_(mode), warned_(false) {
|
||||
// Sanitize bits_per_key
|
||||
if (bits_per_key < 1.0) {
|
||||
bits_per_key = 1.0;
|
||||
@@ -527,7 +565,26 @@ FilterBitsBuilder* BloomFilterPolicy::GetBuilderWithContext(
|
||||
case kFastLocalBloom:
|
||||
return new FastLocalBloomBitsBuilder(millibits_per_key_);
|
||||
case kLegacyBloom:
|
||||
return new LegacyBloomBitsBuilder(whole_bits_per_key_);
|
||||
if (whole_bits_per_key_ >= 14 && context.info_log &&
|
||||
!warned_.load(std::memory_order_relaxed)) {
|
||||
warned_ = true;
|
||||
const char* adjective;
|
||||
if (whole_bits_per_key_ >= 20) {
|
||||
adjective = "Dramatic";
|
||||
} else {
|
||||
adjective = "Significant";
|
||||
}
|
||||
// For more details, see
|
||||
// https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter
|
||||
ROCKS_LOG_WARN(
|
||||
context.info_log,
|
||||
"Using legacy Bloom filter with high (%d) bits/key. "
|
||||
"%s filter space and/or accuracy improvement is available "
|
||||
"with format_version>=5.",
|
||||
whole_bits_per_key_, adjective);
|
||||
}
|
||||
return new LegacyBloomBitsBuilder(whole_bits_per_key_,
|
||||
context.info_log);
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -27,6 +28,11 @@ class BuiltinFilterBitsBuilder : public FilterBitsBuilder {
|
||||
// metadata. Passing the result to CalculateNumEntry should
|
||||
// return >= the num_entry passed in.
|
||||
virtual uint32_t CalculateSpace(const int num_entry) = 0;
|
||||
|
||||
// Returns an estimate of the FP rate of the returned filter if
|
||||
// `keys` keys are added and the filter returned by Finish is `bytes`
|
||||
// bytes.
|
||||
virtual double EstimatedFpRate(size_t keys, size_t bytes) = 0;
|
||||
};
|
||||
|
||||
// RocksDB built-in filter policy for Bloom or Bloom-like filters.
|
||||
@@ -125,6 +131,10 @@ class BloomFilterPolicy : public FilterPolicy {
|
||||
// implementation) for building new SST filters.
|
||||
Mode mode_;
|
||||
|
||||
// Whether relevant warnings have been logged already. (Remember so we
|
||||
// only report once per BloomFilterPolicy instance, to keep the noise down.)
|
||||
mutable std::atomic<bool> warned_;
|
||||
|
||||
// For newer Bloom filter implementation(s)
|
||||
FilterBitsReader* GetBloomBitsReader(const Slice& contents) const;
|
||||
};
|
||||
|
||||
@@ -39,6 +39,9 @@ IndexBuilder* IndexBuilder::CreateIndexBuilder(
|
||||
table_opt.index_shortening, /* include_first_key */ false);
|
||||
} break;
|
||||
case BlockBasedTableOptions::kHashSearch: {
|
||||
// Currently kHashSearch is incompatible with index_block_restart_interval
|
||||
// > 1
|
||||
assert(table_opt.index_block_restart_interval == 1);
|
||||
result = new HashIndexBuilder(
|
||||
comparator, int_key_slice_transform,
|
||||
table_opt.index_block_restart_interval, table_opt.format_version,
|
||||
|
||||
@@ -47,6 +47,7 @@ class MockBlockBasedTableTester {
|
||||
context.column_family_name = "mock_cf";
|
||||
context.compaction_style = ioptions_.compaction_style;
|
||||
context.level_at_creation = kMockLevel;
|
||||
context.info_log = ioptions_.info_log;
|
||||
return BloomFilterPolicy::GetBuilderFromContext(context);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -351,8 +351,7 @@ void RandomInitCFOptions(ColumnFamilyOptions* cf_opt, DBOptions& db_options,
|
||||
// size_t options
|
||||
cf_opt->arena_block_size = rnd->Uniform(10000);
|
||||
cf_opt->inplace_update_num_locks = rnd->Uniform(10000);
|
||||
cf_opt->max_successive_merges =
|
||||
db_options.allow_concurrent_memtable_write ? 0 : rnd->Uniform(10000);
|
||||
cf_opt->max_successive_merges = rnd->Uniform(10000);
|
||||
cf_opt->memtable_huge_page_size = rnd->Uniform(10000);
|
||||
cf_opt->write_buffer_size = rnd->Uniform(10000);
|
||||
|
||||
|
||||
+22
-13
@@ -1,21 +1,30 @@
|
||||
set(TOOLS
|
||||
set(CORE_TOOLS
|
||||
sst_dump.cc
|
||||
db_sanity_test.cc
|
||||
write_stress.cc
|
||||
ldb.cc
|
||||
db_repl_stress.cc
|
||||
dump/rocksdb_dump.cc
|
||||
dump/rocksdb_undump.cc)
|
||||
foreach(src ${TOOLS})
|
||||
ldb.cc)
|
||||
foreach(src ${CORE_TOOLS})
|
||||
get_filename_component(exename ${src} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX}
|
||||
${src})
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${ROCKSDB_LIB})
|
||||
list(APPEND tool_deps ${exename})
|
||||
list(APPEND core_tool_deps ${exename})
|
||||
endforeach()
|
||||
|
||||
list(APPEND tool_deps)
|
||||
if(WITH_TOOLS)
|
||||
set(TOOLS
|
||||
db_sanity_test.cc
|
||||
write_stress.cc
|
||||
db_repl_stress.cc
|
||||
dump/rocksdb_dump.cc
|
||||
dump/rocksdb_undump.cc)
|
||||
foreach(src ${TOOLS})
|
||||
get_filename_component(exename ${src} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX}
|
||||
${src})
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} ${ROCKSDB_LIB})
|
||||
list(APPEND tool_deps ${exename})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(ldb_tests
|
||||
COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/ldb_tests.py
|
||||
DEPENDS ldb)
|
||||
add_custom_target(ldb_tests
|
||||
COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/ldb_tests.py
|
||||
DEPENDS ldb)
|
||||
endif()
|
||||
@@ -92,7 +92,7 @@ default_params = {
|
||||
[t * 16384 if t < 3 else 1024 * 1024 * 1024 for t in range(1, 30)]),
|
||||
# Sync mode might make test runs slower so running it in a smaller chance
|
||||
"sync" : lambda : random.choice(
|
||||
[0 if t == 0 else 1 for t in range(1, 20)]),
|
||||
[1 if t == 0 else 0 for t in range(0, 20)]),
|
||||
# Disable compation_readahead_size because the test is not passing.
|
||||
#"compaction_readahead_size" : lambda : random.choice(
|
||||
# [0, 0, 1024 * 1024]),
|
||||
@@ -105,7 +105,9 @@ default_params = {
|
||||
"level_compaction_dynamic_level_bytes" : True,
|
||||
"verify_checksum_one_in": 1000000,
|
||||
"verify_db_one_in": 100000,
|
||||
"continuous_verification_interval" : 0
|
||||
"continuous_verification_interval" : 0,
|
||||
"max_key_len": 3,
|
||||
"key_len_percent_dist": "1,30,69"
|
||||
}
|
||||
|
||||
_TEST_DIR_ENV_VAR = 'TEST_TMPDIR'
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <cmath>
|
||||
|
||||
#include "rocksdb/slice.h"
|
||||
#include "util/hash.h"
|
||||
@@ -20,6 +21,70 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class BloomMath {
|
||||
public:
|
||||
// False positive rate of a standard Bloom filter, for given ratio of
|
||||
// filter memory bits to added keys, and number of probes per operation.
|
||||
// (The false positive rate is effectively independent of scale, assuming
|
||||
// the implementation scales OK.)
|
||||
static double StandardFpRate(double bits_per_key, int num_probes) {
|
||||
// Standard very-good-estimate formula. See
|
||||
// https://en.wikipedia.org/wiki/Bloom_filter#Probability_of_false_positives
|
||||
return std::pow(1.0 - std::exp(-num_probes / bits_per_key), num_probes);
|
||||
}
|
||||
|
||||
// False positive rate of a "blocked"/"shareded"/"cache-local" Bloom filter,
|
||||
// for given ratio of filter memory bits to added keys, number of probes per
|
||||
// operation (all within the given block or cache line size), and block or
|
||||
// cache line size.
|
||||
static double CacheLocalFpRate(double bits_per_key, int num_probes,
|
||||
int cache_line_bits) {
|
||||
double keys_per_cache_line = cache_line_bits / bits_per_key;
|
||||
// A reasonable estimate is the average of the FP rates for one standard
|
||||
// deviation above and below the mean bucket occupancy. See
|
||||
// https://github.com/facebook/rocksdb/wiki/RocksDB-Bloom-Filter#the-math
|
||||
double keys_stddev = std::sqrt(keys_per_cache_line);
|
||||
double crowded_fp = StandardFpRate(
|
||||
cache_line_bits / (keys_per_cache_line + keys_stddev), num_probes);
|
||||
double uncrowded_fp = StandardFpRate(
|
||||
cache_line_bits / (keys_per_cache_line - keys_stddev), num_probes);
|
||||
return (crowded_fp + uncrowded_fp) / 2;
|
||||
}
|
||||
|
||||
// False positive rate of querying a new item against `num_keys` items, all
|
||||
// hashed to `fingerprint_bits` bits. (This assumes the fingerprint hashes
|
||||
// themselves are stored losslessly. See Section 4 of
|
||||
// http://www.ccs.neu.edu/home/pete/pub/bloom-filters-verification.pdf)
|
||||
static double FingerprintFpRate(size_t num_keys, int fingerprint_bits) {
|
||||
double inv_fingerprint_space = std::pow(0.5, fingerprint_bits);
|
||||
// Base estimate assumes each key maps to a unique fingerprint.
|
||||
// Could be > 1 in extreme cases.
|
||||
double base_estimate = num_keys * inv_fingerprint_space;
|
||||
// To account for potential overlap, we choose between two formulas
|
||||
if (base_estimate > 0.0001) {
|
||||
// A very good formula assuming we don't construct a floating point
|
||||
// number extremely close to 1. Always produces a probability < 1.
|
||||
return 1.0 - std::exp(-base_estimate);
|
||||
} else {
|
||||
// A very good formula when base_estimate is far below 1. (Subtract
|
||||
// away the integral-approximated sum that some key has same hash as
|
||||
// one coming before it in a list.)
|
||||
return base_estimate - (base_estimate * base_estimate * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the probably of either of two independent(-ish) events
|
||||
// happening, given their probabilities. (This is useful for combining
|
||||
// results from StandardFpRate or CacheLocalFpRate with FingerprintFpRate
|
||||
// for a hash-efficient Bloom filter's FP rate. See Section 4 of
|
||||
// http://www.ccs.neu.edu/home/pete/pub/bloom-filters-verification.pdf)
|
||||
static double IndependentProbabilitySum(double rate1, double rate2) {
|
||||
// Use formula that avoids floating point extremely close to 1 if
|
||||
// rates are extremely small.
|
||||
return rate1 + rate2 - (rate1 * rate2);
|
||||
}
|
||||
};
|
||||
|
||||
// A fast, flexible, and accurate cache-local Bloom implementation with
|
||||
// SIMD-optimized query performance (currently using AVX2 on Intel). Write
|
||||
// performance and non-SIMD read are very good, benefiting from fastrange32
|
||||
@@ -72,6 +137,16 @@ namespace rocksdb {
|
||||
//
|
||||
class FastLocalBloomImpl {
|
||||
public:
|
||||
// NOTE: this has only been validated to enough accuracy for producing
|
||||
// reasonable warnings / user feedback, not for making functional decisions.
|
||||
static double EstimatedFpRate(size_t keys, size_t bytes, int num_probes,
|
||||
int hash_bits) {
|
||||
return BloomMath::IndependentProbabilitySum(
|
||||
BloomMath::CacheLocalFpRate(8.0 * bytes / keys, num_probes,
|
||||
/*cache line bits*/ 512),
|
||||
BloomMath::FingerprintFpRate(keys, hash_bits));
|
||||
}
|
||||
|
||||
static inline int ChooseNumProbes(int millibits_per_key) {
|
||||
// Since this implementation can (with AVX2) make up to 8 probes
|
||||
// for the same cost, we pick the most accurate num_probes, based
|
||||
@@ -328,6 +403,26 @@ class LegacyLocalityBloomImpl {
|
||||
}
|
||||
|
||||
public:
|
||||
// NOTE: this has only been validated to enough accuracy for producing
|
||||
// reasonable warnings / user feedback, not for making functional decisions.
|
||||
static double EstimatedFpRate(size_t keys, size_t bytes, int num_probes) {
|
||||
double bits_per_key = 8.0 * bytes / keys;
|
||||
double filter_rate = BloomMath::CacheLocalFpRate(bits_per_key, num_probes,
|
||||
/*cache line bits*/ 512);
|
||||
if (!ExtraRotates) {
|
||||
// Good estimate of impact of flaw in index computation.
|
||||
// Adds roughly 0.002 around 50 bits/key and 0.001 around 100 bits/key.
|
||||
// The + 22 shifts it nicely to fit for lower bits/key.
|
||||
filter_rate += 0.1 / (bits_per_key * 0.75 + 22);
|
||||
} else {
|
||||
// Not yet validated
|
||||
assert(false);
|
||||
}
|
||||
// Always uses 32-bit hash
|
||||
double fingerprint_rate = BloomMath::FingerprintFpRate(keys, 32);
|
||||
return BloomMath::IndependentProbabilitySum(filter_rate, fingerprint_rate);
|
||||
}
|
||||
|
||||
static inline void AddHash(uint32_t h, uint32_t num_lines, int num_probes,
|
||||
char *data, int log2_cache_line_bytes) {
|
||||
const int log2_cache_line_bits = log2_cache_line_bytes + 3;
|
||||
|
||||
+26
-3
@@ -26,6 +26,7 @@ int main() {
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stderr_logger.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
@@ -94,14 +95,20 @@ void _always_assert_fail(int line, const char *file, const char *expr) {
|
||||
#define ALWAYS_ASSERT(cond) \
|
||||
((cond) ? (void)0 : ::_always_assert_fail(__LINE__, __FILE__, #cond))
|
||||
|
||||
#ifndef NDEBUG
|
||||
// This could affect build times enough that we should not include it for
|
||||
// accurate speed tests
|
||||
#define PREDICT_FP_RATE
|
||||
#endif
|
||||
|
||||
using rocksdb::Arena;
|
||||
using rocksdb::BlockContents;
|
||||
using rocksdb::BloomFilterPolicy;
|
||||
using rocksdb::BloomHash;
|
||||
using rocksdb::BuiltinFilterBitsBuilder;
|
||||
using rocksdb::CachableEntry;
|
||||
using rocksdb::EncodeFixed32;
|
||||
using rocksdb::fastrange32;
|
||||
using rocksdb::FilterBitsBuilder;
|
||||
using rocksdb::FilterBitsReader;
|
||||
using rocksdb::FilterBuildingContext;
|
||||
using rocksdb::FullFilterBlockReader;
|
||||
@@ -112,6 +119,7 @@ using rocksdb::ParsedFullFilterBlock;
|
||||
using rocksdb::PlainTableBloomV1;
|
||||
using rocksdb::Random32;
|
||||
using rocksdb::Slice;
|
||||
using rocksdb::StderrLogger;
|
||||
using rocksdb::mock::MockBlockBasedTableTester;
|
||||
|
||||
struct KeyMaker {
|
||||
@@ -243,6 +251,7 @@ struct FilterBench : public MockBlockBasedTableTester {
|
||||
Random32 random_;
|
||||
std::ostringstream fp_rate_report_;
|
||||
Arena arena_;
|
||||
StderrLogger stderr_logger_;
|
||||
|
||||
FilterBench()
|
||||
: MockBlockBasedTableTester(new BloomFilterPolicy(
|
||||
@@ -252,6 +261,7 @@ struct FilterBench : public MockBlockBasedTableTester {
|
||||
for (uint32_t i = 0; i < FLAGS_batch_size; ++i) {
|
||||
kms_.emplace_back(FLAGS_key_size < 8 ? 8 : FLAGS_key_size);
|
||||
}
|
||||
ioptions_.info_log = &stderr_logger_;
|
||||
}
|
||||
|
||||
void Go();
|
||||
@@ -298,10 +308,13 @@ void FilterBench::Go() {
|
||||
|
||||
std::cout << "Building..." << std::endl;
|
||||
|
||||
std::unique_ptr<FilterBitsBuilder> builder;
|
||||
std::unique_ptr<BuiltinFilterBitsBuilder> builder;
|
||||
|
||||
size_t total_memory_used = 0;
|
||||
size_t total_keys_added = 0;
|
||||
#ifdef PREDICT_FP_RATE
|
||||
double weighted_predicted_fp_rate = 0.0;
|
||||
#endif
|
||||
|
||||
rocksdb::StopWatchNano timer(rocksdb::Env::Default(), true);
|
||||
|
||||
@@ -326,12 +339,17 @@ void FilterBench::Go() {
|
||||
info.filter_ = info.plain_table_bloom_->GetRawData();
|
||||
} else {
|
||||
if (!builder) {
|
||||
builder.reset(GetBuilder());
|
||||
builder.reset(&dynamic_cast<BuiltinFilterBitsBuilder &>(*GetBuilder()));
|
||||
}
|
||||
for (uint32_t i = 0; i < keys_to_add; ++i) {
|
||||
builder->AddKey(kms_[0].Get(filter_id, i));
|
||||
}
|
||||
info.filter_ = builder->Finish(&info.owner_);
|
||||
#ifdef PREDICT_FP_RATE
|
||||
weighted_predicted_fp_rate +=
|
||||
keys_to_add *
|
||||
builder->EstimatedFpRate(keys_to_add, info.filter_.size());
|
||||
#endif
|
||||
if (FLAGS_new_builder) {
|
||||
builder.reset();
|
||||
}
|
||||
@@ -358,6 +376,11 @@ void FilterBench::Go() {
|
||||
|
||||
double bpk = total_memory_used * 8.0 / total_keys_added;
|
||||
std::cout << "Bits/key actual: " << bpk << std::endl;
|
||||
#ifdef PREDICT_FP_RATE
|
||||
std::cout << "Predicted FP rate %: "
|
||||
<< 100.0 * (weighted_predicted_fp_rate / total_keys_added)
|
||||
<< std::endl;
|
||||
#endif
|
||||
if (!FLAGS_quick && !FLAGS_best_case) {
|
||||
double tolerable_rate = std::pow(2.0, -(bpk - 1.0) / (1.4 + bpk / 50.0));
|
||||
std::cout << "Best possible FP rate %: " << 100.0 * std::pow(2.0, -bpk)
|
||||
|
||||
@@ -32,8 +32,7 @@ CompactionFilter::Decision BlobIndexCompactionFilterBase::FilterV2(
|
||||
if (!blob_index.IsInlined() &&
|
||||
blob_index.file_number() < context_.next_file_number &&
|
||||
context_.current_blob_files.count(blob_index.file_number()) == 0) {
|
||||
// Corresponding blob file gone. Could have been garbage collected or
|
||||
// evicted by FIFO eviction.
|
||||
// Corresponding blob file gone (most likely, evicted by FIFO eviction).
|
||||
evicted_count_++;
|
||||
evicted_size_ += key.size() + value.size();
|
||||
return Decision::kRemove;
|
||||
|
||||
@@ -116,9 +116,9 @@ class BlobIndexCompactionFilterGC : public BlobIndexCompactionFilterBase {
|
||||
// that creates non-GC filters.
|
||||
class BlobIndexCompactionFilterFactoryBase : public CompactionFilterFactory {
|
||||
public:
|
||||
BlobIndexCompactionFilterFactoryBase(BlobDBImpl* blob_db_impl, Env* env,
|
||||
Statistics* statistics)
|
||||
: blob_db_impl_(blob_db_impl), env_(env), statistics_(statistics) {}
|
||||
BlobIndexCompactionFilterFactoryBase(BlobDBImpl* _blob_db_impl, Env* _env,
|
||||
Statistics* _statistics)
|
||||
: blob_db_impl_(_blob_db_impl), env_(_env), statistics_(_statistics) {}
|
||||
|
||||
protected:
|
||||
BlobDBImpl* blob_db_impl() const { return blob_db_impl_; }
|
||||
@@ -134,9 +134,10 @@ class BlobIndexCompactionFilterFactoryBase : public CompactionFilterFactory {
|
||||
class BlobIndexCompactionFilterFactory
|
||||
: public BlobIndexCompactionFilterFactoryBase {
|
||||
public:
|
||||
BlobIndexCompactionFilterFactory(BlobDBImpl* blob_db_impl, Env* env,
|
||||
Statistics* statistics)
|
||||
: BlobIndexCompactionFilterFactoryBase(blob_db_impl, env, statistics) {}
|
||||
BlobIndexCompactionFilterFactory(BlobDBImpl* _blob_db_impl, Env* _env,
|
||||
Statistics* _statistics)
|
||||
: BlobIndexCompactionFilterFactoryBase(_blob_db_impl, _env, _statistics) {
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "BlobIndexCompactionFilterFactory";
|
||||
@@ -149,9 +150,10 @@ class BlobIndexCompactionFilterFactory
|
||||
class BlobIndexCompactionFilterFactoryGC
|
||||
: public BlobIndexCompactionFilterFactoryBase {
|
||||
public:
|
||||
BlobIndexCompactionFilterFactoryGC(BlobDBImpl* blob_db_impl, Env* env,
|
||||
Statistics* statistics)
|
||||
: BlobIndexCompactionFilterFactoryBase(blob_db_impl, env, statistics) {}
|
||||
BlobIndexCompactionFilterFactoryGC(BlobDBImpl* _blob_db_impl, Env* _env,
|
||||
Statistics* _statistics)
|
||||
: BlobIndexCompactionFilterFactoryBase(_blob_db_impl, _env, _statistics) {
|
||||
}
|
||||
|
||||
const char* Name() const override {
|
||||
return "BlobIndexCompactionFilterFactoryGC";
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace blob_db {
|
||||
|
||||
// A wrapped database which puts values of KV pairs in a separate log
|
||||
// and store location to the log in the underlying DB.
|
||||
// It lacks lots of importatant functionalities, e.g. DB restarts,
|
||||
// garbage collection, iterators, etc.
|
||||
//
|
||||
// The factory needs to be moved to include/rocksdb/utilities to allow
|
||||
// users to use blob DB.
|
||||
|
||||
@@ -940,7 +940,6 @@ Status BlobDBImpl::PutUntil(const WriteOptions& options, const Slice& key,
|
||||
const Slice& value, uint64_t expiration) {
|
||||
StopWatch write_sw(env_, statistics_, BLOB_DB_WRITE_MICROS);
|
||||
RecordTick(statistics_, BLOB_DB_NUM_PUT);
|
||||
TEST_SYNC_POINT("BlobDBImpl::PutUntil:Start");
|
||||
Status s;
|
||||
WriteBatch batch;
|
||||
{
|
||||
@@ -953,7 +952,6 @@ Status BlobDBImpl::PutUntil(const WriteOptions& options, const Slice& key,
|
||||
if (s.ok()) {
|
||||
s = db_->Write(options, &batch);
|
||||
}
|
||||
TEST_SYNC_POINT("BlobDBImpl::PutUntil:Finish");
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1531,8 +1529,6 @@ Status BlobDBImpl::GetImpl(const ReadOptions& read_options,
|
||||
get_impl_options.value = &index_entry;
|
||||
get_impl_options.is_blob_index = &is_blob_index;
|
||||
s = db_impl_->GetImpl(ro, key, get_impl_options);
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:1");
|
||||
TEST_SYNC_POINT("BlobDBImpl::Get:AfterIndexEntryGet:2");
|
||||
if (expiration != nullptr) {
|
||||
*expiration = kNoExpiration;
|
||||
}
|
||||
@@ -1836,321 +1832,6 @@ std::pair<bool, int64_t> BlobDBImpl::ReclaimOpenFiles(bool aborted) {
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
|
||||
// Write callback for garbage collection to check if key has been updated
|
||||
// since last read. Similar to how OptimisticTransaction works. See inline
|
||||
// comment in GCFileAndUpdateLSM().
|
||||
class BlobDBImpl::GarbageCollectionWriteCallback : public WriteCallback {
|
||||
public:
|
||||
GarbageCollectionWriteCallback(ColumnFamilyData* cfd, const Slice& key,
|
||||
SequenceNumber upper_bound)
|
||||
: cfd_(cfd), key_(key), upper_bound_(upper_bound) {}
|
||||
|
||||
Status Callback(DB* db) override {
|
||||
auto* db_impl = static_cast_with_check<DBImpl, DB>(db);
|
||||
auto* sv = db_impl->GetAndRefSuperVersion(cfd_);
|
||||
SequenceNumber latest_seq = 0;
|
||||
bool found_record_for_key = false;
|
||||
bool is_blob_index = false;
|
||||
Status s = db_impl->GetLatestSequenceForKey(
|
||||
sv, key_, false /*cache_only*/, 0 /*lower_bound_seq*/, &latest_seq,
|
||||
&found_record_for_key, &is_blob_index);
|
||||
db_impl->ReturnAndCleanupSuperVersion(cfd_, sv);
|
||||
if (!s.ok() && !s.IsNotFound()) {
|
||||
// Error.
|
||||
assert(!s.IsBusy());
|
||||
return s;
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
assert(!found_record_for_key);
|
||||
return Status::Busy("Key deleted");
|
||||
}
|
||||
assert(found_record_for_key);
|
||||
assert(is_blob_index);
|
||||
if (latest_seq > upper_bound_) {
|
||||
return Status::Busy("Key overwritten");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
bool AllowWriteBatching() override { return false; }
|
||||
|
||||
private:
|
||||
ColumnFamilyData* cfd_;
|
||||
// Key to check
|
||||
Slice key_;
|
||||
// Upper bound of sequence number to proceed.
|
||||
SequenceNumber upper_bound_;
|
||||
};
|
||||
|
||||
// iterate over the blobs sequentially and check if the blob sequence number
|
||||
// is the latest. If it is the latest, preserve it, otherwise delete it
|
||||
// if it is TTL based, and the TTL has expired, then
|
||||
// we can blow the entity if the key is still the latest or the Key is not
|
||||
// found
|
||||
// WHAT HAPPENS IF THE KEY HAS BEEN OVERRIDEN. Then we can drop the blob
|
||||
// without doing anything if the earliest snapshot is not
|
||||
// referring to that sequence number, i.e. it is later than the sequence number
|
||||
// of the new key
|
||||
//
|
||||
// if it is not TTL based, then we can blow the key if the key has been
|
||||
// DELETED in the LSM
|
||||
Status BlobDBImpl::GCFileAndUpdateLSM(const std::shared_ptr<BlobFile>& bfptr,
|
||||
GCStats* gc_stats) {
|
||||
StopWatch gc_sw(env_, statistics_, BLOB_DB_GC_MICROS);
|
||||
uint64_t now = EpochNow();
|
||||
|
||||
std::shared_ptr<Reader> reader =
|
||||
bfptr->OpenRandomAccessReader(env_, db_options_, env_options_);
|
||||
if (!reader) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"File sequential reader could not be opened for %s",
|
||||
bfptr->PathName().c_str());
|
||||
return Status::IOError("failed to create sequential reader");
|
||||
}
|
||||
|
||||
BlobLogHeader header;
|
||||
Status s = reader->ReadHeader(&header);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Failure to read header for blob-file %s",
|
||||
bfptr->PathName().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
auto cfh = db_impl_->DefaultColumnFamily();
|
||||
auto* cfd = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh)->cfd();
|
||||
auto column_family_id = cfd->GetID();
|
||||
bool has_ttl = header.has_ttl;
|
||||
|
||||
// this reads the key but skips the blob
|
||||
Reader::ReadLevel shallow = Reader::kReadHeaderKey;
|
||||
|
||||
ExpirationRange expiration_range;
|
||||
|
||||
{
|
||||
ReadLock file_lock(&bfptr->mutex_);
|
||||
expiration_range = bfptr->GetExpirationRange();
|
||||
}
|
||||
|
||||
bool file_expired = has_ttl && now >= expiration_range.second;
|
||||
|
||||
if (!file_expired) {
|
||||
// read the blob because you have to write it back to new file
|
||||
shallow = Reader::kReadHeaderKeyBlob;
|
||||
}
|
||||
|
||||
BlobLogRecord record;
|
||||
std::shared_ptr<BlobFile> newfile;
|
||||
std::shared_ptr<Writer> new_writer;
|
||||
uint64_t blob_offset = 0;
|
||||
|
||||
while (true) {
|
||||
assert(s.ok());
|
||||
|
||||
// Read the next blob record.
|
||||
Status read_record_status =
|
||||
reader->ReadRecord(&record, shallow, &blob_offset);
|
||||
// Exit if we reach the end of blob file.
|
||||
// TODO(yiwu): properly handle ReadRecord error.
|
||||
if (!read_record_status.ok()) {
|
||||
break;
|
||||
}
|
||||
gc_stats->blob_count++;
|
||||
|
||||
// Similar to OptimisticTransaction, we obtain latest_seq from
|
||||
// base DB, which is guaranteed to be no smaller than the sequence of
|
||||
// current key. We use a WriteCallback on write to check the key sequence
|
||||
// on write. If the key sequence is larger than latest_seq, we know
|
||||
// a new versions is inserted and the old blob can be disgard.
|
||||
//
|
||||
// We cannot use OptimisticTransaction because we need to pass
|
||||
// is_blob_index flag to GetImpl.
|
||||
SequenceNumber latest_seq = GetLatestSequenceNumber();
|
||||
bool is_blob_index = false;
|
||||
PinnableSlice index_entry;
|
||||
DBImpl::GetImplOptions get_impl_options;
|
||||
get_impl_options.column_family = cfh;
|
||||
get_impl_options.value = &index_entry;
|
||||
get_impl_options.is_blob_index = &is_blob_index;
|
||||
Status get_status =
|
||||
db_impl_->GetImpl(ReadOptions(), record.key, get_impl_options);
|
||||
TEST_SYNC_POINT("BlobDBImpl::GCFileAndUpdateLSM:AfterGetFromBaseDB");
|
||||
if (!get_status.ok() && !get_status.IsNotFound()) {
|
||||
// error
|
||||
s = get_status;
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Error while getting index entry: %s",
|
||||
s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
if (get_status.IsNotFound() || !is_blob_index) {
|
||||
// Either the key is deleted or updated with a newer version whish is
|
||||
// inlined in LSM.
|
||||
gc_stats->num_keys_overwritten++;
|
||||
gc_stats->bytes_overwritten += record.record_size();
|
||||
continue;
|
||||
}
|
||||
|
||||
BlobIndex blob_index;
|
||||
s = blob_index.DecodeFrom(index_entry);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Error while decoding index entry: %s",
|
||||
s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
if (blob_index.IsInlined() ||
|
||||
blob_index.file_number() != bfptr->BlobFileNumber() ||
|
||||
blob_index.offset() != blob_offset) {
|
||||
// Key has been overwritten. Drop the blob record.
|
||||
gc_stats->num_keys_overwritten++;
|
||||
gc_stats->bytes_overwritten += record.record_size();
|
||||
continue;
|
||||
}
|
||||
|
||||
GarbageCollectionWriteCallback callback(cfd, record.key, latest_seq);
|
||||
|
||||
// If key has expired, remove it from base DB.
|
||||
// TODO(yiwu): Blob indexes will be remove by BlobIndexCompactionFilter.
|
||||
// We can just drop the blob record.
|
||||
if (file_expired || (has_ttl && now >= record.expiration)) {
|
||||
gc_stats->num_keys_expired++;
|
||||
gc_stats->bytes_expired += record.record_size();
|
||||
TEST_SYNC_POINT("BlobDBImpl::GCFileAndUpdateLSM:BeforeDelete");
|
||||
WriteBatch delete_batch;
|
||||
Status delete_status = delete_batch.Delete(record.key);
|
||||
if (delete_status.ok()) {
|
||||
delete_status = db_impl_->WriteWithCallback(WriteOptions(),
|
||||
&delete_batch, &callback);
|
||||
}
|
||||
if (!delete_status.ok() && !delete_status.IsBusy()) {
|
||||
// We hit an error.
|
||||
s = delete_status;
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Error while deleting expired key: %s",
|
||||
s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
// Continue to next blob record or retry.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Relocate the blob record to new file.
|
||||
if (!newfile) {
|
||||
// new file
|
||||
std::string reason("GC of ");
|
||||
reason += bfptr->PathName();
|
||||
newfile = NewBlobFile(bfptr->HasTTL(), bfptr->expiration_range_, reason);
|
||||
|
||||
s = CheckOrCreateWriterLocked(newfile, &new_writer);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"Failed to open file %s for writer, error: %s",
|
||||
newfile->PathName().c_str(), s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
newfile->file_size_ = BlobLogHeader::kSize;
|
||||
|
||||
s = new_writer->WriteHeader(newfile->header_);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"File: %s - header writing failed",
|
||||
newfile->PathName().c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
// We don't add the file to open_ttl_files_ or open_non_ttl_files_, to
|
||||
// avoid user writes writing to the file, and avoid
|
||||
// EvictExpiredFiles close the file by mistake.
|
||||
WriteLock wl(&mutex_);
|
||||
blob_files_.insert(std::make_pair(newfile->BlobFileNumber(), newfile));
|
||||
}
|
||||
|
||||
std::string new_index_entry;
|
||||
uint64_t new_blob_offset = 0;
|
||||
uint64_t new_key_offset = 0;
|
||||
// write the blob to the blob log.
|
||||
s = new_writer->AddRecord(record.key, record.value, record.expiration,
|
||||
&new_key_offset, &new_blob_offset);
|
||||
|
||||
BlobIndex::EncodeBlob(&new_index_entry, newfile->BlobFileNumber(),
|
||||
new_blob_offset, record.value.size(),
|
||||
bdb_options_.compression);
|
||||
|
||||
newfile->blob_count_++;
|
||||
newfile->file_size_ +=
|
||||
BlobLogRecord::kHeaderSize + record.key.size() + record.value.size();
|
||||
|
||||
TEST_SYNC_POINT("BlobDBImpl::GCFileAndUpdateLSM:BeforeRelocate");
|
||||
WriteBatch rewrite_batch;
|
||||
Status rewrite_status = WriteBatchInternal::PutBlobIndex(
|
||||
&rewrite_batch, column_family_id, record.key, new_index_entry);
|
||||
if (rewrite_status.ok()) {
|
||||
rewrite_status = db_impl_->WriteWithCallback(WriteOptions(),
|
||||
&rewrite_batch, &callback);
|
||||
}
|
||||
if (rewrite_status.ok()) {
|
||||
gc_stats->num_keys_relocated++;
|
||||
gc_stats->bytes_relocated += record.record_size();
|
||||
} else if (rewrite_status.IsBusy()) {
|
||||
// The key is overwritten in the meanwhile. Drop the blob record.
|
||||
gc_stats->num_keys_overwritten++;
|
||||
gc_stats->bytes_overwritten += record.record_size();
|
||||
} else {
|
||||
// We hit an error.
|
||||
s = rewrite_status;
|
||||
ROCKS_LOG_ERROR(db_options_.info_log, "Error while relocating key: %s",
|
||||
s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
} // end of ReadRecord loop
|
||||
|
||||
{
|
||||
WriteLock wl(&mutex_);
|
||||
ObsoleteBlobFile(bfptr, GetLatestSequenceNumber(), true /*update_size*/);
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"%s blob file %" PRIu64 ". Total blob records: %" PRIu64
|
||||
", expired: %" PRIu64 " keys/%" PRIu64
|
||||
" bytes, updated or deleted by user: %" PRIu64 " keys/%" PRIu64
|
||||
" bytes, rewrite to new file: %" PRIu64 " keys/%" PRIu64 " bytes.",
|
||||
s.ok() ? "Successfully garbage collected" : "Failed to garbage collect",
|
||||
bfptr->BlobFileNumber(), gc_stats->blob_count, gc_stats->num_keys_expired,
|
||||
gc_stats->bytes_expired, gc_stats->num_keys_overwritten,
|
||||
gc_stats->bytes_overwritten, gc_stats->num_keys_relocated,
|
||||
gc_stats->bytes_relocated);
|
||||
RecordTick(statistics_, BLOB_DB_GC_NUM_FILES);
|
||||
RecordTick(statistics_, BLOB_DB_GC_NUM_KEYS_OVERWRITTEN,
|
||||
gc_stats->num_keys_overwritten);
|
||||
RecordTick(statistics_, BLOB_DB_GC_NUM_KEYS_EXPIRED,
|
||||
gc_stats->num_keys_expired);
|
||||
RecordTick(statistics_, BLOB_DB_GC_BYTES_OVERWRITTEN,
|
||||
gc_stats->bytes_overwritten);
|
||||
RecordTick(statistics_, BLOB_DB_GC_BYTES_EXPIRED, gc_stats->bytes_expired);
|
||||
if (newfile != nullptr) {
|
||||
{
|
||||
MutexLock l(&write_mutex_);
|
||||
WriteLock lock(&mutex_);
|
||||
WriteLock file_lock(&newfile->mutex_);
|
||||
CloseBlobFile(newfile);
|
||||
}
|
||||
total_blob_size_ += newfile->file_size_;
|
||||
ROCKS_LOG_INFO(db_options_.info_log, "New blob file %" PRIu64 ".",
|
||||
newfile->BlobFileNumber());
|
||||
RecordTick(statistics_, BLOB_DB_GC_NUM_NEW_FILES);
|
||||
RecordTick(statistics_, BLOB_DB_GC_NUM_KEYS_RELOCATED,
|
||||
gc_stats->num_keys_relocated);
|
||||
RecordTick(statistics_, BLOB_DB_GC_BYTES_RELOCATED,
|
||||
gc_stats->bytes_relocated);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics_, BLOB_DB_GC_FAILURES);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::pair<bool, int64_t> BlobDBImpl::DeleteObsoleteFiles(bool aborted) {
|
||||
if (aborted) {
|
||||
return std::make_pair(false, -1);
|
||||
@@ -2240,17 +1921,6 @@ void BlobDBImpl::CopyBlobFiles(
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, int64_t> BlobDBImpl::RunGC(bool aborted) {
|
||||
if (aborted) {
|
||||
return std::make_pair(false, -1);
|
||||
}
|
||||
|
||||
// TODO(yiwu): Garbage collection implementation.
|
||||
|
||||
// reschedule
|
||||
return std::make_pair(true, -1);
|
||||
}
|
||||
|
||||
Iterator* BlobDBImpl::NewIterator(const ReadOptions& read_options) {
|
||||
auto* cfd =
|
||||
reinterpret_cast<ColumnFamilyHandleImpl*>(DefaultColumnFamily())->cfd();
|
||||
@@ -2365,13 +2035,6 @@ void BlobDBImpl::TEST_ObsoleteBlobFile(std::shared_ptr<BlobFile>& blob_file,
|
||||
return ObsoleteBlobFile(blob_file, obsolete_seq, update_size);
|
||||
}
|
||||
|
||||
Status BlobDBImpl::TEST_GCFileAndUpdateLSM(std::shared_ptr<BlobFile>& bfile,
|
||||
GCStats* gc_stats) {
|
||||
return GCFileAndUpdateLSM(bfile, gc_stats);
|
||||
}
|
||||
|
||||
void BlobDBImpl::TEST_RunGC() { RunGC(false /*abort*/); }
|
||||
|
||||
void BlobDBImpl::TEST_EvictExpiredFiles() {
|
||||
EvictExpiredFiles(false /*abort*/);
|
||||
}
|
||||
|
||||
@@ -60,20 +60,11 @@ struct BlobFileComparator {
|
||||
const std::shared_ptr<BlobFile>& rhs) const;
|
||||
};
|
||||
|
||||
struct GCStats {
|
||||
uint64_t blob_count = 0;
|
||||
uint64_t num_keys_overwritten = 0;
|
||||
uint64_t num_keys_expired = 0;
|
||||
uint64_t num_keys_relocated = 0;
|
||||
uint64_t bytes_overwritten = 0;
|
||||
uint64_t bytes_expired = 0;
|
||||
uint64_t bytes_relocated = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The implementation class for BlobDB. This manages the value
|
||||
* part in TTL aware sequentially written files. These files are
|
||||
* Garbage Collected.
|
||||
* The implementation class for BlobDB. It manages the blob logs, which
|
||||
* are sequentially written files. Blob logs can be of the TTL or non-TTL
|
||||
* varieties; the former are cleaned up when they expire, while the latter
|
||||
* are (optionally) garbage collected.
|
||||
*/
|
||||
class BlobDBImpl : public BlobDB {
|
||||
friend class BlobFile;
|
||||
@@ -86,12 +77,6 @@ class BlobDBImpl : public BlobDB {
|
||||
// deletions check period
|
||||
static constexpr uint32_t kDeleteCheckPeriodMillisecs = 2 * 1000;
|
||||
|
||||
// gc percentage each check period
|
||||
static constexpr uint32_t kGCFilePercentage = 100;
|
||||
|
||||
// gc period
|
||||
static constexpr uint32_t kGCCheckPeriodMillisecs = 60 * 1000;
|
||||
|
||||
// sanity check task
|
||||
static constexpr uint32_t kSanityCheckPeriodMillisecs = 20 * 60 * 1000;
|
||||
|
||||
@@ -208,11 +193,6 @@ class BlobDBImpl : public BlobDB {
|
||||
SequenceNumber obsolete_seq = 0,
|
||||
bool update_size = true);
|
||||
|
||||
Status TEST_GCFileAndUpdateLSM(std::shared_ptr<BlobFile>& bfile,
|
||||
GCStats* gc_stats);
|
||||
|
||||
void TEST_RunGC();
|
||||
|
||||
void TEST_EvictExpiredFiles();
|
||||
|
||||
void TEST_DeleteObsoleteFiles();
|
||||
@@ -231,7 +211,6 @@ class BlobDBImpl : public BlobDB {
|
||||
#endif // !NDEBUG
|
||||
|
||||
private:
|
||||
class GarbageCollectionWriteCallback;
|
||||
class BlobInserter;
|
||||
|
||||
// Create a snapshot if there isn't one in read options.
|
||||
@@ -300,14 +279,10 @@ class BlobDBImpl : public BlobDB {
|
||||
// periodic sanity check. Bunch of checks
|
||||
std::pair<bool, int64_t> SanityCheck(bool aborted);
|
||||
|
||||
// delete files which have been garbage collected and marked
|
||||
// obsolete. Check whether any snapshots exist which refer to
|
||||
// the same
|
||||
// Delete files that have been marked obsolete (either because of TTL
|
||||
// or GC). Check whether any snapshots exist which refer to the same.
|
||||
std::pair<bool, int64_t> DeleteObsoleteFiles(bool aborted);
|
||||
|
||||
// Major task to garbage collect expired and deleted blobs
|
||||
std::pair<bool, int64_t> RunGC(bool aborted);
|
||||
|
||||
// periodically check if open blob files and their TTL's has expired
|
||||
// if expired, close the sequential writer and make the file immutable
|
||||
std::pair<bool, int64_t> EvictExpiredFiles(bool aborted);
|
||||
@@ -398,12 +373,6 @@ class BlobDBImpl : public BlobDB {
|
||||
Status CheckOrCreateWriterLocked(const std::shared_ptr<BlobFile>& blob_file,
|
||||
std::shared_ptr<Writer>* writer);
|
||||
|
||||
// Iterate through keys and values on Blob and write into
|
||||
// separate file the remaining blobs and delete/update pointers
|
||||
// in LSM atomically
|
||||
Status GCFileAndUpdateLSM(const std::shared_ptr<BlobFile>& bfptr,
|
||||
GCStats* gcstats);
|
||||
|
||||
// checks if there is no snapshot which is referencing the
|
||||
// blobs
|
||||
bool VisibleToActiveSnapshot(const std::shared_ptr<BlobFile>& file);
|
||||
|
||||
@@ -117,8 +117,6 @@ class BlobDBIterator : public Iterator {
|
||||
private:
|
||||
// Return true if caller should continue to next value.
|
||||
bool UpdateBlobValue() {
|
||||
TEST_SYNC_POINT("BlobDBIterator::UpdateBlobValue:Start:1");
|
||||
TEST_SYNC_POINT("BlobDBIterator::UpdateBlobValue:Start:2");
|
||||
value_.Reset();
|
||||
status_ = Status::OK();
|
||||
if (iter_->Valid() && iter_->status().ok() && iter_->IsBlob()) {
|
||||
|
||||
@@ -341,10 +341,6 @@ TEST_F(BlobDBTest, PutWithTTL) {
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(data.size(), gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
@@ -371,10 +367,6 @@ TEST_F(BlobDBTest, PutUntil) {
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_OK(bdb_impl->TEST_CloseBlobFile(blob_files[0]));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(bdb_impl->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(100 - data.size(), gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(data.size(), gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
@@ -588,243 +580,6 @@ TEST_F(BlobDBTest, MultipleWriters) {
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCAfterOverwriteKeys) {
|
||||
Random rnd(301);
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
DBImpl *db_impl = static_cast_with_check<DBImpl, DB>(blob_db_->GetBaseDB());
|
||||
std::map<std::string, std::string> data;
|
||||
for (int i = 0; i < 200; i++) {
|
||||
PutRandom("key" + ToString(i), &rnd, &data);
|
||||
}
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
|
||||
// Test for data in SST
|
||||
size_t new_keys = 0;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (rnd.Next() % 2 == 1) {
|
||||
new_keys++;
|
||||
PutRandom("key" + ToString(i), &rnd, &data);
|
||||
}
|
||||
}
|
||||
db_impl->TEST_FlushMemTable(true /*wait*/);
|
||||
// Test for data in memtable
|
||||
for (int i = 100; i < 200; i++) {
|
||||
if (rnd.Next() % 2 == 1) {
|
||||
new_keys++;
|
||||
PutRandom("key" + ToString(i), &rnd, &data);
|
||||
}
|
||||
}
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(200, gc_stats.blob_count);
|
||||
ASSERT_EQ(0, gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(200 - new_keys, gc_stats.num_keys_relocated);
|
||||
VerifyDB(data);
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCRelocateKeyWhileOverwriting) {
|
||||
Random rnd(301);
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
ASSERT_OK(blob_db_->Put(WriteOptions(), "foo", "v1"));
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::GCFileAndUpdateLSM:AfterGetFromBaseDB",
|
||||
"BlobDBImpl::PutUntil:Start"},
|
||||
{"BlobDBImpl::PutUntil:Finish",
|
||||
"BlobDBImpl::GCFileAndUpdateLSM:BeforeRelocate"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto writer = port::Thread(
|
||||
[this]() { ASSERT_OK(blob_db_->Put(WriteOptions(), "foo", "v2")); });
|
||||
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
ASSERT_EQ(0, gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(1, gc_stats.num_keys_overwritten);
|
||||
ASSERT_EQ(0, gc_stats.num_keys_relocated);
|
||||
writer.join();
|
||||
VerifyDB({{"foo", "v2"}});
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, GCExpiredKeyWhileOverwriting) {
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options, options);
|
||||
mock_env_->set_current_time(100);
|
||||
ASSERT_OK(blob_db_->PutUntil(WriteOptions(), "foo", "v1", 200));
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
|
||||
mock_env_->set_current_time(300);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::GCFileAndUpdateLSM:AfterGetFromBaseDB",
|
||||
"BlobDBImpl::PutUntil:Start"},
|
||||
{"BlobDBImpl::PutUntil:Finish",
|
||||
"BlobDBImpl::GCFileAndUpdateLSM:BeforeDelete"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto writer = port::Thread([this]() {
|
||||
ASSERT_OK(blob_db_->PutUntil(WriteOptions(), "foo", "v2", 400));
|
||||
});
|
||||
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(blob_files[0], &gc_stats));
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
ASSERT_EQ(1, gc_stats.num_keys_expired);
|
||||
ASSERT_EQ(0, gc_stats.num_keys_relocated);
|
||||
writer.join();
|
||||
VerifyDB({{"foo", "v2"}});
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, NewFileGeneratedFromGCShouldMarkAsImmutable) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
auto blob_file1 = blob_files[0];
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_file1));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(blob_file1, &gc_stats));
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
ASSERT_EQ(1, gc_stats.num_keys_relocated);
|
||||
blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(2, blob_files.size());
|
||||
ASSERT_EQ(blob_file1, blob_files[0]);
|
||||
ASSERT_TRUE(blob_files[1]->Immutable());
|
||||
}
|
||||
|
||||
// This test is no longer valid since we now return an error when we go
|
||||
// over the configured max_db_size.
|
||||
// The test needs to be re-written later in such a way that writes continue
|
||||
// after a GC happens.
|
||||
TEST_F(BlobDBTest, DISABLED_GCOldestSimpleBlobFileWhenOutOfSpace) {
|
||||
// Use mock env to stop wall clock.
|
||||
Options options;
|
||||
options.env = mock_env_.get();
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.max_db_size = 100;
|
||||
bdb_options.blob_file_size = 100;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
std::string value(100, 'v');
|
||||
ASSERT_OK(blob_db_->PutWithTTL(WriteOptions(), "key_with_ttl", value, 60));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(blob_db_->Put(WriteOptions(), "key" + ToString(i), value));
|
||||
}
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(11, blob_files.size());
|
||||
ASSERT_TRUE(blob_files[0]->HasTTL());
|
||||
ASSERT_TRUE(blob_files[0]->Immutable());
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
ASSERT_FALSE(blob_files[i]->HasTTL());
|
||||
if (i < 10) {
|
||||
ASSERT_TRUE(blob_files[i]->Immutable());
|
||||
}
|
||||
}
|
||||
blob_db_impl()->TEST_RunGC();
|
||||
// The oldest simple blob file (i.e. blob_files[1]) has been selected for GC.
|
||||
auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
|
||||
ASSERT_EQ(1, obsolete_files.size());
|
||||
ASSERT_EQ(blob_files[1]->BlobFileNumber(),
|
||||
obsolete_files[0]->BlobFileNumber());
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, ReadWhileGC) {
|
||||
// run the same test for Get(), MultiGet() and Iterator each.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
Open(bdb_options);
|
||||
blob_db_->Put(WriteOptions(), "foo", "bar");
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
std::shared_ptr<BlobFile> bfile = blob_files[0];
|
||||
uint64_t bfile_number = bfile->BlobFileNumber();
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBImpl::Get:AfterIndexEntryGet:1",
|
||||
"BlobDBTest::ReadWhileGC:1"},
|
||||
{"BlobDBTest::ReadWhileGC:2",
|
||||
"BlobDBImpl::Get:AfterIndexEntryGet:2"}});
|
||||
break;
|
||||
case 1:
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"BlobDBIterator::UpdateBlobValue:Start:1",
|
||||
"BlobDBTest::ReadWhileGC:1"},
|
||||
{"BlobDBTest::ReadWhileGC:2",
|
||||
"BlobDBIterator::UpdateBlobValue:Start:2"}});
|
||||
break;
|
||||
}
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto reader = port::Thread([this, i]() {
|
||||
std::string value;
|
||||
std::vector<std::string> values;
|
||||
std::vector<Status> statuses;
|
||||
switch (i) {
|
||||
case 0:
|
||||
ASSERT_OK(blob_db_->Get(ReadOptions(), "foo", &value));
|
||||
ASSERT_EQ("bar", value);
|
||||
break;
|
||||
case 1:
|
||||
// VerifyDB use iterator to scan the DB.
|
||||
VerifyDB({{"foo", "bar"}});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("BlobDBTest::ReadWhileGC:1");
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(bfile, &gc_stats));
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
ASSERT_EQ(1, gc_stats.num_keys_relocated);
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
// The file shouln't be deleted
|
||||
blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(2, blob_files.size());
|
||||
ASSERT_EQ(bfile_number, blob_files[0]->BlobFileNumber());
|
||||
auto obsolete_files = blob_db_impl()->TEST_GetObsoleteFiles();
|
||||
ASSERT_EQ(1, obsolete_files.size());
|
||||
ASSERT_EQ(bfile_number, obsolete_files[0]->BlobFileNumber());
|
||||
TEST_SYNC_POINT("BlobDBTest::ReadWhileGC:2");
|
||||
reader.join();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// The file is deleted this time
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_NE(bfile_number, blob_files[0]->BlobFileNumber());
|
||||
ASSERT_EQ(0, blob_db_impl()->TEST_GetObsoleteFiles().size());
|
||||
VerifyDB({{"foo", "bar"}});
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, SstFileManager) {
|
||||
// run the same test for Get(), MultiGet() and Iterator each.
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(
|
||||
@@ -835,16 +590,20 @@ TEST_F(BlobDBTest, SstFileManager) {
|
||||
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.enable_garbage_collection = true;
|
||||
bdb_options.garbage_collection_cutoff = 1.0;
|
||||
Options db_options;
|
||||
|
||||
int files_deleted_directly = 0;
|
||||
int files_scheduled_to_delete = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleFileDeletion",
|
||||
[&](void * /*arg*/) { files_scheduled_to_delete++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile",
|
||||
[&](void * /*arg*/) { files_deleted_directly++; });
|
||||
"SstFileManagerImpl::ScheduleFileDeletion", [&](void *arg) {
|
||||
assert(arg);
|
||||
const std::string *const file_path =
|
||||
static_cast<const std::string *>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
db_options.sst_file_manager = sst_file_manager;
|
||||
|
||||
@@ -856,34 +615,29 @@ TEST_F(BlobDBTest, SstFileManager) {
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
std::shared_ptr<BlobFile> bfile = blob_files[0];
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(bfile, &gc_stats));
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
// Even if SSTFileManager is not set, DB is creating a dummy one.
|
||||
ASSERT_EQ(1, files_scheduled_to_delete);
|
||||
ASSERT_EQ(0, files_deleted_directly);
|
||||
Destroy();
|
||||
// Make sure that DestroyBlobDB() also goes through delete scheduler.
|
||||
ASSERT_GE(files_scheduled_to_delete, 2);
|
||||
// Due to a timing issue, the WAL may or may not be deleted directly. The
|
||||
// blob file is first scheduled, followed by WAL. If the background trash
|
||||
// thread does not wake up on time, the WAL file will be directly
|
||||
// deleted as the trash size will be > DB size
|
||||
ASSERT_LE(files_deleted_directly, 1);
|
||||
ASSERT_EQ(2, files_scheduled_to_delete);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
sfm->WaitForEmptyTrash();
|
||||
}
|
||||
|
||||
TEST_F(BlobDBTest, SstFileManagerRestart) {
|
||||
int files_deleted_directly = 0;
|
||||
int files_scheduled_to_delete = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleFileDeletion",
|
||||
[&](void * /*arg*/) { files_scheduled_to_delete++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile",
|
||||
[&](void * /*arg*/) { files_deleted_directly++; });
|
||||
"SstFileManagerImpl::ScheduleFileDeletion", [&](void *arg) {
|
||||
assert(arg);
|
||||
const std::string *const file_path =
|
||||
static_cast<const std::string *>(arg);
|
||||
if (file_path->find(".blob") != std::string::npos) {
|
||||
++files_scheduled_to_delete;
|
||||
}
|
||||
});
|
||||
|
||||
// run the same test for Get(), MultiGet() and Iterator each.
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(
|
||||
@@ -912,9 +666,7 @@ TEST_F(BlobDBTest, SstFileManagerRestart) {
|
||||
|
||||
// Make sure that reopening the DB rescan the existing trash files
|
||||
Open(bdb_options, db_options);
|
||||
ASSERT_GE(files_scheduled_to_delete, 3);
|
||||
// Depending on timing, the WAL file may or may not be directly deleted
|
||||
ASSERT_LE(files_deleted_directly, 1);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 2);
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
|
||||
@@ -937,67 +689,68 @@ TEST_F(BlobDBTest, SstFileManagerRestart) {
|
||||
TEST_F(BlobDBTest, SnapshotAndGarbageCollection) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
bdb_options.enable_garbage_collection = true;
|
||||
bdb_options.garbage_collection_cutoff = 1.0;
|
||||
bdb_options.disable_background_tasks = true;
|
||||
|
||||
// i = when to take snapshot
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (bool delete_key : {true, false}) {
|
||||
const Snapshot *snapshot = nullptr;
|
||||
Destroy();
|
||||
Open(bdb_options);
|
||||
// First file
|
||||
ASSERT_OK(Put("key1", "value"));
|
||||
if (i == 0) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
|
||||
// Second file
|
||||
ASSERT_OK(Put("key2", "value"));
|
||||
if (i == 1) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(2, blob_files.size());
|
||||
auto bfile = blob_files[1];
|
||||
ASSERT_FALSE(bfile->Immutable());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
|
||||
// Third file
|
||||
ASSERT_OK(Put("key3", "value"));
|
||||
if (i == 2) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
if (delete_key) {
|
||||
Delete("key2");
|
||||
}
|
||||
GCStats gc_stats;
|
||||
ASSERT_OK(blob_db_impl()->TEST_GCFileAndUpdateLSM(bfile, &gc_stats));
|
||||
ASSERT_TRUE(bfile->Obsolete());
|
||||
ASSERT_EQ(1, gc_stats.blob_count);
|
||||
if (delete_key) {
|
||||
ASSERT_EQ(0, gc_stats.num_keys_relocated);
|
||||
} else {
|
||||
ASSERT_EQ(1, gc_stats.num_keys_relocated);
|
||||
}
|
||||
ASSERT_EQ(blob_db_->GetLatestSequenceNumber(),
|
||||
bfile->GetObsoleteSequence());
|
||||
if (i == 3) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
size_t num_files = delete_key ? 3 : 4;
|
||||
ASSERT_EQ(num_files, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
Destroy();
|
||||
Open(bdb_options);
|
||||
|
||||
const Snapshot *snapshot = nullptr;
|
||||
|
||||
// First file
|
||||
ASSERT_OK(Put("key1", "value"));
|
||||
if (i == 0) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
|
||||
auto blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(blob_files[0]));
|
||||
|
||||
// Second file
|
||||
ASSERT_OK(Put("key2", "value"));
|
||||
if (i == 1) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
|
||||
blob_files = blob_db_impl()->TEST_GetBlobFiles();
|
||||
ASSERT_EQ(2, blob_files.size());
|
||||
auto bfile = blob_files[1];
|
||||
ASSERT_FALSE(bfile->Immutable());
|
||||
ASSERT_OK(blob_db_impl()->TEST_CloseBlobFile(bfile));
|
||||
|
||||
// Third file
|
||||
ASSERT_OK(Put("key3", "value"));
|
||||
if (i == 2) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_TRUE(bfile->Obsolete());
|
||||
ASSERT_EQ(blob_db_->GetLatestSequenceNumber(),
|
||||
bfile->GetObsoleteSequence());
|
||||
|
||||
Delete("key2");
|
||||
if (i == 3) {
|
||||
snapshot = blob_db_->GetSnapshot();
|
||||
}
|
||||
|
||||
ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
if (i >= 2) {
|
||||
// The snapshot shouldn't see data in bfile
|
||||
ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
blob_db_->ReleaseSnapshot(snapshot);
|
||||
} else {
|
||||
// The snapshot will see data in bfile, so the file shouldn't be deleted
|
||||
ASSERT_EQ(4, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
blob_db_->ReleaseSnapshot(snapshot);
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
if (i == 3) {
|
||||
// The snapshot shouldn't see data in bfile
|
||||
ASSERT_EQ(num_files - 1, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
blob_db_->ReleaseSnapshot(snapshot);
|
||||
} else {
|
||||
// The snapshot will see data in bfile, so the file shouldn't be deleted
|
||||
ASSERT_EQ(num_files, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
blob_db_->ReleaseSnapshot(snapshot);
|
||||
blob_db_impl()->TEST_DeleteObsoleteFiles();
|
||||
ASSERT_EQ(num_files - 1, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
}
|
||||
ASSERT_EQ(2, blob_db_impl()->TEST_GetBlobFiles().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1434,9 +1187,7 @@ TEST_F(BlobDBTest, FilterExpiredBlobIndex) {
|
||||
// filtered regardless of snapshot.
|
||||
const Snapshot *snapshot = blob_db_->GetSnapshot();
|
||||
// Issue manual compaction to trigger compaction filter.
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(),
|
||||
blob_db_->DefaultColumnFamily(), nullptr,
|
||||
nullptr));
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
blob_db_->ReleaseSnapshot(snapshot);
|
||||
// Verify expired blob index are filtered.
|
||||
std::vector<KeyVersion> versions;
|
||||
@@ -1450,7 +1201,7 @@ TEST_F(BlobDBTest, FilterExpiredBlobIndex) {
|
||||
}
|
||||
|
||||
// Test compaction filter should remove any blob index where corresponding
|
||||
// blob file has been removed (either by FIFO or garbage collection).
|
||||
// blob file has been removed.
|
||||
TEST_F(BlobDBTest, FilterFileNotAvailable) {
|
||||
BlobDBOptions bdb_options;
|
||||
bdb_options.min_blob_size = 0;
|
||||
@@ -1481,7 +1232,6 @@ TEST_F(BlobDBTest, FilterFileNotAvailable) {
|
||||
ASSERT_EQ("foo", versions[1].user_key);
|
||||
VerifyDB({{"bar", "v2"}, {"foo", "v1"}});
|
||||
|
||||
ASSERT_OK(blob_db_->Flush(FlushOptions()));
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(GetAllKeyVersions(base_db, "", "", kMaxKeys, &versions));
|
||||
ASSERT_EQ(2, versions.size());
|
||||
@@ -1720,9 +1470,7 @@ TEST_F(BlobDBTest, GarbageCollection) {
|
||||
|
||||
mock_env_->set_current_time(kCompactTime);
|
||||
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(),
|
||||
blob_db_->DefaultColumnFamily(), nullptr,
|
||||
nullptr));
|
||||
ASSERT_OK(blob_db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// We expect the data to remain the same and the blobs from the oldest N files
|
||||
// to be moved to new files. Sequence numbers get zeroed out during the
|
||||
|
||||
@@ -76,7 +76,7 @@ class BlobFile {
|
||||
// The latest sequence number when the file was closed/made immutable.
|
||||
SequenceNumber immutable_sequence_{0};
|
||||
|
||||
// has a pass of garbage collection successfully finished on this file
|
||||
// Whether the file was marked obsolete (due to either TTL or GC).
|
||||
// obsolete_ still needs to do iterator/snapshot checks
|
||||
std::atomic<bool> obsolete_{false};
|
||||
|
||||
@@ -168,13 +168,13 @@ class BlobFile {
|
||||
return immutable_sequence_;
|
||||
}
|
||||
|
||||
// if the file has gone through GC and blobs have been relocated
|
||||
// Whether the file was marked obsolete (due to either TTL or GC).
|
||||
bool Obsolete() const {
|
||||
assert(Immutable() || !obsolete_.load());
|
||||
return obsolete_.load();
|
||||
}
|
||||
|
||||
// Mark file as obsolete by garbage collection. The file is not visible to
|
||||
// Mark file as obsolete (due to either TTL or GC). The file is not visible to
|
||||
// snapshots with sequence greater or equal to the given sequence.
|
||||
void MarkObsolete(SequenceNumber sequence);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "include/rocksdb/comparator.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include "include/rocksdb/env.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -5820,7 +5820,7 @@ TEST_P(TransactionTest, DuplicateKeys) {
|
||||
} // do_rollback
|
||||
} // do_prepare
|
||||
|
||||
if (!options.unordered_write && !options.allow_concurrent_memtable_write) {
|
||||
if (!options.unordered_write) {
|
||||
// Also test with max_successive_merges > 0. max_successive_merges will not
|
||||
// affect our algorithm for duplicate key insertion but we add the test to
|
||||
// verify that.
|
||||
|
||||
Reference in new issue
Block a user